[RFC] Adding Register Field Information to lldb-server

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 whole target.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 lldblldb-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 :slight_smile:

(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 or lldb-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.

I think Register Fields are very useful, even more so for embedded devices. For example, on Hexagon if I want to know if the HVX (Hexagon Vector eXtensions) unit is enabled for the current hardware thread, I read the SSR and check the XE bit. It’s way easier to have the debugger do that for me than for me to look it up in the manual and calculate it, as your example showed.

To answer your questions:

  • I agree with your approach. This should be done in the stub, not the debugger, much like how the stub publishes the register list.
  • I think we should say we try to be as accurate as possible, but in certain cases the fields might not be. For example, Hexagon changes fairly regularly. Every once in a while a register might change - new fields added, fields deprecated, etc. If the stub doesn’t have the latest definitions, or the user overrides the core rev in the simulator, we might get values that don’t correspond to what the user expects.
  • I’m interested in AArch64, Hexagon and RISC-V, using hardware, QEMU and the Hexagon simulator. I don’t think there are any weird scenarios there.

Where’s a good place to see how this feature works? I would like to add support to the Hexagon simulator.

Does this currently work with QEMU?

This is a pretty interesting way to implement it – testing for the hardware features in lldb-server and not including fields that are known to be unavailable. I originally thought of the more simple-minded approach of describing all of the fields that are documented, and letting the user work out which are actually available versus currently-not-enabled.

lldb originally started with the idea that the remote stub would describe the registers and lldb knew nothing. This was to counter the problem gdb had before the xml target register description was used, where there was no negotiation about registers and gdb & the remote stub needed to be in agreement about everything - the register numbering, the sizes, and the offsets in the register context for g/G packets. It was a ripe area for bugs. lldb started with “lldb knows nothing, it learns everything from the remote stub” including things like ABI details like which dwarf register number a register is represented by (initially by qRegisterInfo, then later with the target.xml), or how some psueoregisters overlay with other registers (e.g. w0 & x0)

Eventually we got a little smarter and were able to move to a model where lldb knows all registers that might exist for a given target, and we rely on the remote stub to tell us the names of registers it knows, and lldb knows all of hte specifics of these register names on this architecture.

I originally thought we might encode the superset of all fields for registers like this in lldb, or in the stub and sent up to lldb, because they are cpu determined. But using the fact that the stub can dynamically know the refined set of fields that are possible for the given target is pretty cool.

Yes 100% accuracy is only for situations where you’re using an lldb from some vendor’s toolkit, or using an override they have provided. There’s always going to be a lag as architecture vendors update.

But I agree with you that even so, the value of seeing the common bits is still worth it. Certainly for Arm, the key arithmetic flags have been and will be around for decades, just seeing those will help beginners a lot.

(and if you see a weird field in lldb’s output, at least you now know what to search the manual for :slight_smile: )

I’m not sure where it would go, but certainly I want to document these expectations and what to do if the user suspects there is an inaccuracy.

Brilliant, one of my future goals here was to pitch this to people who had stubs other than lldb-server.

Target Description Format (Debugging with GDB) introduces the format.

https://github.com/llvm/llvm-project/blob/main/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterFlags.py has a bunch of snippets in tests, mainly the register bits.

(and FYI if Hexagon is big endian that should be fine too, I tested this with s390x and that worked)

One way to get a full XML dump is to connect to a gdbserver as shown in my first post. Before you gdb-remote do log enable gdb-remote packets. Then connect and look for read packet: $l<?xml version="1.0"?>.

What follows is the full target.xml. GDB describes at least CPSR on AArch64 and probably at least one register on x86.

Once your simulator is including this in the target XML, it should show up in lldb without modifications to lldb. If you find anything weird we can fix it up of course.

Last I checked at least qemu-user for AArch64 did not send any register information, but it does send a target XML file. I didn’t see any references to the register field XML attributes anywhere else in QEMU either.

Purely on a source code, technical level, there’s nothing stopping someone adding the basic information to QEMU as I plan to do to lldb-server.

I did do an experiment where I sort of “injected” Arm’s register XML into QEMU and it did gave me fields for all the system registers. For various reasons I can’t take this beyond a local hack (generated material in a GPL project etc.), but it shows it’s possible.

If I were a vendor who controlled the simulator and the specs, it’s the road I’d go down.

One reason I like detecting the register content is that we can connect to any random CPU and give the user some value even if we don’t know the CPU ID.

I did consider using the -march=native machinery to do the detection, but was worried that it needs CPU IDs to be added, and even if it doesn’t, it may only be looking at features that impact code generation.

If you know exactly what the debugger - target pairing is (maybe you select a particular simulator in an IDE) then having an override that says “I know exactly what this is, use this” is also useful. We can implement both routes if there’s demand.

It also avoids some of the IP issues involved in upstreaming information provided by architecture vendors, into projects like QEMU.

  • Machine readable spec is converted into target.xml
  • Pass the target.xml to lldb
  • lldb overrides the remote’s target.xml (and merges some bits I expect)

For what I’m doing right now, it’d just be a fun demo so I don’t think I’ll pursue this upstream. It’s always a possibility though.

We’ve got 4 stubs - hexagon-sim, internal QEMU, external QEMU (built from upstream sources), and our OS stub. hexagon-sim, internal QEMU and the OS stub use qRegisterInfo. External QEMU uses a target XML file, because upstream wasn’t interested in the qRegisterInfo changes.

I’d like to see packet logging showing qRegisterInfo adding register field info. I can get our 3 teams who own the 3 stubs to update qRegisterInfo with interesting fields.

I don’t think David should have to define a format for qRegisterInfo to specify the fields, not sure if that’s what you meant here.

I consider the qRegisterInfo method to be obsolete tbh, lldb-server, debugserver, and most other stubs we talk to are using the target xml description. I have one internal tool at Apple which generates qRegisterInfo, and it works fine so I haven’t pushed for them to switch over to an xml description, but if that team wanted to add a feature we get with target xml, I would tell them to switch over.

If someone wants to add these fields to qRegisterInfo in lldb, of course they’re welcome to do that. If it were me, I’d rather spend the time switching the stubs using qRegisterInfo over to the more standard target xml format.

If someone does do that, we are able to write tests where we replay the responses of another debug stub. So it could be tested upstream, despite lldb-server using target.xml instead.

(I think we have a test for an AVR debug stub that uses this technique)

The first supporting patch is [lldb] Add a single bit constructor for RegisterFlags::Field by DavidSpickett · Pull Request #69315 · llvm/llvm-project · GitHub, which I will likely land anyway because it simplifies existing tests as well.

The next two will teach lldb-server to make the XML and configure the first register. I’ll put those up next week and folks can comment here or on those, might be easier once you see the polished patches.

lldb-gdb-remote.txt says this:
// PRIORITY TO IMPLEMENT
// High. Any target that can self describe its registers, should do so.
// This means if new registers are ever added to a remote target, they
// will get picked up automatically, and allows registers to change
// depending on the actual CPU type that is used.
//
// NB: As of summer 2015, lldb can get register information from the
// “qXfer:features:read:target.xml” FSF gdb standard register packet
// where the stub provides register definitions in an XML file.
// If qXfer:features:read:target.xml is supported, qRegisterInfo does
// not need to be implemented.

@jasonmolenda what features do we get with target xml that we don’t get with qRegisterInfo?

On x64 Linux, ToT uses a target xml file. 15.0.x, from a year ago, uses qRegisterInfo.

I couldn’t find the commit where this was changed. Do you happen to know?

Will qRegisterInfo support go away? Do you think we should transition our internal stubs to using an xml file instead of qRegisterInfo?

lldb-gdb-remote.txt says this:
// PRIORITY TO IMPLEMENT
// High. Any target that can self describe its registers, should do so.
// This means if new registers are ever added to a remote target, they
// will get picked up automatically, and allows registers to change
// depending on the actual CPU type that is used.
//
// NB: As of summer 2015, lldb can get register information from the
// “qXfer:features:read:target.xml” FSF gdb standard register packet
// where the stub provides register definitions in an XML file.
// If qXfer:features:read:target.xml is supported, qRegisterInfo does
// not need to be implemented.

To be fair, this says that either qRegisterInfo OR target.xml must be supported, although it’s not as clear as it could be.

@jasonmolenda what features do we get with target xml that we don’t get with qRegisterInfo?

We changed debugserver from qRegisterInfo (an lldb-only packet) to target.xml (everyone else, including every VM/jtag debugger) in 2015, and qRegisterInfo has been uncommon here inside Apple since then. The driver for us in 2015 was performance - sending a single packet describing the registers instead of packets for registers. It can be a lot of packet traffic and you pay it for the start of every debug session. Beyond performance, interop with all other stubs was important so I view target.xml as more important to support than qRegisterInfo.

On x64 Linux, ToT uses a target xml file. 15.0.x, from a year ago, uses qRegisterInfo.

I couldn’t find the commit where this was changed. Do you happen to know?

No sorry, I wasn’t watching. I’m surprised it was still using qRegisterInfo that recently.

PR to update the qRegisterInfo docs [lldb] Update qRegisterInfo docs to recommend target.xml by jasonmolenda · Pull Request #69853 · llvm/llvm-project · GitHub

First one is up https://github.com/llvm/llvm-project/pull/69951.

First, to document what was done…

This was the chosen approach.

This is what I implemented/documented. For the registers in question there were no fields we couldn’t detect or weren’t always present, so it’s basically the same as the third option.

Testing was implemented this way, and added to core files. Which is great because we get testing on all platforms with those.

The work is complete as of [llvm][lldb][Docs] Add release note for register field info in lldb-s… · llvm/llvm-project@c4e57b1 · GitHub and will be in LLDB 18.

AArch64 Linux now provides register information for the control registers when using lldb → lldb-server on Linux.

That completes the “AArch64 architecture support” aspect for me, but I do plan to continue working on register fields as a generic feature. For example I want to investigate what it would take to get these fields into DAP enabled debuggers like VS Code, and look into supporting FreeBSD.

Happy to help anyone who wants to enable this feature for their target/OS/debugger/simulator/etc.