[HN Gopher] The Hunt for Error -22
       ___________________________________________________________________
        
       The Hunt for Error -22
        
       Author : mpalme
       Score  : 50 points
       Date   : 2025-01-22 12:07 UTC (4 days ago)
        
 (HTM) web link (tweedegolf.nl)
 (TXT) w3m dump (tweedegolf.nl)
        
       | sagacity wrote:
       | This reminds of a similar issue I had recently when working with
       | Rust and dx12 using the excellent windows-rs crates. Dx12 is
       | designed around the concept of command buffers that only get
       | submitted and evaluated at (usually) the end of the frame. This
       | means that you need to take care to keep references around to the
       | data you're submitting because otherwise dx12 will be looking at
       | broken pointers once the command buffers actually get executed.
       | 
       | The tooling around this is much easier to deal with it, of
       | course, since it's all just Windows and there's a bunch of sane
       | debug layers that you can use.
       | 
       | Massive respect to be able to debug the issue on an embedded
       | system!
        
       | antithesis-nl wrote:
       | Yeah, overly complicated firmware like this just isn't great.
       | It's _always_ hard to debug and fix these issues, and there is no
       | "if everything were just written in X" miracle-world where that
       | goes away.
       | 
       | For example, I've encountered hardware that would occasionally
       | write unexpected-error details to a memory location that was
       | completely undocumented. And if you expect more than a shrug from
       | a vendor after pointing out such things, well...
        
       | macgyverismo wrote:
       | I can see how the author came to the conclusion that libmodem
       | written in rust would have prevented the issue, but isn't it
       | simply pushing the problem further down the stack?
       | 
       | The author needed to use unsafe in order to pass his pointer to
       | libmodem, but libmodem is going to require a pointer with static
       | lifetime itself. Which would have prevented the issue in the
       | first place had the author done this.
       | 
       | I can see why you wouldn't want to use static, it hinders
       | testability, but that means you need to ensure that the pointer
       | you supply libmodem outlives libmodem. I would use RAII to do
       | that in C++ and I am sure in rust you could/would do the same.
       | 
       | I guess I am asking, is there anything here that a libmodem
       | written in rust would have magically solved? It feels like
       | wishful thinking, but I am open to learn where I am mistaken.
       | 
       | In any case, kudos for finding this bug. Having worked with
       | Zephyr/NRF connect SDK and this exact chip myself I can
       | definitely relate to the pain they (can) bring.
        
         | consp wrote:
         | The author mentioned in the first chapter that everything works
         | fine in rust, since it solves all problems. So I guess they
         | throw "better in rust" against every problem.
         | 
         | The assumption nobody ever makes mistakes is mistake one.
        
         | orf wrote:
         | > I guess I am asking, is there anything here that a libmodem
         | written in rust would have magically solved?
         | 
         | I'm not following your comment, but I think the point is simply
         | "the lifetime of the config is in the function signature,
         | rather than _hopefully_ ( _sometimes_ ) being in the
         | documentation, and _hopefully_ ( _sometimes_ ) correct".
        
         | swatcoder wrote:
         | The existing C interface doesn't have means to describe the
         | lifetime of the data being passed in. It just takes a pointer.
         | An experienced C programmer would often understand what's
         | happening _by convention_ and not encounter the problem.
         | 
         | But the custom Rust wrapper was composed as a game of telephone
         | (ugh), with the author blindly mimicking "Jonathan" who seemed
         | to have been blindly mimicking a sloppy (and later repaired)
         | example from Nordic.
         | 
         | The argument is that if the library and its internals were
         | originally written in Rust, which has richer semantics for
         | object lifetimes, Rust would have been able to _formally_
         | convey that the input data needed to outlive the individual
         | function call, throwing an error at compile time.
         | 
         | The wrapper could have enforced this constraint itself, as it
         | probably does now, but the handoff between Rust and C needs
         | somebody to account for and understand the _by convention_
         | stuff in C so that it can be expressed _formally_ in Rust, and
         | that human process failed to happen here.
        
           | wrs wrote:
           | If I read the article correctly, Nordic _changed_ the rules
           | on this function without saying anything. It used to work
           | with a stack-allocated config and now it doesn't. The only
           | way for the caller to know about that in C is a comment.
        
             | swatcoder wrote:
             | That's possible, but unlikely given conventions in embedded
             | development and how something like this interface would
             | generally need to work.
             | 
             | More likely (but not necessarily), Nordic's early example
             | was either bugged or conditionally valid (benefiting from
             | other implicit details of their implementation) and then
             | was revised either because the mistake was identified or
             | something else about the example change.
             | 
             | That's all pretty common in this domain. Inadvertently
             | stumbling because you uncritically followed some vendor
             | example is also pretty common and completely
             | understandable. Better tools, like using a language with
             | richer semantics, are indeed something that can help with
             | that.
        
       | fch42 wrote:
       | Caution: I'm a C programmer "by history" and therefore I see "C
       | Interfaces" as that and don't always / immediately view them
       | through the rust lens of ownership...
       | 
       | Reading the article (nice troubleshooting story!), my summary, as
       | a C programmer, is that the "C Interface" here "takes ownership".
       | Given C cannot express this properly, a pointer is passed - and
       | the called function "simply" makes the assumption that from
       | hence-on, what was given to it will remain.
       | 
       | As "semantics" this (the need to pass an "owned" piece of data to
       | a function) isn't unusual irrespective of the programming
       | language. Just in case of Rust, this is explicit in the interface
       | (if the func takes a non-ref arg, or a shared smart ref of
       | sorts), while in C ... this can lead to errors of the observed
       | kind. I haven't looked whether any of the sources or docs of
       | libmodem say "this pointer must be either global/static or
       | malloc'ed (and the caller shall not free it)".
       | 
       | A rust wrapper for this could / should possibly "leak a
       | reference" here; Something that prevents the initialisation
       | object from being dropped. yea, accepted, needs "nasty hacks"
       | whether static lifetime, Pin, manual drops, explicit Arc leaks,
       | ... possible though.
       | 
       | It'd be nice if libmodem were stricter about such ownership,
       | agreed, and then a rust wrapper could take advantage. Takes time
       | to evolve; is there a bug report / enhancement request out there
       | for this in libmodem ?
        
         | steveklabnik wrote:
         | > Takes time to evolve; is there a bug report / enhancement
         | request out there for this in libmodem ?
         | 
         | The end of the post says
         | 
         | > This would have been so simple to put in the docs. I've
         | opened a ticket on their DevZone forum. As of writing they've
         | still not updated the docs of the init function.
         | 
         | And they've replied
         | 
         | > Thank you for reporting this, it will be fixed in the next
         | `libmodem` release by the end of the month.
        
         | kevin_thibedeau wrote:
         | C would benefit from a new keyword that allows a function to
         | require a pointer argument be a non-stack object when passed
         | from a function that returns. That would cover the most common
         | cases where you accidentally pass in a temporary object that
         | must persist.
        
       | diffuse_l wrote:
       | I'm not sure about the exact hardware setup used here, but is it
       | possible that something like valgrind would have helped here?
       | 
       | I guess not, as the seems to run on the microcontroller, but I
       | remember getting at least some warning from valgrind in similar
       | situations
        
       | wrs wrote:
       | Two general debugging lessons to note here: (1) Data watchpoints
       | are a lifesaver that should be in your toolbox. (2) When
       | something funky is happening to a data structure in a C program,
       | look at its address to see if it's in the stack or the heap, in
       | case the answer is a surprise.
        
       ___________________________________________________________________
       (page generated 2025-01-26 23:01 UTC)