[RFC] Showing register fields in LLDB

GDB can show register fields generated from XML, I want to add that to LLDB.

(lldb) register read cpsr
    cpsr = 0x60001000
| N | Z | C | V | TCO | DIT | UAO | PAN | SS | IL | SSBS | BTYPE | D | A | I | F | nRW | EL | SP |
| 0 | 1 | 1 | 0 |  0  |  0  |  0  |  0  | 0  | 0  |  1   |   0   | 0 | 0 | 0 | 0 |  0  | 0  | 0  |

(formatting very much TBC, suggestions welcome!)

The above output is what I have so far and it also works connected to a gdbserver.
(Commits · DavidSpickett/llvm-project · GitHub)

The purpose of this RFC is to get your feedback on how this feature could be added to lldb in a way that is user friendly.

What’s the benefit?

The biggest benefit is that users don’t have to leave lldb to work out what a register means.

For me this usually involves asking python to do some bit shifting that I worked out from the Arm architecture manual. It gets in the way of “what floating point mode am I in” or “what flags are set so I know what this next branch will do”.

That applies to experienced users and beginners. Seeing Arm flags really helps learning Arm assembly for example, or for people new to Arm who are porting software.

The information is readily available in the manuals and is already present, as mentioned, in GDB. If we end up using official documentation’s descriptions there are some IP issues there but that’s a future issue, showing field names and values is well trodden ground.

Now for the selfish reason. I work on Arm enablement and a bunch of features lately have boiled down to new mode bits in various registers. I’d really like to have register fields so I can call these features “supported” in lldb.

Should we follow GDB on this?

GDB’s target XML is documented here:

We already use/generate some XML in this format but just don’t include register info. Following GDB gives us the information from gdbserver and its implementations (e.g. qemu) for free.

Extending that format wouldn’t be too difficult if we needed to, with some coordination with the GDB community. For example descriptions and field enums would be great additions.

When should we show register fields?

My guess is only when specific registers are asked for:

  • register read foo → can show fields
  • register read foo bar → can show fields
  • register read --all → does not show fields

We could add compact formatting for that last command. Something like GDB does:

cpsr = 0x00001000 [SSBS, nRW, EL=0]

In general I don’t want to turn some command into “spam” by printing all this when it’s not appropriate.

Where does the XML info come from?

  • gdbserver (when you do lldb → gdbserver)
  • handcrafted XML for testing ProcessGDBRemote
  • XML in lldb-server that we make by reading the architecture manuals

In the future I would like to investigate using Arm’s register XML (Exploration Tools – Arm Developer) to generate this information. There are IP issues here but it would be the most complete set we could use (perhaps some setting to point to it).

What else can we do with this information?

I would like to add a “register info” command that shows:

  • the layout of the register
  • it’s size
  • its name and alternate names (and anything like oh this is known to be an argument register)
  • registers that it invalidates (e.g. w0 on AArch64 invalidates x0 because they overlap)
  • …and any other useful stuff we can derive.

This is a good discovery tool and would help anyone writing C code over in another editor as they can see where fields start and end (again no need to pull up the manual).

The main gap here is the lack of descriptions. For example for “cpsr”, which is actually what the architecture calls “spsr”, you would want to see “Holds the saved process state when an exception is taken to EL1.”.

This may not be possible due to IP issues given that the architecture manual is Arm’s property. There are perhaps ways to blend Arm’s register XML with ours either at build or runtime (obligatory not a lawyer, would consult one if I intended to try this).

The far future of this stuff is being able to set the fields directly. Either from the commands or from scripts. Easier said than done of course but once the information is accessible it’s a possibility.

What’s my initial goal?

To get lldb and lldb-server to display XML described “flags” and describe the most common AArch64 control registers. So far I have done this for 1 register, “cpsr”.

What are the other pitfalls?

How do we generate accurate XML? If for example, a bit was added in armv8.7-a how can we tell you are using a v8.7-a device? Or do we just show all bits defined in the latest Architecture. In other words, if you saw the name of that bit shown, would you assume your device had that feature?

GDB says no you shouldn’t make that assumption. It certainly makes life easier and avoids situations where userspace simply cannot tell if a feature is enabled without running code. I tend to agree for the same reasons.

Which registers should have this information? Arm’s register files include every single system register and clearly 9% of the time we don’t want that (though kernel debugging would be interesting for that). I propose sticking to the common control registers and pseudo registers an OS like Linux provides.

(and anyone would be able to add new registers if they disagree about what is “interesting” enough)

Should this information be generated on a per OS level or start from some core set and be augmented? For example Mac OS and Linux probably provide a floating point control register but only Linux is going to show the memory tagging control pseudo register.

I think the latter though I’m not sure what the code will look like yet.

Is this Arm only?

No, I just happen to be working on Arm enablement. The XML content is the target specific bit, the processing is all generic. After my changes you should be able to connect lldb to, for example, a PowerPC gdbserver and see any info it sends.

5 Likes

| DavidSpickett
August 18 |

  • | - |

