Stream: project-error-handling

Topic: core::Error and core::Backtrace


view this post on Zulip Nick Cameron (Jul 16 2021 at 04:44):

I've been trying to get a handle on the work to move Error and Backtrace from std to core. I have many questions! But before I ask them, I thought it would be useful to write down my understanding as somebody new to the work and leave the links that I think are important, hopefully this will make it easier for others to get involved.

view this post on Zulip Nick Cameron (Jul 16 2021 at 05:00):

Big picture, we want to move the Error trait (https://doc.rust-lang.org/nightly/std/error/trait.Error.html) into core. The motivation is to allow integrating Error with the panic infrastructure, make error handling more uniform (e.g., for no-std crates), and facilitate moving other traits to core (I can't find the reference again, but iirc some io traits like Read or Write). This is blocked on two things: the use of Box in downcast() and the use of Backtrace in backtrace(). The former can be solved by moving downcast to an impl on Box<Error> in alloc or std, rather than being an inherent method (see https://github.com/rust-lang/rust/pull/72981#issuecomment-662143462). The latter is solved by moving Backtrace to core too. Moving Bactrace to core is blocking its stabilisation (https://github.com/rust-lang/rust/pull/72981).

Question: is moving Backtrace to core a desire in its own right? E.g., do no-std crates want to use Backtrace?

view this post on Zulip Nick Cameron (Jul 16 2021 at 05:03):

Some links:

view this post on Zulip Nick Cameron (Jul 16 2021 at 05:10):

AIUI, the reason it is non-trivial to move Backtrace into core is that a real, captured backtrace uses lots of std (or at least alloc) types, so we want the version in core to be somewhat abstract and only provide concrete versions for 'dummy' backtraces, such as those created when capturing a backtrace is disabled. The obvious way to do that is using a trait, but then to pass around a trait object we need to use a pointer type and we can't use Box from core. Furthermore, we want std to extend core::Backtrace with a real backtrace (currently a LazilyResolvedCapture), which means we want Error::backtrace (or some global function which it delegates to) to do something different depending on whether or not std is linked. (Is this correct?)

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:18):

That all seems quite accurate

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:18):

The source for the read and write traits wanting an error trait in core is another thread I believe in t-libs suggesting having a core::io::Write trait and an associated internals thread by the same author

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:19):

That's not a strong motivator at the moment though, since that work is fairly early in the process and we're not sure we can or should do that yet

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:19):

As for motivations for a backtrace in core, I don't have concrete examples of people wanting it

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:20):

But I know @Josh Triplett and some others have talked about wanting to customize backtrace functionality

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:20):

Or it being potentially useful

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:20):

So I suspect that there would be value to backtrace in core but I'm not positive it will be useful in its own right

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:22):

Also I'd point out that there at least one more alternative to the backtrace dependency blocking Error in core, namely removing the backtrace accessor method on the error trait in favor of the generic member access RFC

view this post on Zulip Nick Cameron (Jul 16 2021 at 05:24):

I'm least clear on the possible solutions (there is lots of discussion in lots of places, but I can't figure out what is current and what is obsolete). The RFC doesn't yet specify the solution. The PoC implementation uses a trait for the kind of backtrace, using a raw pointer trait object in Backtrace. There are 'dummy' concrete backtrace types in core, and StdBacktrace in std which has can contain a real backtrace. There are functions for checking if backtraces are enabled and creating one, and these are lang items defined in both core and std. I'm not sure how the compiler knows which one to call, perhaps this is the open question about linking discussed in the comments? std uses a Box to back the raw pointer trait object. There is a fair amount of unsafe code around to facilitate this and some todo!s, I didn't delve into it so I'm not sure if this just requires filling in the gaps or if there are open questions around safety.

I'm not clear if the PoC as implemented is what will be described in the RFC or whether there have been improvements discussed.

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:25):

There could very well be safety issues

view this post on Zulip Nick Cameron (Jul 16 2021 at 05:26):

in favor of the generic member access RFC

For completeness, this is https://github.com/rust-lang/rfcs/pull/2895. AIUI, that is blocked on pulling out some of the more generic mechanisms into a new RFC?

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:26):

The PoC is going to be the basis of the RFC, which will then possibly lead to a cleaned up version assuming the lang item route seems best

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:26):

