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