assembly 从程序集调用C函数,以ARM调用约定传递参数并获取返回值

06odsfpq  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(142)

我要调用一个C函数,说:

int foo(int a, int b) {return 2;}

在汇编(ARM)代码中。我读到我需要提到的

import foo

在我的汇编代码中,汇编程序在C文件中搜索foo。但是,我在从汇编程序中传递参数a和b并在汇编程序中再次检索一个整数(这里是2)时遇到了麻烦。有人能用一个小例子解释一下我如何做吗?

kwvwclae

kwvwclae1#

您已经编写了最小的示例。

int foo(int a, int b) {return 2;}

编译与反汇编

arm-none-eabi-gcc -O2 -c so.c -o so.o
arm-none-eabi-objdump -d so.o

so.o:     file format elf32-littlearm

Disassembly of section .text:

00000000 <foo>:
   0:   e3a00002    mov r0, #2
   4:   e12fff1e    bx  lr

任何与a和B有关的东西都是死代码,所以优化了。虽然使用C来学习asm是好的/可以开始,但你真的想用优化来做,这意味着你必须更加努力地制作实验代码。

int foo(int a, int b) {return 2;}
int bar ( void )
{
    return(foo(5,4));
}

我们学不到什么新东西。

Disassembly of section .text:

00000000 <foo>:
   0:   e3a00002    mov r0, #2
   4:   e12fff1e    bx  lr

00000008 <bar>:
   8:   e3a00002    mov r0, #2
   c:   e12fff1e    bx  lr

需要为呼叫执行以下操作:

int foo(int a, int b);
int bar ( void )
{
    return(foo(5,4));
}

现在我们看到

00000000 <bar>:
   0:   e92d4010    push    {r4, lr}
   4:   e3a01004    mov r1, #4
   8:   e3a00005    mov r0, #5
   c:   ebfffffe    bl  0 <foo>
  10:   e8bd4010    pop {r4, lr}
  14:   e12fff1e    bx  lr

(yes这是为该编译器的默认目标armv 4 t而构建的,对于其他一些人来说应该是显而易见的,我/我们不知道如何知道)(也可以从该示例中判断编译器的新旧程度(几年前有一个abi更改,在此处可见)(gcc的新版本比旧版本更差,因此旧版本仍然适用于某些用例))
根据此编译器约定(现在,虽然此编译器确实将某些文档的某些版本的arm约定用于此编译器的某些版本,但请始终记住,这是编译器作者的选择,他们没有义务遵守任何人的编写标准,而是自己选择)
所以我们看到第一个参数在r 0中,第二个在r1中。你可以用更多的操作数或更多类型的操作数来构造函数,看看有什么细微差别。寄存器中有多少个操作数,它们何时开始使用堆栈。例如,先用64位变量,然后用32位变量作为操作数,然后反过来。
以查看被调用方的情况。

int foo(int a, int b)
{
    return((a<<1)+b+0x123);
}

我们看到r 0和r1是前两个操作数,否则编译器将严重损坏。

00000000 <foo>:
   0:   e0810080    add r0, r1, r0, lsl #1
   4:   e2800e12    add r0, r0, #288    ; 0x120
   8:   e2800003    add r0, r0, #3
   c:   e12fff1e    bx  lr

我们在调用者示例中没有明确看到的是,r 0是返回值的存储位置(至少对于这个变量类型)。
ABI文档不是一本容易阅读的书,但是如果你首先“试一试”,然后如果你想参考文档,它应该会对文档有帮助。在一天结束的时候,你有了一个你要使用的编译器,它有一个约定,且可能是工具链一部分,因此您必须遵守编译器约定,而不是某些第三方文档(即使该第三方是ARM)而且您可能应该使用该工具链汇编程序,这意味着您应该使用该汇编语言(ARM有许多不兼容的汇编语言,该工具定义的是语言而不是目标)。
你可以看到自己解决这个问题是多么简单。
所以这会很麻烦,但是你可以看看编译器的汇编输出,至少有些编译器会允许你这样做。

int foo(int a, int b)
{
    return 2;
}

    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .align  2
    .global foo
    .arch armv4t
    .syntax unified
    .arm
    .fpu softvfp
    .type   foo, %function
foo:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    mov r0, #2
    bx  lr
    .size   foo, .-foo
    .ident  "GCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]"

这些几乎都不是你“需要”的。
最小值如下所示

.globl foo
foo:
    mov r0,#2
    bx lr

.global或.globl是等价的,在某种程度上反映了您学习gnu汇编程序的年龄或方式/时间。
如果混合使用arm和thumb指令,则会中断,默认为arm。
安全防护系统
x.o:文件格式elf 32-littlearm
拆卸章节文本:
00000000:0:e3 a00002移动r 0,#2 4:左后侧
如果我们想要拇指,我们必须告诉它

.thumb
.globl foo
foo:
    mov r0,#2
    bx lr

我们得到拇指。

00000000 <foo>:
   0:   2002        movs    r0, #2
   2:   4770        bx  lr

有了ARM和gnu工具链,至少你可以混合使用arm和thumb,链接器会处理好转换

int foo ( int, int );
int fun ( void )
{
    return(foo(1,2));
}

我们不需要一个引导程序或其他东西来让链接器链接,所以我们可以看到它的那部分是如何工作的。

arm-none-eabi-ld so.o x.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00008000 <fun>:
    8000:   e92d4010    push    {r4, lr}
    8004:   e3a01002    mov r1, #2
    8008:   e3a00001    mov r0, #1
    800c:   eb000001    bl  8018 <foo>
    8010:   e8bd4010    pop {r4, lr}
    8014:   e12fff1e    bx  lr