Correct

view this post on Zulip Jane Lusby (Jul 16 2021 at 05:26):

I need to follow up with @Plecra to see if they're still planning on working on that

view this post on Zulip Nick Cameron (Jul 16 2021 at 10:03):

I should probably read the rfc again, but isn’t the backtrace case different to the generic context case because we want to implement backtrace() once for all errors, not implement it each time for individual error types?

view this post on Zulip Nick Cameron (Jul 16 2021 at 10:07):

Spitballing an idea, could we have an extension trait ErrorWithBacktrace with the backtrace method on it in std and a blanket impl for all implementers of Error? You’d have to import the trait to get a backtrace so marginally less ergonomic, but it would statically prevent you trying to get a backtrace if you didn’t use std

view this post on Zulip Nick Cameron (Jul 16 2021 at 10:07):

(I’m sure there must be a problem with that)

view this post on Zulip Lokathor (Jul 16 2021 at 14:01):

To answer one question you had Nick: Having the ability to walk a backtrace with an iterator and immediately print it out to whatever sink in a no_std situation without actually allocating would be good to have.

view this post on Zulip Jane Lusby (Jul 16 2021 at 14:06):

I'm not sure I understand what you mean @Nick Cameron. As I understand it you can't have a default impl for fn backtrace, you have to manually specify which member you're using as the source of the Backtrace and implement the method per Error type.

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:12):

Sorry, I think I was confusing the backtrace method on Error with the methods on Backtrace

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:14):

So, is there a reason that backtrace are more privileged than other context info? Ie why do they have their own method rather than being accessed via the context mechanism?

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:15):

the argument that was made way back before I started the error handling project group was that Backtrace is the context most people will want to use

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:16):

Hmmm

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:17):

What about having an ErrorWithBacktrace trait in std which extends the core::Error trait with the backtrace method?

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:17):

I'd be worried about people wanting to use that instead of the Error trait and ending up in a similar situation with the Fail trait

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:18):

where you have lots of incompatibilities because you need to compose errors one way but they're implemented the other way

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:18):

It wouldn’t be quite that bad because you could always upcast to Error

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:24):

Another idea which you’ve probably already thought about, the backtrace returns a reference to a Backtrace, so either Backtrace could be a trait, rather than a struct wrapping a trait, or the Backtrace struct could be unsized, either way it means you can’t pass a Backtrace by value, but you don’t need the raw pointer and explicit destructor

view this post on Zulip Jacob Lifshay (Jul 16 2021 at 18:28):

so std's public API would just be Box<dyn Backtrace> or something?

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:29):

So for what?

view this post on Zulip Jacob Lifshay (Jul 16 2021 at 18:29):

sounds much better to me!

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:29):

Sorry, api for what?

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:29):

Nick Cameron said:

It wouldn’t be quite that bad because you could always upcast to Error

I think it might still fall over when you want to have the ErrorWithBacktrace type be a source error, rather than the outermost error. Now you need to downcast from a dyn Error to a dyn ErrorWithBacktrace or have an alternate source method on ErrorWithBacktrace that returns it as a dyn ErrorWithBacktrace, then you need to check both source methods at each step to iterate fully through a chain of errors

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:30):

it might be possible but from experience we know this direction is fraught

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:32):

if we had generic member access we could have an extension trait implemented for all E: Error types that just wraps a call to self.context::<Backtrace>() and re-exports that as an fn backtrace()

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:32):

And another question: why have the methods for capturing a backtrace in core? Could we not have the Backtrace type in core just as an abstraction with no way to capture one, then the capturing functions in a module in std?

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:32):

though you'd still have to expose that backtrace internally in the provide method from generic member access

view this post on Zulip Jacob Lifshay (Jul 16 2021 at 18:32):

so fn std::backtrace::capture() -> Box<dyn core::backtrace::Backtrace> (excuse my syntax)

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:33):

Nick Cameron said:

And another question: why have the methods for capturing a backtrace in core? Could we not have the Backtrace type in core just as an abstraction with no way to capture one, then the capturing functions in a module in std?

I kinda came at it trying to emulate the way we handle panics

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:33):

so I figured if someone writes some no_std code that calls Backtrace::capture but std is actually linked in they'd want to get a full backtrace

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:33):

