[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)