On register profiles and packet sizes

July 12, 2017

Part 1: Register profiles

What is a register profile? Well, since radare2 supports muliple architectures, it needs a way to store register names, data, roles etc. in a way that works for all architectures. So the solution is to have a data buffer where reg data is stored, and reg items which store stuff like the name of the register, and its offset into the data buffer. Simple as that.

To describe these register profiles, r2 has its own register profile string syntax. Looks something like this -

=PC	rip
=SP	rsp
...
gpr	rax	.64	0	0
gpr	rbx	.64	8	0
...
fpu	st0	.80	164	0
fpu	st1	.80	174	0
...

Yeah “what?” The lines beginning with = are aliases, for register types that are common across architectures (For instance PC is the program counter, and for x86_64 that’s the rip register). Next we have definitions of the registers themselves. For instance, rax is a gpr (general purpose register), 64 bits, and at an offset of 0 in the data block (I still don’t know what the last 0 is for though..)

Now gdb has it’s own register-profile format, which is quite different from r2’s. And here lies the problem. Since register contents are sent over as a big chunk of data, we need to know at what offset in the data block a given register starts. This isn’t possible unless the r2 and gdb register profiles are synced.

Back in the day, pancake used to do this by hand. But the r2 GitHub repo had an old issue asking for a drp sub-command to parse a gdb register profile and dump an r2 one. And so I took to implementing that. And the results are fairly satisfactory -

[0x7ffff7dd9d00]> !head gdbreg
Name         Nr  Rel Offset    Size  Type            Groups
 rax           0    0      0       8 int64_t         general,all,save,restore
 rbx           1    1      8       8 int64_t         general,all,save,restore
 rcx           2    2     16       8 int64_t         general,all,save,restore
 rdx           3    3     24       8 int64_t         general,all,save,restore
 rsi           4    4     32       8 int64_t         general,all,save,restore
 rdi           5    5     40       8 int64_t         general,all,save,restore
 rbp           6    6     48       8 *1              general,all,save,restore
 rsp           7    7     56       8 *1              general,all,save,restore
 r8            8    8     64       8 int64_t         general,all,save,restore

[0x7ffff7dd9d00]> drp?
|Usage: arp # Register profile commands
| drp                           Show the current register profile
| drp [regprofile-file]         Set the current register profile
| drpi [gdb] [regprofile-file]  Parse gdb register profile and dump an r2 profile string
| drp.                          Show internal representation of the register profile
| drpj                          Show the current fake size
| drps                          Show the current register profile (JSON)
| drps [new fake size]          Set the fake size

[0x7ffff7dd9d00]> drp gdb gdbreg
gpr	rax	.64	0	0
gpr	rbx	.64	8	0
gpr	rcx	.64	16	0
gpr	rdx	.64	24	0
gpr	rsi	.64	32	0
gpr	rdi	.64	40	0
gpr	rbp	.64	48	0
gpr	rsp	.64	56	0
gpr	r8	.64	64	0

Part 2: Packet sizes

The next issue that was bugging r2land was to do with packet sizes. A lot of people were reporting that gdbservers were segfaulting, or that they were getting Garbage at end of packet and it was messing up their debugging session. After staring at a few pcaps of debugging sessions of r2 with simavr, I found that the problem was to do with r2 not honoring the maximum supported packet size reported by the gdbserver. Also, in the case where packet size is not reported, r2’s defaults were way too high.

So I set about cutting the default down to 64 bytes (which ought to be safe. Come on!). This obviously would be inefficient for gdbservers which the user knows to support bigger packet sizes. So the next step was a way to set the packet size during runtime. And so I added it to the IO system interface -

[0x7ffff7dd9d00]> =!?
Usage: =!cmd args
 ...
 =!pktsz           - get max packet size used
 =!pktsz bytes     - set max. packet size as 'bytes' bytes
 ...

And then to wrap it up, I added the option of setting the max packet size before execution, with an environment variable, R2_GDB_PKTSZ.

$ export R2_GDB_PKTSZ=64

$ r2 -d gdb://localhost:8000
...
[0x7ffff7dd9d00]> =!pktsz
64