GDB can show register fields generated from XML, I want to add that to LLDB

(lldb) register read cpsr
    cpsr = 0x60001000
> N | Z | C | V | TCO | DIT | UAO | PAN | SS | IL | SSBS | BTYPE | D | A | I | F | nRW | EL | SP |
> 0 | 1 | 1 | 0 |  0  |  0  |  0  |  0  | 0  | 0  |  1   |   0   | 0 | 0 | 0 | 0 |  0  | 0  | 0  |

(formatting very much TBC, suggestions welcome!)

Would it be much harder to read if we treated the cpsr as a “fake structure” and presented the fields as we would any other structure? The reason I ask is that all our register printing is done by getting the register value into a ValueObject, and then printing that. So the most natural implementation in lldb would be to make a synthetic child provider that we attach by hand to these register ValueObjects that knows how to chop up the fields (using your XML description presumably). We also have intended for most of llldb’s history to add a “set value” capability to the Synthetic Child providers, so using the child provider will lead naturally to a way of setting the values as well. Plus, the disclosure form is going to work better in a GUI register display…

Jim

I like this a lot, and I have missed this feature in LLDB. Not just flags, but also the ability to view vector registers ([xyz]mm) as vectors of (e.g.) floats of various sizes.

I also like Jim’s idea of reusing our existing mechanisms for printing values. We did something similar for signal information (struct siginfo). We already know how to pretty print bitmask enums, so it would be nice if we could just say that the cpsr register is of type enum CPSR { N = 1, Z = 2, C = 4, ...}; and have our existing enum-printing code handle the rest.

I guess one question would be whether we want to display the fact that a particular flag bit is not set more prominently – our current enum printing code would just omit printing the particular bit. However, I would say that any improvements here could also be useful for printing some kinds of enums as well…

I’m not even sure whether we need synthetic value objects for this to work. I mean, it’s not like we’re inventing data out-of-the-blue here. We’re essentially reinterpret_casting the bytes of the register to some type that can be printed more nicely, and I think that our regular valueobjects should be able to do that.

Yes, if you can do this with an invented type rather than synthetic children, that’s even better. You should even get setting the bits for free if you can do it that way.

I too would like to see this feature and please do this using the ValueObject system where we can ask a register if it has children as this will allow registers to be expanded when they are shown in the UI.

If the register is displayed on its own, then we can auto expand the children during display (“reg read cpsr”) and if we are reading all registers we might be able to have register ValueObjects return the short hand version of the children as the “Summary” of the value object and they would just show up inline.

Our value objects for registers with this data just need to say that the ValueObject has children if there is bit information attached to the RegisterInfo structure and we should be able to modify the ValueObjectRegister class to do the right thing.

This is something we’ve wanted to tackle for a while, thanks for digging in David.

As for “how do we know which ISA is in effect for the target process” - debugserver/lldb-server will be running on the same machine as the inferior process, and it decides what to include in the target.xml. lldb-server/debugserver can test the capabilities/ISA of the chip its executing on and return the appropriate xml descriptions, right

Thinking about it, I’m not sure it’s quite that easy. We’ve slowly moved towards “lldb knows the sizes and types of all the registers, and the remote stub only tells us a name and number to refer to it by”, so we can interop with a stub which doesn’t tell us that x29 is GENERIC_REGISTER_RA (or whatever) - lldb knows x29 is GENERIC_REGISTER_RA and it only needs to know “what number does the remote stub use for x29”. In the beginning the stub told lldb everything about registers, like the DWARF numbers for them and such, and it didn’t make a lot of sense to do that.

As long as we agree on register names (lol cpsr/xpsr/spsr), all we need from the stub is for it to tell us what registers it can provide.

But in this case, lldb knows about a control register, and maybe it even has a definition of the fields of that register. But which fields are valid for the inferior can only be known by the remote stub – so lldb’s hardcoded knowledge of the fields can’t be relied on. Ugh.

No, I’m just not familiar with all that. I’ll look into doing this though, I like the sound of free functionality!

There is a thing in target XML “unions” which I’ve seen GDB use to describe vector registers. It’s along those lines.

For AArch64 there are also specific names for viewing a register as a different format and I’d like lldb to support those too (unrelated to this effort). “register aliases” if you will. This name is just this register formatted this way.

That’s where I was thinking along the lines of if you print specific registers you get everything. Then if it’s a whole set you get the summary that doesn’t include the fields that are zero.

Depends on how wide you expect people’s terminals to be :slight_smile:

Are there examples of “invented types” in lldb already? I’ve heard these terms but not worked with that area of code yet.

Most of them could be done as:

  • ask ptrace for the register, if it fails you don’t have it at all
  • read/modify/write back the single bit you want to test
  • if it comes back as 0 it doesn’t exist (or whatever reset value the manual has)
  • if ptrace complains, assume it doesn’t exist
  • write the original value back

You could do that from lldb as well it would just take more packets back and forth. Though it would be less ptrace specific, albeit not totally generic because different OS call the registers different things.