kinda like how you can core panic and have it hit the std panic handler

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:34):

Ah, I see, thanks

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:34):

but I'm not sure we _need_ it to have a capture interface in core

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:34):

Could source return Self rather than Error or is it too late to change that?

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:34):

Jacob Lifshay said:

so std's public API would just be Box<dyn Backtrace> or something?

one of the proposals was to add a Backtrace trait to core and have the type still live in std

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:35):

though back when we first discussed that the option of using hooks was preferred

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:35):

i think because of the larger repr of a trait object

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:35):

and because it would require changing the backtrace fn's interface

view this post on Zulip Jacob Lifshay (Jul 16 2021 at 18:35):

we could always add the fn capture() to alloc later, that way it won't block moving Error to core

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:36):

Nick Cameron said:

Could source return Self rather than Error or is it too late to change that?

Self as in the concrete type of the error? wouldnt that break object safety or am I misunderstanding?

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:36):

Don’t we already pay the larger repr of the trait object because wit is embedded in the Backtrace struct?

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:36):

Oh yeah, pesky object safety :frown:

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:37):

Nick Cameron said:

Don’t we already pay the larger repr of the trait object because wit is embedded in the Backtrace struct?

In the PoC impl? uuuh, yea I think you're right :sweat_smile:

view this post on Zulip Jane Lusby (Jul 16 2021 at 18:38):

that might be a fucky wucky on my part

view this post on Zulip Nick Cameron (Jul 16 2021 at 18:39):

Given that there doesn’t seem much motivation to have Backtrace in core other than to support Error, I’d be keen to consider keeping capturing in std if it spares us a lot of complications

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:39):

To answer one question you had Nick: Having the ability to walk a backtrace with an iterator and immediately print it out to whatever sink in a no_std situation without actually allocating would be good to have.

@Lokathor aiui, it will not be possible to collect a backtrace in a no-std crate, so the only way this could happen is if a no-std crate is used by another crate which does use std, and that crate passes an error with a backtrace to the no-std crate, which seems like something which won't happen too often. Did you have another scenario in mind?

view this post on Zulip Lokathor (Jul 20 2021 at 01:41):

i don't want to collect it to a vec or anything, i want to print it directly right there

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:44):

In that case, it seems like you would have to collect the backtrace yourself (since core doesn't give you the OS stuff required to collect the backtrace), so is there any advantage to using a standardised Backtrace type rather than it being a completely custom type?

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:44):

(If it even makes sense for there to be a type at all if the data is not being stored)

view this post on Zulip Lokathor (Jul 20 2021 at 01:46):

In truth I have not been following the full discussion, but I was at one point lead to believe that it would be possible to get an iterator over the backtrace frames, even from within core code. I would want to have a panic handler that could do something like for frame in get_core_backtrace_iterator() { my_embedded_print_fn(frame) }

view this post on Zulip Lokathor (Jul 20 2021 at 01:47):

but if that is not possible, then nevermind

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:48):

Heh, I'm new to this, so I may be completely wrong :-) My impression is that that would not be possible, but I'm probably wrong

view this post on Zulip Mario Carneiro (Jul 20 2021 at 01:54):

is there anything you can do with backtrace frames other than print them? The API seems a little thin

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:59):

I think "print them" is non-trivial in this case, you might want to process them to be user friendly or in a specific log format or something. I can imagine some programs might want to make statistical info from them (90% of errors originated from X module or something) or maybe influence recovery (Y was in the backtrace, so there is no point in trying recovery mechanism Z). I'm just guessing what people might do, I have no data

view this post on Zulip Nick Cameron (Jul 20 2021 at 01:59):

What do you mean by "pretty thin"? Do you think it should be bigger? Or that it is not worth having at all? Or...?

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:05):

If we were to leave the backtrace collection functions in std, could we move them to core later? Since it seems that the std ones would 'override' the core ones anyway?

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:16):

So, at a high level the solution space to the Backtrace in std problem seems to be either a) move Backtrace to core, or b) use generic access to get the backtrace from an Error type. In (a), Backtrace would become a trait rather than an enum, and either backtrace returns a trait object, or there is a wrapper type around the trait (as in the current PoC impl). In case (b), we could make things more ergonomic by having an extension trait of Error in std to provide a backtrace method which delegates to the generic access. Obviously, approach (b) is blocked on the generic access work itself.

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:17):

