I'm trying to teach myself assembly. I've got years and year of experience with C, Java and Python- but I cant make ANY headway here and I'm about to give up.
So, I downloaded uVision4, and assumed I could just write a basic assembly program:
MOV R1, #0x7F0E0C2D
MOV R3, #0x1048B3C5
ADCS R1, R3, ROR #0x18
END
So, establish two variables, do an operation, done. Check the Registers for output and debugger for condition flags, surely.
Apparently, this is impossible.
I create the text file, write my code, save as a .asm file, then try to build-
It hates that.
Okay, so I create a new project, add the .asm file,
And it refuses, demanding I apparently write an entire device driver to do a god damn hello world.
How can I run a simple couple lines of code to start learning?
Related: How to single step ARM assembly in GDB on QEMU? is another similar walkthrough, with a program that makes an exit system-call so you can let it run without crashing, not just single-step.
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 ARM 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, if you don't define _start
:
$ 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: @ having a symbol name makes debugging easier
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 GAS syntax doesn't use ARMASM's end directive.
clang -target arm -mcpu=cortex-15 -c arm-simple.S
can assemble this, but even with -static -nostdlib
it can't link it into a static executable. Clang tries to use the system gcc
as a linker, which is not an ARM linker. So you will need cross-tools installed, at least a linker.
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 (PSR) symbolically, though. Modern x86 gdb does that.
The disassembly of later instructions after the three from our source is because there are bytes in memory right after them, probably including those 0x7F0E0C2D
and 0x1048B3C5
. So they disassemble as something. (And if you kept single-stepping, execution would fall into them, since there's no exit system call at the bottom of our code. Which is fine, we only wanted to single-step them in GDB anyway.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With