I do stuff like this all the time on my x86 desktop, using gdb to single-step code. Usually with x86 instructions, but it's doable for ARM cross-development, too. Build with gcc -nostdlib foo.S
, and it should set the default entry point to the beginning of your .text section. You do get a warning from the linker, though:
$ arm-linux-gnueabi-gcc -nostdlib arm-simple.S
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000010098
I had to modify your source for it to assemble. Here's my arm-simple.S:
.globl _start
_start: @ make debugging easier to have a symbol name
ldr R1, =#0x7F0E0C2D @ ARM immediate constants can't be arbitrary 32-bit values. Use the ldr reg, =value pseudo-op, which in this case assembles to a PC-relative load from a nearby literal pool. Often it can use mov reg, #imm or movn reg, #imm
ldr R3, =#0x1048B3C5
ADCS R1, R3, ROR #0x18
@END This isn't an instruction.
Then you can use gdb and set a breakpoint at the first instruction, run it, and single step.
You can even do this in a cross-development environment, with a few wrinkles.
In one terminal, run QEMU on your binary, waiting for a debugger connection:
$ arm-linux-gnueabi-gcc -g -nostdlib arm-simple.S
$ qemu-arm -g 12345 ./a.out # user-mode emulation, waiting for gdb to connect
Use -mcpu=something
for gcc, and -cpu model
for qemu if you want to be specific.
In another terminal, run ARM gdb (in my case, from Ubuntu's gdb-arm-none-eabi package, since they Ubuntu doesn't distribute a arm-linux-gnueabi-gdb cross-ARM-gdb package for x86).
TODO: try gdb-multiarch. Regular gdb on an x86 desktop can only debug x86 binaries, so you definitely can't use that.
$ arm-none-eabi-gdb ./a.out # give the gdb client the same binary to read symbols / debug info
(gdb) target remote localhost:12345
(gdb) layout asm
(gdb) layout reg
(gdb) si # single step by instruction, not source line
(gdb) si
Then gdb shows:
+--Register group: general-----------------------------------------------------------------------------------------------------------------------------------------+
|r0 0x0 0 r1 0x7f0e0c2d 2131627053 r2 0x0 0 |
|r3 0x1048b3c5 273200069 r4 0x0 0 r5 0x0 0 |
|r6 0x0 0 r7 0x0 0 r8 0x0 0 |
|r9 0x0 0 r10 0x100ac 65708 r11 0x0 0 |
|r12 0x0 0 sp 0xf6ffea40 0xf6ffea40 lr 0x0 0 |
|pc 0x100a0 0x100a0 <_start+8> cpsr 0x10 16 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
----------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0x10098 <_start> ldr r1, [pc, #4] ; 0x100a4 <_start+12> |
|0x1009c <_start+4> ldr r3, [pc, #4] ; 0x100a8 <_start+16> |
>|0x100a0 <_start+8> adcs r1, r1, r3, ror #24 |
|0x100a4 <_start+12> svcvc 0x000e0c2d |
|0x100a8 <_start+16> subne r11, r8, r5, asr #7 |
|0x100ac andeq r1, r0, r1, asr #18 |
|0x100b0 cmnvs r5, r0, lsl #2 |
|0x100b4 tsteq r0, r2, ror #18 |
|0x100b8 andeq r0, r0, pc |
|0x100bc subseq r3, r4, r5, lsl #10 |
|0x100c0 tsteq r8, r6, lsl #6 |
|0x100c4 andeq r0, r0, r9, lsl #2 |
|0x100c8 andeq r0, r0, r12, lsl r0 |
|0x100cc andeq r0, r0, r2 |
|0x100d0 andeq r0, r4, r0 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
remote Remote target In: _start Line: 6 PC: 0x100a0
(gdb) si
It highlights the last register(s) modified, which is pretty great.
It seems to be too old to decode flags symbolically, though. modern x86 gdb does that.