In case (a) we could consider the backtrace collection functions too (in case b, I think they just stay in std). We could either leave them in std only, move them to core (as in the current PoC) or leave them in std for now with the option of moving to core/using hooks later.

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:17):

Have I missed any alternatives?

view this post on Zulip Mario Carneiro (Jul 20 2021 at 03:19):

Nick Cameron said:

What do you mean by "pretty thin"? Do you think it should be bigger? Or that it is not worth having at all? Or...?

I think you should be able to programmatically query information about the backtrace: what function it was, what module it was in, maybe the actual code address - any information that can be provided on most platforms. If the only public method is to print it then that means I have to parse the output of the formatter to figure out what's going on, which is exactly the kind of stringly typed API we want to avoid

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:19):

From the above solutions, I prefer case (a) since it is not blocked by the generic access work, though if we think the generic access work and moving Error to core are on similar timelines, then (b) is more attractive. If we go with (a), my preference would be to have a Backtrace trait without wrapping it in a concrete type, and to leave the collection functions in std with an open question for moving to core later (if we think that is possible).

view this post on Zulip Nick Cameron (Jul 20 2021 at 03:21):

@Mario Carneiro agree! My understanding (possibly incorrect, since I am just getting the feel of things) is that Backtrace and BacktraceFrame will have methods added over the long run to support this kind of thing, and that the 'print only' API is just a minimal starting place.

view this post on Zulip Lokathor (Jul 20 2021 at 03:21):

By "print" we all mean Display::fmt right?

view this post on Zulip Mario Carneiro (Jul 20 2021 at 03:21):

yes

view this post on Zulip Mario Carneiro (Jul 20 2021 at 03:21):

I think it has a debug formatter too

view this post on Zulip Jakub Duchniewicz (Jul 20 2021 at 08:33):

Very stimulating discussion going on here. I will update the RFC with different approaches we can take. As I understood it, we need to move Error to core or at least alloc?

view this post on Zulip Jakub Duchniewicz (Jul 20 2021 at 08:35):

Also, since no_std users might want the Backtrace to be available, the idea a) as @Nick Cameron stated, will allow them to write their own impls as they have to write panic fn's themselves

view this post on Zulip Jakub Duchniewicz (Jul 20 2021 at 08:38):

Not sure about the allocating part of the Backtrace when it is in core. It may be possible to have some preallocated (static) memory we will be filling with out backtrace log and do whatever we need with it. Probably this could be a part of Backtrace API.

view this post on Zulip Jakub Duchniewicz (Jul 20 2021 at 08:43):

What other methods would you envision in the API? In my opinion simply returning the frames to the user is enough, as people can do whatever they require afterwards.

view this post on Zulip Nick Cameron (Jul 20 2021 at 09:22):

we need to move Error to core

That is my understanding too

view this post on Zulip Nick Cameron (Jul 20 2021 at 09:24):

Not sure about the allocating part of the Backtrace when it is in core. It may be possible to have some preallocated (static) memory we will be filling with out backtrace log and do whatever we need with it. Probably this could be a part of Backtrace API.

I guess if we let people implement their own Backtrace types (implementing a Backtrace trait) then they would be free to do this and it shouldn't affect the API too much? As long as we e.g., return references to frames rather than Box or whatever

view this post on Zulip Nick Cameron (Jul 20 2021 at 09:26):

What other methods would you envision in the API? In my opinion simply returning the frames to the user is enough, as people can do whatever they require afterwards.

I think we can leave this for later? AIUI, we aim to stabilise a minimal Backtrace API for now (just a Display impl, etc) and we can add API for iterating frames later. Having said that, my assumption was that the interesting question is what a Frame object looks like and the Backtrace API would be quite simple, just providing a FrameIterator or something similar

view this post on Zulip Nick Cameron (Jul 22 2021 at 21:21):

@Jakub Duchniewicz did you get a chance to update the RFC? Is there anything I can help with?

view this post on Zulip Jakub Duchniewicz (Jul 23 2021 at 10:01):

@Nick Cameron will be doing it today and during the weekend, I will ask here for clarifications and update my progress

view this post on Zulip Jakub Duchniewicz (Jul 26 2021 at 10:38):