When I worked on MIPS debug we had to do this for finding the number of breakpoints. We ended up adding an all in one read/modify/write/replace packet just for that. I don’t think we have to go that far but it is in the back of my mind to not add too much overhead to startup.

For anything where you need to run code (which I hope would be rare) either lldb-server does it locally or lldb has to jit it into the process somewhere.

My current WIP just adds it statically (WIP! · DavidSpickett/llvm-project@150be50 · GitHub) (well, with a shared_ptr but you get the idea, no logic there yet).

The XML is just a transport so we can make this information in lldb-server and/or lldb. For example ABIAArch64::AugmentRegisterInfo is one point that could modify what comes from the stub if needed (e.g. if you had local XML files you wanted to use instead).

I think there could be 2 goals here:

  1. Be compatible with GDB stubs that send this information.
  2. Have our own target information in the place that suits lldb best (with some setting like “prefer remote target information”)

Yes, that’s exactly what I had in mind.

Perhaps you could look at Platform::GetSiginfoType and the surrounding code. I suppose we could do something similar, except that the code would not live in the platform class. The ABI class is better, as that’s where we do our other register shenanigans, although even that might go into the Architecture class these days. And an open question is where to store these (clang) types (the RegisterInfo struct is probably too low-level for that).

One thing I’m considering is plumbing the local (e.g. core-file based) register info descriptions through the augmentation code in the ABI classes. That would mean that even the local plugins would not need to provide all the subregister information – they’d just need to know how to read a full register, and the rest could be done generically.

Just something to keep in mind if you find yourself modifying a lot of register info descriptions.

I didn’t consider core files. There’s no server to give you the target XML in that case.

Can you explain more what you’re suggesting? I don’t understand what the benefit is for the plugins.

I’m looking at this and the immediate problem is that describing the fields in the natural way with C bitfields means we can’t guarantee order (which has long annoyed me but that’s a whole other thing).

Which suggests a few options:

  • Don’t create bitfield structs, create structs that match the order of the fields but use char/uint16 whatever sizes. Then have something convert a register value into that type, and back into a register value later.
  • Add an optional “offset” to the bitfield type constructor. Which might be a pain given that you’d never need that for a C bitfield. But clang must have a concept of an offset internally, even if it is only known to clang so perhaps I can override that.

Do either of those sound viable to you or am I missing something?

I’ll probably stumble across the solution as I look at the code more but it you have any immediate “oh no that’s a terrible idea” reactions that’s helpful.

I’m afraid I don’t understand what you mean. What kind of order are you referring to? The order of bitfields in a struct should be given by the C ABI.

Maybe an example would help to clarify things?

I’m referring to both orders and what getting them mismatched could cause.

Let’s say we have a register laid out as:

Bit position | 31 - 16 | 15 - 0 |
Field name   | A       | B      |

If I were to describe that as a C struct with bitfields I could write:

struct Register {
  union {
    struct {
      uint16_t A: 16;
      uint16_t B: 16;
    };
    uint32_t value;
  };
};

If I then do:

Register r;
r.value = 0x11114444;
assert(r.A == 0x1111);

I will get a surprise depending on platform (and this is why bitfields are a problem when you try to map them onto hardware registers). Since this type we’re making is a clang type, lldb will have the same concern.

All the little endian compilers I’ve tried put A as the least significant bits of the value. Big endian is reversed. (Compiler Explorer)

I’m not sure if it’s as simple as that for all bitfields. Our rules are abi-aa/aapcs64.rst at main · ARM-software/abi-aa · GitHub but I’m yet to digest them. For fields that are <8 bits I need to confirm the behaviour and if we can reason about it well enough to attempt this.

Basically if we can make a type and try it, then we could choose the right order. If changing the layout just slightly shuffles all the fields, that’s not going to be practical.

So amongst our options:

  • Order the fields in the type we make to match what we want to happen ordering wise.
    So little endian we would have B/A and big endian A/B.
    Does not keep the printed field order the same between targets.
  • Keep the order the same and bit reverse the register value if we need to.
    For little endian we would reverse it, for big endian we would not (reversing again if needed, before a write).
    Keeps the field order the same between targets (top = most significant).

I would prefer option 2 for keeping the printed order the same. Though it does need some hooks to do the reversing.

(and by reverse I mean bit reverse as opposed to an endian swap at the byte level, bit 31 swaps with bit 0, bit 30 with bit 1, etc)

Or I could abandon bitfields altogether and create a type like:

struct Register {
  union {
    struct {
      uint16_t A;
      uint16_t B;
    };
    uint32_t value;
  };
};

And have lldb do extra work to first extract A from the register and then set it in the struct. When we want to write the result back, we’d repack it into the register format. Which is functionally a lot like the reversing idea.

Though you wouldn’t be able to print the type and see the proper size of each field.

As per usual I think I talked myself into the solution. We only care about the order clang chooses and if we need to reverse bits so be it. I’m going to work on that idea.