00008018 <foo>:
    8018:   2002        movs    r0, #2
    801a:   4770        bx  lr

现在它被破坏了,不仅仅是因为我们没有引导等等,而且有一个bl到foo,但是foo是thumb,调用者是arm。所以对于arm的gnu汇编程序,你可以走这个捷径,我想我是从一个更老的gcc那里学来的,但是不管怎样

.thumb

.thumb_func
.globl foo
foo:
    mov r0,#2
    bx lr

.thumb_func表示您找到的下一个标签被认为是函数标签,而不仅仅是地址。

00008000 <fun>:
    8000:   e92d4010    push    {r4, lr}
    8004:   e3a01002    mov r1, #2
    8008:   e3a00001    mov r0, #1
    800c:   eb000003    bl  8020 <__foo_from_arm>
    8010:   e8bd4010    pop {r4, lr}
    8014:   e12fff1e    bx  lr

00008018 <foo>:
    8018:   2002        movs    r0, #2
    801a:   4770        bx  lr
    801c:   0000        movs    r0, r0
    ...

00008020 <__foo_from_arm>:
    8020:   e59fc000    ldr ip, [pc]    ; 8028 <__foo_from_arm+0x8>
    8024:   e12fff1c    bx  ip
    8028:   00008019    .word   0x00008019
    802c:   00000000    .word   0x00000000

链接器添加了一个我称之为trampoline的东西,我想其他人也称之为vaneer。不管是哪种方式,只要我们写对了代码,工具链都会处理。
请记住,特别是这个汇编器的语法是非常特定于汇编器的,其他汇编器可能有其他的语法来使其工作。从gcc生成的代码中,我们看到了通用的解决方案,它是更多的类型化,但可能是一个更好的习惯。

.thumb

.type foo, %function
.global foo
foo:
    mov r0,#2
    bx lr

.type foo,%函数在arm的gnu汇编器中对arm和thumb都有效。并且它不需要放在标签之前(就像.globl或.global也不需要一样。我们从这个汇编语言的工具链中得到了相同的结果。
只是为了示范...

arm-none-eabi-as x.s -o x.o
arm-none-eabi-gcc -O2 -mthumb -c so.c -o so.o
arm-none-eabi-ld so.o x.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-objdump -d so.elf

so.elf:     file format elf32-littlearm

Disassembly of section .text:

00008000 <fun>:
    8000:   b510        push    {r4, lr}
    8002:   2102        movs    r1, #2
    8004:   2001        movs    r0, #1
    8006:   f000 f807   bl  8018 <__foo_from_thumb>
    800a:   bc10        pop {r4}
    800c:   bc02        pop {r1}
    800e:   4708        bx  r1

00008010 <foo>:
    8010:   e3a00002    mov r0, #2
    8014:   e12fff1e    bx  lr

00008018 <__foo_from_thumb>:
    8018:   4778        bx  pc
    801a:   e7fd        b.n 8018 <__foo_from_thumb>
    801c:   eafffffb    b   8010 <foo>


你可以看到它是双向工作的,拇指到手臂,手臂到拇指,如果我们写asm,写它为我们做剩下的工作。

现在我个人讨厌统一语法,这是arm和CMSIS沿着犯的主要错误之一。但是,你想以此为生,你会发现你非常讨厌大多数公司的决策,更糟糕的是,你不得不与他们一起工作/操作。通常情况下,统一语法会产生错误的指令,不得不摆弄语法来让它工作。但是如果我必须得到一个特定的指令,那么我必须摆弄它来生成我所追求的特定指令。除了引导程序和一些其他的异常,你不经常写汇编语言,无论如何,通常编译一些东西,然后用编译器生成的代码,并调整它或替换它。
在统一语法之前,我开始使用arm gnu工具,所以我习惯于

.thumb
.globl hello
hello:
  sub r0,#1
  bne hello

而不是

.thumb
.globl hello
hello:
  subs r0,#1
  bne hello

在两种语法之间来回切换也很好(统一和不统一,是的,一个工具中有两种汇编语言)。
以上所有都是32位arm,如果你对64位arm感兴趣,并且使用gnu工具,那么这一点仍然适用,你只需要使用aarch 64工具而不是gnu的arm工具。ARM的aarch 64是一个完全不同的,不兼容的,指令集。但是像.global和.type... function这样的gnu语法经常在所有gnu支持的目标上使用。对于某些指令也有例外,但是如果您采取同样的方法,让工具本身告诉您它们是如何工作的...通过使用它们...您可以弄清楚这一点。

so.elf:     file format elf64-littleaarch64

Disassembly of section .text:

0000000000400000 <fun>:
  400000:   52800041    mov w1, #0x2                    // #2
  400004:   52800020    mov w0, #0x1                    // #1
  400008:   14000001    b   40000c <foo>

000000000040000c <foo>:
  40000c:   52800040    mov w0, #0x2                    // #2
  400010:   d65f03c0    ret

存储器

ego6inou

ego6inou2#

您需要做的是根据需要将参数放置在正确的寄存器中(或堆栈上)。关于如何做到这一点的所有细节都是所谓的调用约定,并形成了应用程序二进制接口(ABI)的一个非常重要的部分。
有关ARM(Armv7)调用约定的详细信息,请访问:https://developer.arm.com/documentation/den0013/d/Application-Binary-Interfaces/Procedure-Call-Standard

相关问题