pyrsp is a simple wrapper around the GDB Remote serial protocol. Currently ARM Cortex M3 devices can be tested with the Blackmagic JTAG debugger and i386 and AMD64 qemu targets are supported with qemu. But it should be very simple to support other devices that speak this protocol e.g. buspirates via OpenOCD.
Possible uses: on-device unit testing, fuzzing, reverse engineering.
Currently you can load/dump memory, set/clear breakpoints, load extra information (symbols, debug info, .text segment) from an elf file, dispatch callbacks on breakpoints, run the binary associated with the elf file and display source code associated with addresses.
pip install pyelftools pyserial construct six psutil
- python3 support
- support for debugging multithreading userspace programs
- more robust qemu debugging
- much improved tests
- RLE data handling in protocol
- support for new construct api
- support for STLink2 devices
- rudimentary support for dumping fault status and mpu registers
- fixes
- refactored rsp parts into elf.py and utils.py
- support for i386 and AMD64 qemu targets
- timeout in readpkt
- extract .text directly from elf, do not require .bin
- support slice operator for memory ops
- use reported packetSize
- threadinfo support
from rsp import RSP rsp = CortexM3('/dev/ttyACM0', 'test.elf') # expects test.elf to exist rsp.load() # load test.elf into workarea (.text segment) def br_cb(self): # dump regs self.dump_regs() # retrieve some data print self.dump(10, 0x20000000) # set up some data self.store("aaaaa", 0x20000000) # adjust some register self.set_reg('r0',0) # continue and leave breakpoint intact self.step_over_br() # attach breakpoints to callbacks rsp.set_br('rsp_finish', rsp.finish_cb) rsp.set_br('rsp_dump', rsp.dump_cb) rsp.set_br('my_fun', br_cb) # run binary in mainloop until rsp_finish is hit # or some unhandled signal occurs rsp.run()
should produce the output in the next part, however a similar result, barring the custom callback can also be produced by running:
$ rsp.py cortexm3 /dev/ttyACM0 test
see the code running below in example/
work area: 0x20019000 entry: 0x20019001 Available Targets: No. Att Driver 1 STM32F4xx r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr msp psp 2001f6b4 00000004 00000000 00000000 2001f6b4 2001f754 00000020 00008000 000000ff 2001fb9c 00000020 00000000 08001f31 2001f6b0 2001902b 20019036 21000003 2001f6b0 00000000 load test.bin verify test OK set break: @rsp_finish (0x20019036) OK set break: @rsp_dump (0x20019034) OK set new pc: @test (0x20019001) OK continuing r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr msp psp 2001f6a4 00000004 00000000 00000000 2001f6a4 2001f754 00000020 00008000 000000ff 2001fb9c 00000020 00000000 08001f31 2001f6a0 20019013 20019034 21000003 2001f6a0 00000000 breakpoint hit: rsp_dump test.c:5 rsp_dump((unsigned char*) &number, 4); 00000000 .... r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr msp psp 20019038 0000000b 00000000 00000000 2001f6a4 2001f754 00000020 00008000 000000ff 2001fb9c 00000020 00000000 08001f31 2001f6a0 2001901b 20019034 21000003 2001f6a0 00000000 breakpoint hit: rsp_dump test.c:6 rsp_dump((unsigned char*) "hello world",11); 68656c6c6f20776f 726c64 hello.world r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr msp psp 2001f6a4 00000004 00000000 00000000 2001f6a4 2001f754 00000020 00008000 000000ff 2001fb9c 00000020 00000000 08001f31 2001f6a0 20019027 20019034 21000003 2001f6a0 00000000 breakpoint hit: rsp_dump test.c:8 rsp_dump((unsigned char*) &number, 4); 55aa55aa U.U. r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr msp psp 2001f6a4 00000004 00000000 00000000 2001f6a4 2001f754 00000020 00008000 000000ff 2001fb9c 00000020 00000000 08001f31 2001f6a0 2001902b 20019036 21000003 2001f6a0 00000000 breakpoint hit: rsp_finish clear breakpoint: @rsp_dump (0x20019034) OK clear breakpoint: @rsp_finish (0x20019036) OK continuing and detaching
RSP(self, port, elffile=None, verbose=False)
reads the elf file if given by elffile, connects to the debugging device specified by port, and initializes itself.
send(self, data, retries=50)
sends data via the RSP protocol to the device
readpkt(self, timeout=0)
blocks until it reads an RSP packet and returns it’s data or timeout>0 expires
store(self, data, addr=None)
stores data at addr if given otherwise at beginning of .text segment aka self.workarea
dump(self, size, addr = None)
dumps data from addr if given otherwise at beginning of .text segment aka self.workarea
fetch(self,data)
sends data and returns reply
fetchOK(self,data,ok='OK')
sends data and expects success
set_reg(self, reg, val)
sets value of register reg to val on device
refresh_regs(self)
loads and caches values of the registers on the device
dump_regs(self)
refreshes and dumps registers via stdout
connect(self)
Implements device specific connection procedure, e.g. attaches to blackmagic jtag debugger in swd mode
run(self, start=None)
sets pc to start if given or to entry address from elf header, passes control to the device and handles breakpoints
handle_br(self)
dumps register on breakpoint/signal, continues if unknown, otherwise it calls the appropriate callback.
set_br(self, sym, cb, quiet=False)
sets a breakpoint at symbol sym, and install callback cb for it
del_br(self, addr, quiet=False)
deletes breakpoint at address addr
finish_cb(self)
final breakpoint, if hit it deletes all breakpoints, continues running the cpu, and detaches from the debugging device
get_src_line(self, addr)
returns the source-code line associated with address addr
dump_cb(self)
rsp_dump callback, hit if rsp_dump is called. Outputs to stdout the source line, and a hexdump of the memory pointed by $r0 with a size of $r1 bytes. Then it resumes running.
load(self, verify)
loads binary belonging to elf to beginning of .text segment (alias self.workarea), and if verify is set read it back and check if it matches with the uploaded binary.
call(self, start=None, finish='rsp_finish', dump='rsp_dump', verify=True)
- Loads the .text segment given by self.elf into the device at the workarea (.text seg) of the device.
- and starts execution at the function specified by start or elf e_entry.
- After the breakpoint of rsp_dump is hit, r1 bytes are dumped from the buffer pointed to by r0.
- After the breakpoint of rsp_finish is hit, it removes all break points, and detaches
get_thread_info()
returns a tuple consisting of:
- current thread id,
- extra thread info,
- list of all threads
rsp[0:100]
returns 1st 100 bytes from memory
rsp[100]="hello world"
Stores the string “hello world” at address 100 in memory
If you run your code on an ARMv7, you can call and link the code in rsp.s and rsp.h. It only costs you 4 bytes.
If you use C language for instrumentation GCC might optimize out very simple finish functions, to avoid this you can use the example below:
__attribute__ ((noinline)) void rsp_finish(void) { while(1); } __attribute__ ((noinline)) void rsp_dump(void) { __asm__("nop;"); }