Sorry got bogged down with other work during the weekend. Got to it finally today :east: (summer is hard to push things through :))

view this post on Zulip Nick Cameron (Jul 27 2021 at 23:25):

@Jakub Duchniewicz how did the RFC work go? Did you have any questions or do you want a round of feedback?

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:28):

Hi @Nick Cameron I filled out the RFC slightly, I would love to get some comments to get me going

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:29):

summer time is not the easiest time to work on such things for me (a lot of moving around and other commitments :))

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:29):

hence it does not go as rapidly as I would like it to go

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:30):

I am not sure how long the RFC should be, as some of them are very lenghty and some are brief

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:30):

it seems like this is somewhere in between

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:30):

and I need to include a reference-level explanation in which I will explain how a user might make use of this after its implementation (provide their custom backtrace hooks etc)

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:31):

not sure what to include in prior art as I think that no-one moved traits in such a way before

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:41):

Also @Jane Lusby you could take a look and tell me if I am going in the good direction

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:41):

it is my first RFC so it will for sure require many iterations to get it right :grinning:

view this post on Zulip Jakub Duchniewicz (Jul 28 2021 at 16:42):

hopefully I included all points of view from this discussion along with some of my own

view this post on Zulip Jane Lusby (Jul 28 2021 at 17:52):

Jakub Duchniewicz said:

Also Jane Lusby you could take a look and tell me if I am going in the good direction

:+1:

view this post on Zulip Jane Lusby (Jul 28 2021 at 17:53):

ill take a look right after the libs meetings

view this post on Zulip Jacob Lifshay (Jul 28 2021 at 18:32):

I probably missed the discussion: why can't core's Backtrace implement Display and Debug, which are also in core?

view this post on Zulip Jane Lusby (Jul 28 2021 at 21:37):

Jakub is there an associated PR for your RFC?

view this post on Zulip Jane Lusby (Jul 28 2021 at 21:38):

I need somewhere to leave comments

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 06:43):

I will open a PR right now

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 06:43):

did not want to jump the gun with the WiP state of the RFC

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 06:50):

here is the PR

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 07:31):

a good question @Jacob Lifshay , maybe Jane has a reason in her implementation. I will poke around it to see if it is justified or just a decision

view this post on Zulip Jane Lusby (Jul 29 2021 at 07:46):

I don't recall not having a display and debug impl

view this post on Zulip Jane Lusby (Jul 29 2021 at 07:48):

I think I remember @Nick Cameron suggesting not including a capturing API in core to keep the implementation simpler, but also I gave reasoning based on panic for why I implemented it the way it is currently and so I wouldn't be surprised if we want pretty much feature parity with the current API

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 14:23):

yes, my bad. Just a quick glance at the code and there they are :)

view this post on Zulip Jakub Duchniewicz (Jul 29 2021 at 14:24):

what could we do with the backtrace frame if we did not capture it?

view this post on Zulip Jane Lusby (Jul 29 2021 at 16:33):

presumably we could still print it if it was passed in from some code that has access to std

view this post on Zulip Jane Lusby (Jul 29 2021 at 16:34):

so we could have a no_std library that has a fn like fn print_backtrace(backtrace: &core::backtrace::Backtrace) and then have another crate that depends on this and has access to std which then captures a backtrace and calls our fn

view this post on Zulip Lokathor (Jul 29 2021 at 17:26):

"core only" code might also have "printing" systems even without std, including something as basic as writing the panic message to a known static buffer before the device locks up, and the person debugging can go check the buffer

view this post on Zulip Jane Lusby (Jul 29 2021 at 17:40):

that's assuming we also include the capture API in core right? otherwise there would be nothing to print backtrace wise

view this post on Zulip Lokathor (Jul 29 2021 at 18:02):

yes, you do need to be able to iterate/inspect/whatever the stack frames in core to be able to do that.

view this post on Zulip Lokathor (Jul 29 2021 at 18:23):

For example, with the gba crate we provide core::fmt::Write impls for the debug output streams in the two most commonly used emulators. and then the panic handler can spit the error message out there

view this post on Zulip Lokathor (Jul 29 2021 at 18:24):

displaying a backtrace seems essentially the same, assuming it's available

view this post on Zulip Jane Lusby (Jul 29 2021 at 18:56):

