x86 与 x86_64 调用约定
x86 vs x86_64 calling convention
我们有两种架构和两个不同的年份:2007 年的 x86 和 2017 年的 x86_64。在 2007 年出版的一本基于 x86 的书中,列出了一个编译文件的示例:
#include <stdio.h>
#include <stdlib.h>
void usage(char *program_name) {
printf("Usage: %s <message> <# of times to repeat>\n", program_name);
exit(1);
}
int main(int argc, char *argv[]) {
int i, count;
if(argc < 3) // If fewer than 3 arguments are used,
usage(argv[0]); // display usage message and exit.
count = atoi(argv[2]); // Convert the 2nd arg into an integer.
printf("Repeating %d times..\n", count);
for(i = 0; i < count; i++)
printf("%3d - %s\n", i, argv[1]); // Printf the 1st arg.
return 0;
}
通过 gcc -g -o file.o file.c 编译并在 gdb 中反汇编文件或简单地将文件转储到 x86_64:看起来像这样:
0000000000000000 <usage>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi
c: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
10: 48 89 c6 mov rsi,rax
13: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 1a <usage+0x1a>
1a: b8 00 00 00 00 mov eax,0x0
1f: e8 00 00 00 00 call 24 <usage+0x24>
24: bf 01 00 00 00 mov edi,0x1
29: e8 00 00 00 00 call 2e <main>
000000000000002e <main>:
2e: 55 push rbp
2f: 48 89 e5 mov rbp,rsp
32: 48 83 ec 20 sub rsp,0x20
36: 89 7d ec mov DWORD PTR [rbp-0x14],edi
39: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi
3d: 83 7d ec 02 cmp DWORD PTR [rbp-0x14],0x2
41: 7f 0f jg 52 <main+0x24>
43: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
47: 48 8b 00 mov rax,QWORD PTR [rax]
4a: 48 89 c7 mov rdi,rax
4d: e8 00 00 00 00 call 52 <main+0x24>
52: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
56: 48 83 c0 10 add rax,0x10
5a: 48 8b 00 mov rax,QWORD PTR [rax]
5d: 48 89 c7 mov rdi,rax
60: e8 00 00 00 00 call 65 <main+0x37>
65: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
68: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
6b: 89 c6 mov esi,eax
6d: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 74 <main+0x46>
74: b8 00 00 00 00 mov eax,0x0
79: e8 00 00 00 00 call 7e <main+0x50>
7e: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
85: eb 25 jmp ac <main+0x7e>
87: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
8b: 48 83 c0 08 add rax,0x8
8f: 48 8b 10 mov rdx,QWORD PTR [rax]
92: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
95: 89 c6 mov esi,eax
97: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 9e <main+0x70>
9e: b8 00 00 00 00 mov eax,0x0
a3: e8 00 00 00 00 call a8 <main+0x7a>
a8: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
ac: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
af: 3b 45 f8 cmp eax,DWORD PTR [rbp-0x8]
b2: 7c d3 jl 87 <main+0x59>
b4: b8 00 00 00 00 mov eax,0x0
b9: c9 leave
ba: c3 ret
然而,根据我在 x86 上的汇编程序中学到的,在调用函数之前,通常的调用约定(推送 rbp,将 rsp 移动到 rbp,基本上构建一个new stack frame plus all arguments) sets in. 但是在上述 main 的 objdump 输出中,没有像
这样的行
call <addr_of_function_usage> # <usage+0x0>
就像在 x86 中一般或在书中一样。所以我的问题是,在这个 objdump 的什么地方调用了 usage 函数?
提前致谢。
在目标文件中,函数的地址还没有填写。出于这个原因,反汇编器很难看到您尝试调用的函数。如果您查看反汇编,您会看到一堆 call
指令,其中一个可能调用 usage
。它似乎是地址 0x42
的 call
。
我们有两种架构和两个不同的年份:2007 年的 x86 和 2017 年的 x86_64。在 2007 年出版的一本基于 x86 的书中,列出了一个编译文件的示例:
#include <stdio.h>
#include <stdlib.h>
void usage(char *program_name) {
printf("Usage: %s <message> <# of times to repeat>\n", program_name);
exit(1);
}
int main(int argc, char *argv[]) {
int i, count;
if(argc < 3) // If fewer than 3 arguments are used,
usage(argv[0]); // display usage message and exit.
count = atoi(argv[2]); // Convert the 2nd arg into an integer.
printf("Repeating %d times..\n", count);
for(i = 0; i < count; i++)
printf("%3d - %s\n", i, argv[1]); // Printf the 1st arg.
return 0;
}
通过 gcc -g -o file.o file.c 编译并在 gdb 中反汇编文件或简单地将文件转储到 x86_64:看起来像这样:
0000000000000000 <usage>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi
c: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
10: 48 89 c6 mov rsi,rax
13: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 1a <usage+0x1a>
1a: b8 00 00 00 00 mov eax,0x0
1f: e8 00 00 00 00 call 24 <usage+0x24>
24: bf 01 00 00 00 mov edi,0x1
29: e8 00 00 00 00 call 2e <main>
000000000000002e <main>:
2e: 55 push rbp
2f: 48 89 e5 mov rbp,rsp
32: 48 83 ec 20 sub rsp,0x20
36: 89 7d ec mov DWORD PTR [rbp-0x14],edi
39: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi
3d: 83 7d ec 02 cmp DWORD PTR [rbp-0x14],0x2
41: 7f 0f jg 52 <main+0x24>
43: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
47: 48 8b 00 mov rax,QWORD PTR [rax]
4a: 48 89 c7 mov rdi,rax
4d: e8 00 00 00 00 call 52 <main+0x24>
52: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
56: 48 83 c0 10 add rax,0x10
5a: 48 8b 00 mov rax,QWORD PTR [rax]
5d: 48 89 c7 mov rdi,rax
60: e8 00 00 00 00 call 65 <main+0x37>
65: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
68: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
6b: 89 c6 mov esi,eax
6d: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 74 <main+0x46>
74: b8 00 00 00 00 mov eax,0x0
79: e8 00 00 00 00 call 7e <main+0x50>
7e: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
85: eb 25 jmp ac <main+0x7e>
87: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
8b: 48 83 c0 08 add rax,0x8
8f: 48 8b 10 mov rdx,QWORD PTR [rax]
92: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
95: 89 c6 mov esi,eax
97: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # 9e <main+0x70>
9e: b8 00 00 00 00 mov eax,0x0
a3: e8 00 00 00 00 call a8 <main+0x7a>
a8: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
ac: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
af: 3b 45 f8 cmp eax,DWORD PTR [rbp-0x8]
b2: 7c d3 jl 87 <main+0x59>
b4: b8 00 00 00 00 mov eax,0x0
b9: c9 leave
ba: c3 ret
然而,根据我在 x86 上的汇编程序中学到的,在调用函数之前,通常的调用约定(推送 rbp,将 rsp 移动到 rbp,基本上构建一个new stack frame plus all arguments) sets in. 但是在上述 main 的 objdump 输出中,没有像
这样的行call <addr_of_function_usage> # <usage+0x0>
就像在 x86 中一般或在书中一样。所以我的问题是,在这个 objdump 的什么地方调用了 usage 函数?
提前致谢。
在目标文件中,函数的地址还没有填写。出于这个原因,反汇编器很难看到您尝试调用的函数。如果您查看反汇编,您会看到一堆 call
指令,其中一个可能调用 usage
。它似乎是地址 0x42
的 call
。