我要调用一个C函数,说:
int foo(int a, int b) {return 2;}
在汇编(ARM)代码中。我读到我需要提到的
import foo
在我的汇编代码中,汇编程序在C文件中搜索foo。但是,我在从汇编程序中传递参数a和b并在汇编程序中再次检索一个整数(这里是2)时遇到了麻烦。有人能用一个小例子解释一下我如何做吗?
foo
kwvwclae1#
您已经编写了最小的示例。
编译与反汇编
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
存储器
ego6inou2#
您需要做的是根据需要将参数放置在正确的寄存器中(或堆栈上)。关于如何做到这一点的所有细节都是所谓的调用约定,并形成了应用程序二进制接口(ABI)的一个非常重要的部分。有关ARM(Armv7)调用约定的详细信息,请访问:https://developer.arm.com/documentation/den0013/d/Application-Binary-Interfaces/Procedure-Call-Standard
2条答案
按热度按时间kwvwclae1#
您已经编写了最小的示例。
编译与反汇编
任何与a和B有关的东西都是死代码,所以优化了。虽然使用C来学习asm是好的/可以开始,但你真的想用优化来做,这意味着你必须更加努力地制作实验代码。
我们学不到什么新东西。
需要为呼叫执行以下操作:
现在我们看到
(yes这是为该编译器的默认目标armv 4 t而构建的,对于其他一些人来说应该是显而易见的,我/我们不知道如何知道)(也可以从该示例中判断编译器的新旧程度(几年前有一个abi更改,在此处可见)(gcc的新版本比旧版本更差,因此旧版本仍然适用于某些用例))
根据此编译器约定(现在,虽然此编译器确实将某些文档的某些版本的arm约定用于此编译器的某些版本,但请始终记住,这是编译器作者的选择,他们没有义务遵守任何人的编写标准,而是自己选择)
所以我们看到第一个参数在r 0中,第二个在r1中。你可以用更多的操作数或更多类型的操作数来构造函数,看看有什么细微差别。寄存器中有多少个操作数,它们何时开始使用堆栈。例如,先用64位变量,然后用32位变量作为操作数,然后反过来。
以查看被调用方的情况。
我们看到r 0和r1是前两个操作数,否则编译器将严重损坏。
我们在调用者示例中没有明确看到的是,r 0是返回值的存储位置(至少对于这个变量类型)。
ABI文档不是一本容易阅读的书,但是如果你首先“试一试”,然后如果你想参考文档,它应该会对文档有帮助。在一天结束的时候,你有了一个你要使用的编译器,它有一个约定,且可能是工具链一部分,因此您必须遵守编译器约定,而不是某些第三方文档(即使该第三方是ARM)而且您可能应该使用该工具链汇编程序,这意味着您应该使用该汇编语言(ARM有许多不兼容的汇编语言,该工具定义的是语言而不是目标)。
你可以看到自己解决这个问题是多么简单。
所以这会很麻烦,但是你可以看看编译器的汇编输出,至少有些编译器会允许你这样做。
这些几乎都不是你“需要”的。
最小值如下所示
.global或.globl是等价的,在某种程度上反映了您学习gnu汇编程序的年龄或方式/时间。
如果混合使用arm和thumb指令,则会中断,默认为arm。
安全防护系统
x.o:文件格式elf 32-littlearm
拆卸章节文本:
00000000:0:e3 a00002移动r 0,#2 4:左后侧
如果我们想要拇指,我们必须告诉它
我们得到拇指。
有了ARM和gnu工具链,至少你可以混合使用arm和thumb,链接器会处理好转换
我们不需要一个引导程序或其他东西来让链接器链接,所以我们可以看到它的那部分是如何工作的。
现在它被破坏了,不仅仅是因为我们没有引导等等,而且有一个bl到foo,但是foo是thumb,调用者是arm。所以对于arm的gnu汇编程序,你可以走这个捷径,我想我是从一个更老的gcc那里学来的,但是不管怎样
.thumb_func表示您找到的下一个标签被认为是函数标签,而不仅仅是地址。
链接器添加了一个我称之为trampoline的东西,我想其他人也称之为vaneer。不管是哪种方式,只要我们写对了代码,工具链都会处理。
请记住,特别是这个汇编器的语法是非常特定于汇编器的,其他汇编器可能有其他的语法来使其工作。从gcc生成的代码中,我们看到了通用的解决方案,它是更多的类型化,但可能是一个更好的习惯。
.type foo,%函数在arm的gnu汇编器中对arm和thumb都有效。并且它不需要放在标签之前(就像.globl或.global也不需要一样。我们从这个汇编语言的工具链中得到了相同的结果。
只是为了示范...
型
你可以看到它是双向工作的,拇指到手臂,手臂到拇指,如果我们写asm,写它为我们做剩下的工作。
现在我个人讨厌统一语法,这是arm和CMSIS沿着犯的主要错误之一。但是,你想以此为生,你会发现你非常讨厌大多数公司的决策,更糟糕的是,你不得不与他们一起工作/操作。通常情况下,统一语法会产生错误的指令,不得不摆弄语法来让它工作。但是如果我必须得到一个特定的指令,那么我必须摆弄它来生成我所追求的特定指令。除了引导程序和一些其他的异常,你不经常写汇编语言,无论如何,通常编译一些东西,然后用编译器生成的代码,并调整它或替换它。
在统一语法之前,我开始使用arm gnu工具,所以我习惯于
而不是
在两种语法之间来回切换也很好(统一和不统一,是的,一个工具中有两种汇编语言)。
以上所有都是32位arm,如果你对64位arm感兴趣,并且使用gnu工具,那么这一点仍然适用,你只需要使用aarch 64工具而不是gnu的arm工具。ARM的aarch 64是一个完全不同的,不兼容的,指令集。但是像.global和.type... function这样的gnu语法经常在所有gnu支持的目标上使用。对于某些指令也有例外,但是如果您采取同样的方法,让工具本身告诉您它们是如何工作的...通过使用它们...您可以弄清楚这一点。
存储器
ego6inou2#
您需要做的是根据需要将参数放置在正确的寄存器中(或堆栈上)。关于如何做到这一点的所有细节都是所谓的调用约定,并形成了应用程序二进制接口(ABI)的一个非常重要的部分。
有关ARM(Armv7)调用约定的详细信息,请访问:https://developer.arm.com/documentation/den0013/d/Application-Binary-Interfaces/Procedure-Call-Standard