For sure

view this post on Zulip Nick Cameron (Jul 29 2021 at 19:31):

Aiui, for at least some use cases, being no std doesn’t mean the program can’t capture or print, etc, but that the program will provide these facilities not std (not entirely sure if there is any point of using the Backtrace trait at all in this case, but interoperability maybe?)

view this post on Zulip Lokathor (Jul 29 2021 at 20:35):

The issue is one of knowledge.

And I think that for most Rust programmers those two facts hold true.

view this post on Zulip Nick Cameron (Jul 30 2021 at 00:54):

You wouldn’t necessarily implement it yourself, you might depend on a library to do it, just not std. my assumption is that if you’re doing no std work that’s either because you’re implementing a kernel and you do know how to examine the call stack, you’re on some exotic platform with no std support but there are libraries for it, or you’re doing embedded work and either there’s a library or it’s impractical to get a backtrace at all.

view this post on Zulip Nick Cameron (Jul 30 2021 at 00:56):

Are there no std use cases where it is reasonable to assume we can provide a backtrace some how?

view this post on Zulip Nick Cameron (Jul 30 2021 at 00:56):

Or are you thinking of the case where a no std crate is linked in to a program using std?

view this post on Zulip Lokathor (Jul 30 2021 at 01:54):

But Nick, I am the library author.

Nick Cameron said:

Are there no std use cases where it is reasonable to assume we can provide a backtrace some how?

I don't know enough about backtraces to even say Yes or No to this question.

I can say that the GBA is an ARMv4T arch CPU that LLVM already had a target profile for, basically since forever, the arch was designed in like 1993. The amount of effort to make this "exotic platform" work with Rust is essentially none at all, just the basic amount that all embedded devices have (an assembly code startup before you jump to main, and you need a linker script).

So though I am the maintainer of the GBA library, I have never once been called upon to examine my own stack frames, nor would I have any idea of how to do so.

view this post on Zulip Nick Cameron (Jul 30 2021 at 02:33):

Heh, fair enough. Do you know how a backtrace collector in core would work for such a platform? I thought that it was specific to the platform and therefore we couldn’t possibly implement it in core? (My understanding here is not good)

view this post on Zulip Lokathor (Jul 30 2021 at 02:38):

I kinda assumed that somehow... debug symbols... something something... magic? I'm so unaware of how making a backtrace works that I don't even know what would be different on an old arch CPU without an OS.

view this post on Zulip Nick Cameron (Jul 30 2021 at 03:01):

We can look into this. AFAIK, it depends on the calling convention which is a dependent on the combination of processor, compiler, and OS, which is why we can’t do it in core. But it’s possible there are cross platform standards which would work in at least some cases and which I don’t know about

view this post on Zulip Jacob Lifshay (Jul 30 2021 at 07:20):

In my experience with embedded platforms (microcontrollers), debug info is generally not available on the target cpu, since it is stripped when flashing the binary. If debug info is needed, backtraces can't be captured, unless you can cheat and ask a host system (semi-hosting) for either the debug info or to get a backtrace for you.

view this post on Zulip Jane Lusby (Jul 30 2021 at 15:28):

Lokathor said:

I kinda assumed that somehow... debug symbols... something something... magic? I'm so unaware of how making a backtrace works that I don't even know what would be different on an old arch CPU without an OS.

i think its fairly os specific but I'm definitely not an expert on backtrace implementations

view this post on Zulip Nick Cameron (Aug 01 2021 at 23:22):

I left a bunch of comments on the RFC PR: https://github.com/rust-lang/rfcs/pull/3156#pullrequestreview-719704831

view this post on Zulip Nick Cameron (Aug 01 2021 at 23:24):

I think we should go for the simplest solution which works and unblocks the Error -> core work, then expand on the alternatives/extensions in the alternatives section

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:56):

as for the backtrace on embedded devices, @Jacob Lifshay has a valid point - debug symbols are usually stripped to conserve memory on the target platform. Why and how one would want a standalone backtrace for examining is unclear to me (probably on a intermediary step in development when the target is running some kind of smoke tests or something similar and is running standalone and can write the backtrace to a buffer)

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:57):

Though I cannot fathom other cases where it would be useful (I might ask on the Embedded Rust matrix channel about this)

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:57):

