ziggle314
ziggle314

Reputation: 11

Calling c function printf from ARM assembly language

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

Answers (3)

Adam Liss
Adam Liss

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

old_timer
old_timer

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

Turbo J
Turbo J

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

Related Questions