| @@ -0,0 +1,118 @@ | |||
| --- | |||
| title: "CTF + ARMeb + debugging" | |||
| description: > | |||
| CTF + ARMeb + debugging | |||
| created: !!timestamp '2014-03-05' | |||
| time: 7:21 PM | |||
| tags: | |||
| - ctf | |||
| - debugging | |||
| --- | |||
| I've been working on making the AVILA board work again with FreeBSD. | |||
| Thanks to Jim from Netgate for sending me a board to do this work. | |||
| I still have a pending patch waiting to go through bde to fix an | |||
| unaligned off_t store which gets things farther, but with the patch I'm | |||
| getting a: `panic: vm_page_alloc: page 0xc0805db0 is wired` shortly after | |||
| the machine launches the daemons. | |||
| I did work to get cross gdb working for armeb (committed in r261787 and | |||
| r261788), but that didn't help as there is no kernel gdb support on | |||
| armeb. As I'm doing this debugging over the network, I can't dump a | |||
| core. | |||
| I didn't feel like hand decoding a struct vm_page, so I thought of other | |||
| methods, and one way is to use CTF to parse the data type and decode the | |||
| data. I know python and ctypes, so I decided to wrap libctf and see | |||
| what I could do. | |||
| Getting the initial python wrapper working was easy, but my initial test | |||
| data was the kernel on my amd64 box that I am developing on. Now I | |||
| needed to use real armeb CTF data. I point it to my kernel, and I get: | |||
| "`File uses more recent ELF version than libctf`". Ok, extract the CTF | |||
| data from the kernel (ctf data is stored in a section named `.SUNW_ctf`) | |||
| and work on that directly: | |||
| ``` | |||
| $ objcopy -O binary --set-section-flags optfiles=load,alloc -j .SUNW_ctf /tftpboot/kernel.avila.avila /dev/null | |||
| objcopy: /tftpboot/kernel.avila.avila: File format not recognized | |||
| ``` | |||
| Well, ok, that's not too surprising since it's an ARMEB binary, lets try: | |||
| ``` | |||
| $ /usr/obj/arm.armeb/usr/src.avila/tmp/usr/bin/objcopy -O binary --set-section-flags optfiles=load,alloc -j .SUNW_ctf /tftpboot/kernel.avila.avila /tmp/test.avila.ctf | |||
| $ ls -l /tmp/test.avila.ctf | |||
| -rwxr-xr-x 1 jmg wheel 0 Mar 5 17:59 /tmp/test.avila.ctf | |||
| ``` | |||
| Hmm, that didn't work too well, ok, lets just use dd to extract the data | |||
| using info from `objdump -x`. | |||
| Ok, now that I've done that, I get: | |||
| ``` | |||
| ValueError: '/tmp/avila.ctf': File is not in CTF or ELF format | |||
| ``` | |||
| Hmm, why is that? Well, it turns out that the endian of the CTF data | |||
| is wrong. The magic is `cf f1`, but the magic on amd64 is f1 cf, it's | |||
| endian swapped. That's annoying. After spending some time trying to | |||
| build an cross shared version of libctf, I find that it has the same | |||
| issue. | |||
| After a bit of looking around, I discover the CTF can only ever read | |||
| native endianness, but `ctfmerge` has a magic option that will write out | |||
| endian swapped data if necessary depending upon the ELF file it's | |||
| putting in. This means that the CTF data in an armeb object file will | |||
| be different depending upon the endian you compiled it on, so the object | |||
| file isn't cross compatible. But, this does mean that the data in the | |||
| object files will be readable by libctf, just not the data written into | |||
| the kernel. | |||
| So, I create a sacrificial amd64 binary: | |||
| ``` | |||
| $ echo 'int main() {}' | cc -o /tmp/avila2.ctf -x c - | |||
| ``` | |||
| And use `ctfmerge` to put the data in it: | |||
| ``` | |||
| $ ctfmerge -L fldkj -o /tmp/avila2.ctf /usr/obj/arm.armeb/usr/src.avila/sys/AVILA/*.o | |||
| ``` | |||
| and again use `dd` to extract the `.SUNW_ctf` section into a separate file. | |||
| With all this work, I finally have the CTF data in a format that libctf | |||
| can parse, so, I try to parse some data. Now the interesting thing is | |||
| that the CTF data does encode sizes of integers, but it uses the native | |||
| arch's pointer sizes for `CTF_K_POINTER` types, which means that pointers | |||
| appear to be 8 bytes in size instead of the correct 4 bytes. A little | |||
| more hacking on the ctf.py script to force all pointers to be 4 bytes, | |||
| and a little help to convert ddb output to a string and finally, I have | |||
| a dump of the struct vm_page that I was trying to get all along: | |||
| ``` | |||
| {'act_count': '\x00', | |||
| 'aflags': '\x00', | |||
| 'busy_lock': 1, | |||
| 'dirty': '\xff', | |||
| 'flags': 0, | |||
| 'hold_count': 0, | |||
| 'listq': {'tqe_next': 0xc0805e00, 'tqe_prev': 0xc06d18a0}, | |||
| 'md': {'pv_kva': 3235856384, | |||
| 'pv_list': {'tqh_first': 0x0, 'tqh_last': 0xc0805de0}, | |||
| 'pv_memattr': '\x00', | |||
| 'pvh_attrs': 0}, | |||
| 'object': 0xc06d1878, | |||
| 'oflags': '\x04', | |||
| 'order': '\t', | |||
| 'phys_addr': 17776640, | |||
| 'pindex': 3572, | |||
| 'plinks': {'memguard': {'p': 0, 'v': 3228376932}, | |||
| 'q': {'tqe_next': 0x0, 'tqe_prev': 0xc06d1f64}, | |||
| 's': {'pv': 0xc06d1f64, 'ss': {'sle_next': 0x0}}}, | |||
| 'pool': '\x00', | |||
| 'queue': '\xff', | |||
| 'segind': '\x01', | |||
| 'valid': '\xff', | |||
| 'wire_count': 1} | |||
| ``` | |||
| So, the above was produced w/ the final [ctf.py](https://www.funkthat.com/~jmg/ctf.py) script. | |||