they would have most experience with these matters

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:57):

I am working through your comments @Nick Cameron, leaving comments where something is unclear

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:58):

I think the simplest solution is @Jane Lusby's PR which the RFC is based upon mostly

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:58):

with the rest of ideas being extensions and alternatives

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 15:59):

not sure if it is the simplest(compared to waiting for stabilization of generic member access RFC)

view this post on Zulip Jane Lusby (Aug 03 2021 at 16:00):

i expect just doing generic access would be easiest but I'm not confident the API will pass muster

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 16:27):

not sure if I should write core or core or core :)

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 16:27):

usually it is the middle one

view this post on Zulip Jakub Duchniewicz (Aug 03 2021 at 16:28):

however it strikes me as indistinguishable from the rest of the text usually

view this post on Zulip Nick Cameron (Aug 03 2021 at 17:42):

I usually write plain core or use ‘the core library’ and only use backticks if it is actually code, e.g., in core::error::Backtrace

view this post on Zulip Nick Cameron (Aug 04 2021 at 02:41):

(deleted)

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 06:49):

sure, will stick to the regular naming conventions

view this post on Zulip Jacob Lifshay (Aug 04 2021 at 07:21):

one case where a backtrace on an embedded system would be useful is for automated bug reporting.

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 07:32):

but here we are talking about bigger embedded platforms which can afford to have networking and debug symbols for these kinds of reporting (probably even a full-blown linux with std support)

view this post on Zulip Jacob Lifshay (Aug 04 2021 at 08:22):

having networking doesn't mean there's enough space for debug symbols, a good example is Arduino, which has networking with only 2kB of ram: https://www.arduino.cc/en/Reference/Ethernet

view this post on Zulip Jacob Lifshay (Aug 04 2021 at 08:24):

(admittedly you'd probably want more ram to bother with automated bug reporting...maybe 8kB?)

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 08:40):

ah yes, just generally speaking that we need a more powerful platform for this

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 08:41):

asked on the embedded rust matrix chat

view this post on Zulip nagisa (Aug 04 2021 at 10:36):

backtraces can be symbolized after transmission, doesn't necessarily need to happen on device.

view this post on Zulip nagisa (Aug 04 2021 at 10:37):

debug symbols could also be stored on an external flash chip rather than on the SoC memory.

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 10:44):

one thing that gets me thinking is how heavy would be unwinding code

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 10:46):

not fluent in how they are unwound, so just speculating here

view this post on Zulip nagisa (Aug 04 2021 at 11:13):

With frame pointers enabled (which I believe is typically true on thumb) the unwinding is just chasing a pointer in a linked list. Along with storing the chain somewhere else that'd be a couple hundred bytes if written with care.

view this post on Zulip Jakub Duchniewicz (Aug 04 2021 at 11:26):

great, so it seems to be not that heavy. Curious what embedded folks think about it

view this post on Zulip Lokathor (Aug 05 2021 at 01:36):

You don't even need to necessarily store the chain. You can possibly print/transmit one frame at a time as you iterate the call stack.

view this post on Zulip Jakub Duchniewicz (Aug 05 2021 at 06:51):

not much feedback on embedded matrix, they told me to ask around their next meeting on Tuesday 20:00 CEST

view this post on Zulip nagisa (Aug 05 2021 at 11:15):

Lokathor said:

You don't even need to necessarily store the chain. You can possibly print/transmit one frame at a time as you iterate the call stack.

Yeah, I meant that the code to store the addresses somewhere would be included into the byte estimation ^^

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:05):

I talked yesterday with the embedded folks and they added a lot of valuable input to the discussion

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:06):

The gist of it is that they think that Error in core is a great idea whereas backtrace is not _that_ useful but they would welcome it if it did not induce code bloat

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:08):

So the idea is to provide a default implementation of the capture API which is just an iterator over the frames and each microcontroller crate like cortex-m would add their own implementation of this API

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:09):

I can paste some quotations later if you are interested (or maybe a chat log from yesterday)

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:11):

They also mentioned what was already discussed before, that in some cases it is worth capturing just the several PC addresses

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:12):

And I still need to address comments to the RFC which I will be doing over the weekend + address new concerns from the embedded community

view this post on Zulip Jane Lusby (Aug 18 2021 at 06:20):

