Re: Remote query for structure layout

David Blaikie via Gdb <[email protected]>
Newsgroups gmane.comp.gdb.devel
Message-ID <CAENS6Eteo7ZwOatL8yQ=Zp9fpMmqwNi4d8T9ixoz-zKW=kjMJA@mail.gmail.com>
On Wed, Mar 31, 2021 at 6:00 AM Simon Marchi <[email protected]> wrote:
>
> On 2021-03-30 11:27 p.m., David Blaikie wrote:
> > (let me know if I'm just being an unhelpful bystander here - I clearly
> > don't have a lot of context)
>
> I think it is interesting.  If people are not interested they can just
> skip reading this thread :).
>
> > On Tue, Mar 30, 2021 at 5:16 PM Simon Marchi <[email protected]> wrote:
> >>
> >> On 2021-03-30 6:44 p.m., David Blaikie wrote:
> >>> If it's "just" some user-code, is there a variable of the desired type being declared around the function call?
> >>
> >> AFAIK, this type wouldn't be used by the FreeRTOS code at all, so no.
> >> Again, here's my understanding, hopefully it's close enough to the
> >> reality.
> >>
> >> When a trap occurs, FreeRTOS saves the current task's register values on
> >> the task's stack, using some arch-specific assembly code.  For example,
> >> for RISC-V:
> >>
> >>     https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/534eba66ce4a5bda45d5edeeb81ac5a3cf6d0df8/portable/GCC/RISC-V/portASM.S#L121
> >
> > Ah, OK. So this is part of signal handling - so it's not part of the
> > code that's compiled into the user's program, for instance... not even
> > in some system library linked in, necessarily, I guess?
>
> It is part of "user code".  Wih "big" OSes (e.g. Linux), the OS is
> compiled on its own and the user programs are compiled separately.  With
> embedded RTOSes (at least those I worked with), it's just one big
> program.  Your user code includes <FreeRTOS.h> and calls FreeRTOS
> functions to spawn to tasks.
>
> >> The way these registers are pushed is an implementation detail of
> >> FreeRTOS.  And we can imagine that it can vary depending on the
> >> compile-time FreeRTOS configuration,
> >
> > I guess in the worst case it could be totally dynamic - it could pick
> > a different layout each time.
>
> Yes, if it wanted to be really annoying :).
>
> > (guess a side question: How's this different from other systems? I
> > don't know how other/more common systems handle registers during
> > signals)
>
> I think Linux does the same, pushing the registers (and some information
> about the signal) on the thread's stack before calling a signal handler.
> The layout is defined by the ucontext structure, exposed in the Linux
> ABI:
>
>     https://github.com/torvalds/linux/blob/master/arch/riscv/include/uapi/asm/ucontext.h
>
> GDB hardcodes that layout here:
>
>     https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/riscv-linux-tdep.c;h=ca97a60128ffe681766e7e1600d3f24048bf1f68;hb=HEAD#l103
>
> Being exposed by the Linux kernel and part of this ABI, this structure
> is not likely to change.  That makes it reasonable to hardcode it.

Ah, fair enough - good to understand and compare/contrast.

> >> which OpenOCD doesn't know about.
> >>
> >> To get register values of scheduled out tasks, OpenOCD needs to
> >> interpret these register values from the tasks' stacks.
> >>
> >> So Tim's suggestion is: have FreeRTOS declare a structure that has the
> >> exact layout as the saved registers on the stack:
> >>
> >> struct freertos_saved_regs {
> >>   int x1;
> >>   int x5;
> >>   int x6;
> >>   ...
> >> };
> >>
> >> Consumers could read that structure's layout from the DWARF info, and
> >> read the register values based on that.  That would be a lot more robust
> >> than hard-coding in the consumers how FreeRTOS stores things.
> >
> > If practical experience has shown the hard-coding is not
> > stable/reliable (than FreeRTOS does change its strategy from time to
> > time - but it's always constant for any given build of the FreeRTOS) I
> > guess.
>
> My guess is that FreeRTOS does not change it that often, there's not
> reason to.  But it can vary from build to build due to compile-time
> configuration of FreeRTOS (i.e. include or exclude support for using
> some particular register).  An external observer such as OpenOCD does
> not have access to the compile-time configuration that was used to build
> FreeRTOS, so it's difficult to know what the layout used by this
> particular build is.

Fair enough.

> > But I'm not sure where FreeRTOS would expose this structure to user
> > code - it's not like there's a system library header that user code
> > must include...
>
> There is, any code using FreeRTOS will include FreeRTOS.h.

Oh, that helps a lot, then.

> You can see
> FreeRTOS more like a library than an OS.  FreeRTOS is not involved at
> all until the user program calls functions like xTaskCreate to create
> some tasks and calls vTaskStartScheduler to hand over control to
> FreeRTOS.

OK

> So if the structure was declared somewhere in the port's include file,
> it would necessarily be seen by some CUs (those that invoke FreeRTOS
> functions).

Great!

One possibly "cheap" way would be for FreeRTOS to use the structure in
C code to do the register stashing - rather than or in addition to/in
some kind of hybrid manner along with the assembly. If that's not
possible/would make the code harder to understand, then finding some
other way to pin the data structure into the DWARF would be needed.

I'd be inclined to avoid adding a new attribute or other feature
(though I wouldn't be entirely opposed to it - I can certainly think
of other places where it may be useful to have a "whenever the
compiler sees this type, ensure it makes it into the resulting DWARF
no matter what" attribute) if reasonably possible - instead finding
some lowest-common-denominator/highly reliable signal that compilers
use to emit type information.

The simplest such signal is a global variable of the type (not a
pointer to it, but of the type itself) - though probably has to be
annotated in some way that ensures the compiler won't optimize the
variable away even under LTO, etc. I'm not sure sure off-hand if I
have a great way to do that portably (non-portably I guess there's
various "exported" attributes that ensure the entity is visible even
beyond the shared library/executable scope, and thus can't be
optimized away - maybe such an attribute would be sufficiently
portable for FreeRTOS's needs (ie: no less portable than the rest of
the code already)).
If such a variable would be problematic (taking up space in the image,
etc) - maybe there's a way to ensure it through an extra nonce
parameter (if it's C++ code, a parameter with a default argument - so
the caller never has to think about it) to some core FreeRTOS function
that won't be optimized away/removed as unused/etc. Again, has a
runtime impact, though - maybe if it was zero-size (like a class
template specialization with no members) & with the type as a template
parameter /maybe/ that would suffice...

Happy to play around with mechanisms like that if it's of interest.

> >> However, since that struct would never actually be used by FreeRTOS'
> >> code, the compiler won't emit it.  Hence the need to find a way to force
> >> the compiler to include it in the DWARF.
> >>
> >> Does that clarify the situation?
> >
> > Somewhat - any lack of understanding is just my ignorance in this
> > field/area in general, to be clear. (I'm also not a core gdb
> > developer, so I'm not the sort of person you have to convince of
> > anything - just a curious bystander trying to understand/maybe offer
> > some insightful suggestions (I predominantly work on LLVM's debug info
> > emission, so that's my background/connection))
>
> Well, since it's related to debug info emission, it could land on your
> plate at some point, it's good that you have some context.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.