【问题标题】:Calling c function printf from ARM assembly language从 ARM 汇编语言调用 c 函数 printf
【发布时间】:2011-11-05 18:50:52
【问题描述】:

我需要从 ARM 汇编语言例程中调用 printf。我编写了一个执行相同操作的 c 程序 (printf("%d.%d",1,2))。我反汇编了编译器输出,但格式字符串如何传递并不明显。你们有没有一个代码示例可以做到这一点?

这是我用来尝试查看如何调用 printf 的测试 c 例程。

 #include <stdio.h>
 #include <stdlib.h>

 int main(void) {
         printf("%d.%d\n",1,2);
         return EXIT_SUCCESS;
 }

我的主程序反汇编如下:

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

我看到了 _IO_printf 例程的分支,但我看不到如何将格式字符串传递给它。

【问题讨论】:

  • 如果你附上了反汇编的相关部分,它会更容易解释(我们也可以确定我们说的是同一种语言方言)。
  • 请查看我更新的问题。
  • 格式字符串好像位于00060120。请注意,最后解码的指令实际上并不意味着被解释为指令。
  • 你给了我一个线索。看来我需要在 GNU arm 汇编器中指定一个像“%d.%d”这样的常量并将 r3 指向它?对吗?
  • 是的,但 r0 不是 r3。阅读 ARM 调用约定并查看 Adam Liss 的答案。

标签: assembly arm


【解决方案1】:
#include <stdio.h>
#include <stdlib.h>

int main(void) {
         printf("%d.%d\n",1,2);
         return EXIT_SUCCESS;
}

编译和反汇编:

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是第一个参数,格式字符串,r1是第二个参数a 1,r2是第三个参数a 2。格式字符串是一个字符串,一个指向字节数组的指针。 r0 加载了那个指针,一个字节串的地址。在这种情况下,地址是 0x8524。

如果你好奇你可以去看看 0x8524 看看你的字符串,

8524:   252e6425    strcs   r6, [lr, #-1061]!   ; 0xfffffbdb
8528:   00000a64    andeq   r0, r0, r4, ror #20

0x25、0x64、0x2e、0x25、0x64、0x0A、0x00

同样在你的反汇编中,你的字符串的地址是

81d0:       e59f0014        ldr     r0, [pc, #20]   ; 81ec <.text+0x11c>
...
81ec:       00060120        andeq   r0, r6, r0, lsr #2

如果您查看地址 0x60120 的反汇编,您将看到您的字符串。

【讨论】:

    【解决方案2】:

    在 C 中,字符串存储为字节序列。将字符串传递给函数时,实际上是在传递字符串中第一个字符的地址。

    当您调用printf()(没有编译器优化)时,参数以相反的顺序被压入堆栈,即从右到左。然后printf() 弹出第一个参数,它是(指向)格式字符串的指针。它解析格式字符串以确定每个连续参数要弹出多少字节,以及如何根据它们所代表的数据类型(int、字符串等)来解释它们。

    更新:正如其他人所指出的,ARM 处理器使用不同的调用约定。它不使用堆栈,而是在寄存器中传递第一个参数。但是这些参数的内容与它们在堆栈上传递时的内容相同。 R0 将包含一个指向格式字符串的指针,下面的等效代码仍然是准确的。

    感谢提供更正的人。

    因此,至少就printf() 而言,您的代码相当于:

    const char formatString[] = "%d.%d";
    printf(&formatString[0], 1, 2);
    

    【讨论】:

    • 请留在我身边!我想你知道我需要知道什么。请参阅我更新的问题。
    • ARM 调用约定不使用堆栈作为参数,除非您有超过 4 个。
    • 请看 dwelch 的回答,它非常清晰和完整。 +1 给你!
    • @dwelch - 不太正确。例如,long long 将占用 2 个寄存器,因此如果您有一个函数占用 2 个 long long 和一个 int,例如,参数 #3 将已经在堆栈上。您还可以按值传递更大的结构并用第一个参数用完所有寄存器。更可悲的是,大于 16 字节的参数将被拆分到寄存器和堆栈中。这直接来自 ARM 过程调用标准,文档 ARM IHI 0042D,(第 5.5 节,规则 C.5)。 (按值传递大型结构的智慧是另一回事......)
    • @dwelch & AdamLiss - 顺便说一句,我刚刚教了一个关于 CM3 的小组,而这个主题 - 我原以为大约需要 5 分钟,最终花了将近 30 分钟。所以这只是让人们感到困惑的事情,因此我希望答案是准确和完整的。你们知道你的东西......
    【解决方案3】:

    我看到了 _IO_printf 例程的分支,但我看不到如何将格式字符串传递给它。

    清洁您的眼镜。寄存器R0是字符串的地址,R1是“1”,R2是“2”。 Adam Liss 错了,在 ARM 中你使用 R0-R4 作为前 4 个函数参数。

    线

    81d0: e59f0014 ldr r0, [pc, #20] ; 81ec &lt;.text+0x11c&gt;

    将存储在函数“尾部”的地址加载到 R0 中。

    【讨论】:

      猜你喜欢
      • 2013-04-21
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 2021-04-23
      • 2021-12-31
      • 2019-02-13
      • 1970-01-01
      相关资源
      最近更新 更多