Reputation: 11
I need to call printf from an ARM assembly language routine. I have written a c program that does the same operation (printf("%d.%d",1,2)
). I disassembled the compiler output, but it is not obvious how the format string is passed. Do any of you have an example of code to do this?
Here is my test c routine that I have used to try to see how to call printf.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%d.%d\n",1,2);
return EXIT_SUCCESS;
}
My disassembly for the main routine looks like the following:
000081c4 <main>:
81c4: e1a0c00d mov ip, sp
81c8: e92dd800 stmdb sp!, {fp, ip, lr, pc}
81cc: e24cb004 sub fp, ip, #4 ; 0x4
81d0: e59f0014 ldr r0, [pc, #20] ; 81ec <.text+0x11c>
81d4: e3a01001 mov r1, #1 ; 0x1
81d8: e3a02002 mov r2, #2 ; 0x2
81dc: eb000212 bl 8a2c <_IO_printf>
81e0: e3a03000 mov r3, #0 ; 0x0
81e4: e1a00003 mov r0, r3
81e8: e89da800 ldmia sp, {fp, sp, pc}
81ec: 00060120 andeq r0, r6, r0, lsr #2
I see the branch to the _IO_printf routine, but I do not see how to pass it the format string.
Upvotes: 1
Views: 10590
Reputation: 48280
In C, a string is stored as a sequence of bytes. When you pass a string to a function, you're actually passing the address of the first character in the string.
When you call printf()
(without compiler optimizations), the arguments are pushed onto the stack in reverse order, that is, from right to left. Then printf()
pops the first argument, which is (a pointer to) the format string. It parses the format string to determine how many bytes to pop off for each successive argument, and how to interpret them based on the type of data (int, string, etc) they represent.
Update: as others have pointed out, an ARM processor uses a different calling convention. Rather than using the stack, it passes the first parameters in registers. But the contents of those parameters are the same as they would be if they'd been passed on the stack. R0 will contain a pointer to the format string, and the equivalent code, below, is still accurate.
Thanks to those who offered the correction.
So, at least as far as the printf()
is concerned, your code is equivalent to this:
const char formatString[] = "%d.%d";
printf(&formatString[0], 1, 2);
Upvotes: 1
Reputation: 71506
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%d.%d\n",1,2);
return EXIT_SUCCESS;
}
compile and disassemble:
0000842c <main>:
842c: e92d4008 push {r3, lr}
8430: e3a01001 mov r1, #1
8434: e3a02002 mov r2, #2
8438: e59f0008 ldr r0, [pc, #8] ; 8448 <main+0x1c>
843c: ebffffcc bl 8374 <_init+0x44>
8440: e3a00000 mov r0, #0
8444: e8bd8008 pop {r3, pc}
8448: 00008524 andeq r8, r0, r4, lsr #10
r0 is the first parameter, the format string, r1 is the second parameter a 1, r2 the third parameter a 2. The format string is a string, a pointer to an array of bytes. r0 is loaded with that pointer, an address to a string of bytes. In this case that address is 0x8524.
if you are curious you can go look at 0x8524 and see your string,
8524: 252e6425 strcs r6, [lr, #-1061]! ; 0xfffffbdb
8528: 00000a64 andeq r0, r0, r4, ror #20
0x25, 0x64, 0x2e, 0x25, 0x64, 0x0A, 0x00
Likewise in your disassembly the address to your string is
81d0: e59f0014 ldr r0, [pc, #20] ; 81ec <.text+0x11c>
...
81ec: 00060120 andeq r0, r6, r0, lsr #2
If you look at your disassembly for address 0x60120 you will see your string.
Upvotes: 3
Reputation: 7691
I see the branch to the _IO_printf routine, but I do not see how to pass it the format string.
Clean your glasses. Register R0 is the address of, the string, R1 ist the "1" and R2 is the "2". Adam Liss is wrong, in ARM you use R0-R4 as the first 4 function parameters.
The line
81d0: e59f0014 ldr r0, [pc, #20] ; 81ec <.text+0x11c>
loads this address stored at the "tail" of the function behind the return into R0.
Upvotes: 0