That sounds amazing

view this post on Zulip Jane Lusby (Aug 18 2021 at 06:21):

And yeah any quotes or chat logs you want to share would be great

view this post on Zulip Jane Lusby (Aug 18 2021 at 06:21):

Or if it's all just going to go into the RFC happy to wait

view this post on Zulip Jakub Duchniewicz (Aug 18 2021 at 06:28):

I will send the logs and pinpoint relevant comments + add some of it to the RFC

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 09:43):

logs here (start about 18:22): https://libera.irclog.whitequark.org/rust-embedded/2021-08-17

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 12:16):

Added more details to the RFC and closed several comments

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 12:16):

Please check if I am heading in the proper direction

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 12:16):

Not sure if I should rethink the implementation itself @Nick Cameron

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 12:17):

as you mentioned with the RawBacktrace

view this post on Zulip Jakub Duchniewicz (Aug 23 2021 at 12:17):

Also probably some structural changes should be made (not sure if drawbacks should be addressed just to the proposed impl or to alternatives as well)

view this post on Zulip Nick Cameron (Aug 26 2021 at 01:58):

@Jakub Duchniewicz I reviewed the RFC. I think that it would be better to have a Backtrace trait than a struct + RawBacktrace trait - or at least describe why that isn't possible. I also think that the RFC should cover the alternative of defining Backtrace in core and backtrace capture in std only and describe why that is less desirable (IMO it is actually more desirable because the simplification outweighs the loss of benefits, but I haven't followed some of the most recent discussion, in any case I think it is an alternative that should be described and the trade-offs discussed).

view this post on Zulip Jakub Duchniewicz (Aug 27 2021 at 09:55):

I addressed the comments for the RFC. Not sure if we could do the Backtrace trait TBH. Why did @Jane Lusby decide to do such a split?

view this post on Zulip Jakub Duchniewicz (Aug 27 2021 at 09:57):

Also doing the second thing you mentioned @Nick Cameron seems to be doable but we would require changes in how we use the API (which is not a problem since it is unstable?)

view this post on Zulip Jane Lusby (Aug 27 2021 at 11:53):

The split was added for API stability reasons

view this post on Zulip Jane Lusby (Aug 27 2021 at 11:53):

The worry was that a public trait would restrict how we could evolve the backtrace API over time

view this post on Zulip Jakub Duchniewicz (Aug 27 2021 at 11:59):

I think it is a good concern, we hide all the impl details this way and just give 3 functions to the users

view this post on Zulip Nick Cameron (Aug 28 2021 at 01:22):

How would it restrict things? Usually libraries just make a trait available, it doesn't need wrapping.

view this post on Zulip Nick Cameron (Aug 28 2021 at 01:22):

"we hide all the impl details this way and just give 3 functions to the users" - hiding impl details and only exposing a few functions is exactly what traits are designed for :-)

view this post on Zulip Charles Ellis O'Riley Jr. (Aug 28 2021 at 01:44):

"we hide all the impl details this way and just give 3 functions to the users" - hiding impl details and only exposing a few functions is exactly what traits are designed for :-). Very nice :+1:

view this post on Zulip Jakub Duchniewicz (Aug 28 2021 at 10:37):

I think Jane meant that if we would be needing changes to this trait we would have to consider its existing users

view this post on Zulip Jakub Duchniewicz (Aug 28 2021 at 10:37):

but I might be wrong here

view this post on Zulip Jane Lusby (Aug 28 2021 at 14:43):

Thats correct

view this post on Zulip Jane Lusby (Aug 28 2021 at 14:44):

Depending on how exposed the trait is

view this post on Zulip Jane Lusby (Aug 28 2021 at 14:45):

If it's used as a return type in the error trait and it's publicly exported as part of our API and unsealed then it's a forward compatibility Hazzard and makes it much harder to add functions to backtrace interface

view this post on Zulip Jane Lusby (Aug 28 2021 at 14:45):

We could seal the trait and avoid that though

view this post on Zulip Nick Cameron (Aug 29 2021 at 02:02):

I think that is true of any trait in core/std. We're not proposing stabilising Backtrace, only moving it, so we can still change it for a while until it is stabilised. And even then, it only means we can't add required methods without a default.


Last updated: Jan 26 2022 at 13:46 UTC