This is a follow up to the previous RFC on this theme, [RFC] Showing register fields in LLDB.
Since that RFC and the release of LLDB 17, LLDB can now make use of register
field information provided by a debug server.
Until now, lldb-server
didn’t generate any register field information. This is
the next thing I want to implement.
I have done so on a WIP branch and will discuss that and other options here. It’s a lot of words
for what might ultimately be obvious, but since I was thinking it through anyway
I figured I’d best give the context instead of appearing with patches out of the
blue.
Reminder: What’s a register field?
Take a register like AArch64’s Current Program Status Register (CPSR). It’s
format is largely defined by the “Arm® Architecture Reference Manual”,
“SPSR_EL1, Saved Program Status Register (EL1)” (Linux userspace has a slightly
different view of it).
It’s a 64 bit register, usually shown as 32 bit by tools because the top 32 bits
are reserved.
The rest of the bits have their own meanings, so bit 31 is the negative flag “N”,
set by arithmetic operations. That’s one example of a “register field”.
It would be great if users didn’t have to read cpsr, read the manual (which is no
easy task), figure out the name the manual uses, see that N is bit 31,
python3 -c "print(0xcafef00d >> 31)"
and finally get 1.
Register field information solves that problem by showing you what N is, directly
in the debugger.
Current register field support in LLDB
This information can be consumed by lldb
, assuming the debug server sends it.
$ gdbserver :2345 /tmp/test.o
(lldb) register read cpsr
cpsr = 0x00001000
= (N = 0, Z = 0, C = 0, V = 0, TCO = 0, DIT = <...>)
If the other end is an lldb-server
, it’s just a plain number because lldb-server
doesn’t send anything. Yet.
The next step
I want to start teaching lldb-server
what these registers
contain and send that information to lldb
.
For an idea of scale, I am aiming initially to only do this for a handful of
control registers important to the AArch64 Linux use case. These are:
CPSR (Current Program Status Register)
FPSR (Floating Point Status Register)
FPCR (Floating Point Control Register)
mte_ctrl (Linux pseudo register for controlling the tagged address ABI)
SVCR (Streaming Vector Control Register)
The specifics aren’t important but there being around 5 is useful to appreciate
how big the changes I describe later might be.
One can add this information to system level simulations too of course, I tried
for example hacking Arm’s system register XML
into a QEMU build for a seemingly endless stream of register information.
I don’t intend to do that in lldb-server
. That’s something vendors can do if
they wish to.
The How
We have a few ways to do this, and I think we’ll end up doing more than one if
users and/or vendors like the feature.
1. Build the information into lldb-server as C++
This is the approach my WIP branch takes.
For SVCR I was able to simply add a static RegisterFields
instance and make use of that in the POSIX reginfo files.
This works because:
- SVCR only ever shows up on Linux, so even though those files are included
elsewhere, it doesn’t matter. - SVCR’s contents do not (at this time) change depending on system configuration.
It’s a static set of fields.
For the CPSR register I wanted to only show parts of the register actually relevant
to the current processor. This was done by checking Linux’s HWCAPs.
Meaning that for example, Branch Target Identification fields are only shown on
processors with that extension. We could drop this accuracy goal (GDB reports every architecturally possible field for example) if needed.
One problem with doing this for every register is that there won’t always be a
user visible/easily done check for the presence of whatever the field describes.
(and I’m assuming this can all be done once on lldb-server
startup, and no
register has fields that move about depending on the active process)
So we can make one of a few statements on that:
- Register fields will show all architecturally possible fields as per the
architecture manual at the time of implementation. - LLDB will detect the relevant fields from the above and only show those.
- LLDB will attempt to detect the relevant fields, but may default to always
showing a field if we cannot reliably detect it.
I lean toward the third, but I’m not sure it would actually bother any user if
we went with the first. As long as we have documentation to point to stating so.
Doing detection would prevent things like an Apple CPU seemingly having a memory
tagging related field. I also think it’s an opportunity to take what has been
done elsewhere and do it better in LLDB, albeit in a small way.
The other thing here is that I’ve done this detection in CreateHostNativeRegisterContextLinux
for Arm64. This is the first point where we know
platform and architecture. Even without field detection, I think this is the best
place to start adding information, as it’s possible OSes will have different
contents for the same named register.
(and limit any initial implementation fallout to Linux)
For CPSR specifically, it wouldn’t be hard to abstract the WIP code to use a feature
bit set as the target parser does. Then FreeBSD/OpenBSD/Darwin/Linux can fill
that bit set using whatever native mechanism they have.
(and perhaps debug-server
connected to a simulator may want to ignore the host
capabilities, I have no idea how those scenarios actually work)
2. Build the information into lldb-server as XML
In this approach we abandon detecting fields and simply show everything the
architecture manual includes.
Instead of writing all this out as structs we write it in the XML format in the first place
and lldb-server
just inserts it into the right place.
This is the approach I’ve seen in GDB and (I think?) QEMU for target.xml
overall.
Advantages:
- You’re writing XML directly.
- Perhaps we could at runtime override the content in the binary with external files.
Though at that point the user would have to give us the wholetarget.xml
file.
Disadvantages:
- Very limited flexibility if we do want to detect things and share common info.
- Some would argue XML is harder to read than C++, plus we get some checking
when it’s compiled that it’s valid syntax at least.
Overriding the information could be done from lldb
instead though which
brings me on to…
3. Store the information client side in lldb
This comes in 2 parts, we’d have some built in information that essentially mirrors
approach #1. With all the same properties except that any detection has to be done
further from the debugee itself (network wise). This means that anything we can’t
detect over the GDB protocol will be a pain to figure out.
The second part is that a user could use a script like this
Cortex M example script we already have.
With API additions one could pass field information in the register infos also,
and if you know what your cpu is, there’s no need for detection.
I also considered some sort of hook to point to external XML bundles like Arm’s
where we would convert the information on startup and patch it into the builtin
information. However, this is something I’d only pursue if there was vendor
interest in it (and would need to confirm usage rights around that data).
Testing
Assuming we detect fields, we don’t have access to enough different cpus to run
on to check all the permutations of that. So that will be covered by unit tests
that take feature sets of some sort.
For a register with static contents like SVCR, we can add a few output checks
to existing tests that use it. If we don’t do any field detection, then all
register information will be tested this way.
Anything not covered by that will be a best effort. Given that if I make a mistake
reading the manual, I’ll write incorrect code and incorrect tests. Someone will
have to file an issue to get that fixed.
I think that’s fine though. It’s a bit like the AArch64 Target Parser in that
sense. 99.9% of that is written by Arm itself, based on Arm documentation.
My Preferred Approach
My biases first:
- I think detecting the fields is cool and interesting, so I’d want to keep that door open.
- My focus is AArch64 Linux
lldb
→lldb-server
debug only at this time
(I would likely expand this to the BSDs next).
Which means my preferred approach is #1, encoding this information in lldb-server
as C++, hooking in when we create the native register contexts.
- It’s not that much code.
- It’s closer to the hardware for detection purposes.
- It doesn’t prohibit us implementing the other approaches later.
- …I wrote most of it already
(and very much a side benefit, any alternative client to lldb-server
will get this
information too)
On the accuracy of the information, I think LLDB should make a statement that
it will attempt to detect relevant fields but this is not foolproof and that the
raw value is the source of truth as to what a field’s value is if there is doubt.
Questions For You
- Do you agree with my preferred approach, have I missed another possibility?
- What statement should LLDB make (or not) about the accuracy of this field
information? - Any weird scenarios you know of for your architecture that are worth thinking
about? Including register contents orlldb-server
setup e.g.debugserver
and
iOS simulators, that sort of thing.
If no one has any specific opinions on the matter, that’s fine. I’ll be back with polished
patches to do my preferred approaches.