ABI(application binary interface)
包括
- calling convention
- type representation
- name mangling
- executable format
- …
我个人理解ABI从二进制层面约束了二进制代码的格式应该是什么样的,比如可执行文件的格式啦,使用的指令集啦,calling convention,寄存器使用啦之类的。不同的硬件的指令集不同,不同操作系统使用的calling convention可能不太一样,不同语言的编译器也有自己的一些设定。所以ABI可以看作(编译器 操作系统 硬件)。如果需要链接多个二进制代码文件,就需要确保这些二进制代码文件都遵循相同(兼容)的ABI,否则轻则链接错误,重则运行错误。比如calling convention不同的程序是可以链接在一起的,但是执行的时候就会爆炸。下面举个calling convention的例子,因为我只了解calling convention…
calling convention
x86 32bits
cdecl
1 | int f(int a, int b) { |
1 | f: |
stdcall
thiscall
是c++ non static member function的calling conventions
在linux平台上几乎和cdecl相等,除了隐式传递一个this指针参数作为第一个参数
在windows上this指针放在ecx之中
x86-64 calling conventions
x86-64 calling conventions使用了额外的register来传递参数.同时不兼容的calling convention的数目也减少了,目前主要是两种在使用当中
- Microsoft x64 calling convention
- System V AMD64 ABI
同样一段代码的汇编结果在64bit上就是用寄存器来传参数了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23f:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 5
mov eax, DWORD PTR [rbp-4]
mov esi, 5
mov edi, eax
call f
mov DWORD PTR [rbp-8], eax
mov eax, 0
leave
ret
我之前学习汇编啥的都是用64bit来操作以至于我看到32bit的汇编把所有参数都放在栈上有些吃惊。
一个例子
有一次我从网上抄袭了一段汇编内容,大概内容是这样的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18;
; assemble and link with:
; nasm -f elf printf-test.asm && gcc -m32 -o printf-test printf-test.o
;
section .text
global main
extern printf
main:
mov eax, 0xDEADBEEF
push eax
push message
call printf
add esp, 8
ret
message db "Register = %08X", 10, 0
我尝试在我的机器上1
nasm -f elf printf-test.asm && gcc -m32 -o printf-test printf-test.o
编译之后报错了(因为我是64bit的机器所以m32是需要交叉编译的),我发现了这个问题后一顿操作
改成1
2
3
4
5
6
7
8
9
10
11
12
13
14section .text
global main
extern printf
main:
mov rax, 0xDEADBEEF
push rax
push message
call printf
add rsp, 16
ret
message db "Register = %08X", 10, 0
1 | nasm -f elf64 printf-test.asm && gcc -o printf-test printf-test.o |
成功通过了编译
一运行就segment fault了
至于为什么…看看我是怎么调用printf的就行了1
2
3
4
5
6
7
8
9
10
11
12section .text
global main
extern printf
main:
mov rax, 0xDEADBEEF
mov rsi, rax
mov rdi, message
call printf
ret
message db "Register = %08X", 10, 0
再编译一下运行就成功啦,很显然在64位机器上的gcc使用的calling convention是认为参数在寄存器中的。