我对汇编代码不太熟悉。如果这个问题很天真,请原谅。
我有一个简单的C程序:
int f1(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)
{
int c = 3;
int d = 4;
return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + c + d;
}
int main(int argc, char** argv)
{
f1(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
我将其编译为 elf64-x86-64 ,并获得以下反汇编代码:
f1():
0000000000000000 <f1>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 89 7d ec mov %edi,-0x14(%rbp) ; 1
7: 89 75 e8 mov %esi,-0x18(%rbp) ; 2
a: 89 55 e4 mov %edx,-0x1c(%rbp) ; 3
d: 89 4d e0 mov %ecx,-0x20(%rbp) ; 4
10: 44 89 45 dc mov %r8d,-0x24(%rbp) ; 5
14: 44 89 4d d8 mov %r9d,-0x28(%rbp) ; 6
18: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%rbp) ; c = 3
1f: c7 45 fc 04 00 00 00 movl $0x4,-0x4(%rbp) ; d = 4
26: 8b 45 e8 mov -0x18(%rbp),%eax ;2
29: 8b 55 ec mov -0x14(%rbp),%edx ; 1
2c: 01 c2 add %eax,%edx
2e: 8b 45 e4 mov -0x1c(%rbp),%eax ;3
31: 01 c2 add %eax,%edx
33: 8b 45 e0 mov -0x20(%rbp),%eax ;4
36: 01 c2 add %eax,%edx
38: 8b 45 dc mov -0x24(%rbp),%eax ;5
3b: 01 c2 add %eax,%edx
3d: 8b 45 d8 mov -0x28(%rbp),%eax ; 6
40: 01 c2 add %eax,%edx
42: 8b 45 10 mov 0x10(%rbp),%eax ;7
45: 01 c2 add %eax,%edx
47: 8b 45 18 mov 0x18(%rbp),%eax ; 8
4a: 01 c2 add %eax,%edx
4c: 8b 45 20 mov 0x20(%rbp),%eax ; 9
4f: 01 c2 add %eax,%edx
51: 8b 45 f8 mov -0x8(%rbp),%eax ; c =3
54: 01 c2 add %eax,%edx
56: 8b 45 fc mov -0x4(%rbp),%eax ; d =4
59: 01 d0 add %edx,%eax
5b: 5d pop %rbp
5c: c3 retq
main():
000000000000005d <main>:
5d: 55 push %rbp
5e: 48 89 e5 mov %rsp,%rbp
61: 48 83 ec 30 sub $0x30,%rsp
65: 89 7d fc mov %edi,-0x4(%rbp)
68: 48 89 75 f0 mov %rsi,-0x10(%rbp)
6c: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp)
73: 00
74: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp)
7b: 00
7c: c7 04 24 07 00 00 00 movl $0x7,(%rsp)
83: 41 b9 06 00 00 00 mov $0x6,%r9d
89: 41 b8 05 00 00 00 mov $0x5,%r8d
8f: b9 04 00 00 00 mov $0x4,%ecx
94: ba 03 00 00 00 mov $0x3,%edx
99: be 02 00 00 00 mov $0x2,%esi
9e: bf 01 00 00 00 mov $0x1,%edi
a3: b8 00 00 00 00 mov $0x0,%eax
a8: e8 00 00 00 00 callq ad <main+0x50>
ad: c9 leaveq
ae: c3 retq
从main()
到f1()
传递参数时,堆栈上似乎有一些孔:
我的问题是:
为什么需要这些孔?
为什么我们需要下面两行组装?如果它们是用于上下文还原的,那么我看不到任何说明。 %rsi
寄存器甚至没有在其他地方使用。为什么仍将%rsi
保存在堆栈中?
65: 89 7d fc mov %edi,-0x4(%rbp)
68: 48 89 75 f0 mov %rsi,-0x10(%rbp)
1 ~ 6
已通过寄存器传递,为什么将它们移回内存? f1()
的开头?答案 0 :(得分:2)
在x86-64系统V中传递的arg ABI在堆栈中使用8字节的“插槽”,用于不适合寄存器的arg。不是8字节倍数的任何内容在下一个堆栈arg之前都会有孔(填充)。
这是跨OS /体系结构调用约定的相当标准。在32位调用约定中传递short
将使用4字节的堆栈插槽(或占用整个4字节的寄存器,无论是否将其符号扩展为整个寄存器的宽度)。
您的最后2个问题确实在问同一件事:
您在编译时没有优化,因此为了进行一致的调试,包括函数args在内的每个变量都需要一个内存地址,调试器可以在该内存地址在断点处停止修改该值。这包括main
的{{1}}和argc
,以及argv
的寄存器args。
如果您将f1
定义为main
(这是托管C实现中int main(void)
的两个有效签名之一,另一个是main
),则会有没有传入的args可以使main溢出。
如果启用了优化功能进行编译,则不会有任何废话。请参阅How to remove "noise" from GCC/clang assembly output?,以获取有关如何使编译器制作asm的建议,这很好看。例如从the Godbolt compiler explorer,用int main(int argc, char**argv)
1 编译,您将得到:
gcc -O3 -fPIC
(我使用AT&T语法而不是Intel,因为您在问题中使用了它)
IDK正是为什么gcc保留了比实际需要更多的堆栈空间;即使启用优化,有时也会发生这种情况。例如gcc的f1:
addl %esi, %edi # a2, tmp106 # tmp106 = a1 + a2
movl 8(%rsp), %eax # a7, tmp110
addl %edx, %edi # a3, tmp107
addl %ecx, %edi # a4, tmp108
addl %r8d, %edi # a5, tmp109
addl %r9d, %edi # a6, tmp110
addl %edi, %eax # tmp110, tmp110
addl 16(%rsp), %eax # a8, tmp112
addl 24(%rsp), %eax # a9, tmp113
addl $7, %eax #, tmp105 # c+d = constant 7
ret
看起来像这样:
main
在您使用的功能版本中,所有多余的废话都是由于一致调试所需的反优化结果而产生的,而默认情况下,# gcc -O3
main:
subq $16, %rsp # useless; the space isn't used and it doesn't change stack alignment.
movl $6, %r9d
movl $5, %r8d
movl $4, %ecx
pushq $9
movl $3, %edx
movl $2, %esi
movl $1, %edi
pushq $8
pushq $7
call f1@PLT
xorl %eax, %eax # implicit return 0
addq $40, %rsp
ret
可以使调试得到优化。(一致调试意味着您可以-O0
在断点处停止时的变量,甚至可以set
到同一函数内的另一个源代码行,该程序仍将按照您在C抽象中的预期运行和运行这样一来,编译器就无法在语句之间的寄存器中保留任何内容,也不能基于语句内文字常量以外的任何内容进行优化。)
jump
也意味着快速编译,不要试图有效地分配堆栈空间。
脚注1:-O0
阻止gcc优化-fPIC
中的呼叫。
否则,即使使用main
,它也可以看到该函数没有副作用,因此它可以忽略该调用,而不是对其进行内联和优化。
但是__attribute__((noinline))
意味着为共享库生成代码,(当针对Linux时)意味着可以插入符号,因此编译器无法假设-fPIC
会实际调用 this call f1@plt
的定义,因此无法基于它进行优化而没有副作用。
clang显然假设即使使用f1
,它仍然可以以这种方式进行优化,因此我猜clang假设相同功能的冲突定义是不允许的或者是什么?对于从库中进行的调用,这似乎破坏了库函数的LD_PRELOAD覆盖。