Gcc 为什么附加的指针参数在程序集中消失了?

Gcc 为什么附加的指针参数在程序集中消失了?,gcc,x86-64,reverse-engineering,calling-convention,debug-mode,Gcc,X86 64,Reverse Engineering,Calling Convention,Debug Mode,C代码: 无效PtrArg1int*a,int*b,int*c,int*d,int*e,int*f { 回来 } void PtrArg2int*a,int*b,int*c,int*d,int*e,int*f,int*g,int*h { 回来 } 编译 gcc -c -m64 -o basics basics.c -O0 运行 objdump -d basics -M intel -r 然后产生以下反汇编英特尔语法: 000000000000000b <PtrArg1>:

C代码:

无效PtrArg1int*a,int*b,int*c,int*d,int*e,int*f { 回来 } void PtrArg2int*a,int*b,int*c,int*d,int*e,int*f,int*g,int*h { 回来 } 编译

gcc -c -m64 -o basics basics.c -O0
运行

objdump -d basics -M intel -r
然后产生以下反汇编英特尔语法:

000000000000000b <PtrArg1>:
   b:   f3 0f 1e fa             endbr64 
   f:   55                      push   rbp
  10:   48 89 e5                mov    rbp,rsp
  13:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  17:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  1b:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  1f:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  23:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  27:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  2b:   90                      nop
  2c:   5d                      pop    rbp
  2d:   c3                      ret    

000000000000002e <PtrArg2>:
  2e:   f3 0f 1e fa             endbr64 
  32:   55                      push   rbp
  33:   48 89 e5                mov    rbp,rsp
  36:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  3a:   48 89 75 f0             mov    QWORD PTR [rbp-0x10],rsi
  3e:   48 89 55 e8             mov    QWORD PTR [rbp-0x18],rdx
  42:   48 89 4d e0             mov    QWORD PTR [rbp-0x20],rcx
  46:   4c 89 45 d8             mov    QWORD PTR [rbp-0x28],r8
  4a:   4c 89 4d d0             mov    QWORD PTR [rbp-0x30],r9
  4e:   90                      nop
  4f:   5d                      pop    rbp
  50:   c3                      ret 

PtrArg1和PtrArg2的参数数量不同,但两者的汇编指令相同。为什么?

这是由于呼叫约定系统V AMD64 ABI。前六个参数在整数寄存器中传递,所有其他参数都被推送到堆栈上

执行直到位置PtrArg2+0x4e之后,您将获得以下堆栈布局:

+----------+-----------------+
|  offset  |     content     |
+----------+-----------------+
| rbp-0x30 | f               |
| rbp-0x28 | e               |
| rbp-0x20 | d               |
| rbp-0x18 | c               |
| rbp-0x10 | b               |
| rbp-0x8  | a               |
| rbp+0x0  | saved rbp value |
| rbp+0x8  | return address  |
| rbp+0x10 | g               |
| rbp+0x18 | h               |
+----------+-----------------+
因为g和h是由调用方推送的,所以这两个函数的反汇编是相同的。给打电话的人

void Caller()
{
    PtrArg2(1, 2, 3, 4, 5, 6, 7, 8);
}
为了清晰起见,我添加了必要的类型转换,我们将得到以下分解:

Caller():
    push    rbp
    mov     rbp, rsp
    push    8
    push    7
    mov     r9d, 6
    mov     r8d, 5
    mov     ecx, 4
    mov     edx, 3
    mov     esi, 2
    mov     edi, 1
    call    PtrArg2
    add     rsp, 16
    nop
    leave
    ret


在调用PtrArg2之前,参数h=8和g=7被推送到堆栈上。

这是由于调用约定系统V AMD64 ABI。前六个参数在整数寄存器中传递,所有其他参数都被推送到堆栈上

执行直到位置PtrArg2+0x4e之后,您将获得以下堆栈布局:

+----------+-----------------+
|  offset  |     content     |
+----------+-----------------+
| rbp-0x30 | f               |
| rbp-0x28 | e               |
| rbp-0x20 | d               |
| rbp-0x18 | c               |
| rbp-0x10 | b               |
| rbp-0x8  | a               |
| rbp+0x0  | saved rbp value |
| rbp+0x8  | return address  |
| rbp+0x10 | g               |
| rbp+0x18 | h               |
+----------+-----------------+
因为g和h是由调用方推送的,所以这两个函数的反汇编是相同的。给打电话的人

void Caller()
{
    PtrArg2(1, 2, 3, 4, 5, 6, 7, 8);
}
为了清晰起见,我添加了必要的类型转换,我们将得到以下分解:

Caller():
    push    rbp
    mov     rbp, rsp
    push    8
    push    7
    mov     r9d, 6
    mov     r8d, 5
    mov     ecx, 4
    mov     edx, 3
    mov     esi, 2
    mov     edi, 1
    call    PtrArg2
    add     rsp, 16
    nop
    leave
    ret


在调用PtrArg2之前,将参数h=8和g=7推送到堆栈上。

消失?您希望函数对它们做什么,编译器将发出asm指令来实现它们

你真的回来了;作为void函数中的唯一语句,因此函数除了ret之外无需执行任何操作。如果使用-O2之类的正常优化级别进行编译,您将得到的就只有这些。调试模式代码通常不值得一看,而且充满了冗余/无用的东西。

看到某些参数的任何指令的唯一原因是您在调试模式下编译,即默认优化级别-O0,反优化调试模式。除寄存器局部变量外,每个C对象都有一个内存地址,调试模式确保每个C语句前后的内存中都有该值。这意味着在函数项上将寄存器参数溢出到堆栈


的调用约定在寄存器中传递前6个整数参数,其余的在堆栈上。堆栈参数已经有内存地址;编译器不会发出代码将它们复制到返回地址下面的其他本地变量旁边;那是毫无意义的。被调用方拥有自己的堆栈参数,也就是说,它可以将新值存储到调用方编写参数的堆栈空间,这样即使函数要修改参数,空间也可以是参数的真实地址。

消失?您希望函数对它们做什么,编译器将发出asm指令来实现它们

你真的回来了;作为void函数中的唯一语句,因此函数除了ret之外无需执行任何操作。如果使用-O2之类的正常优化级别进行编译,您将得到的就只有这些。调试模式代码通常不值得一看,而且充满了冗余/无用的东西。

看到某些参数的任何指令的唯一原因是您在调试模式下编译,即默认优化级别-O0,反优化调试模式。除寄存器局部变量外,每个C对象都有一个内存地址,调试模式确保每个C语句前后的内存中都有该值。这意味着在函数项上将寄存器参数溢出到堆栈


的调用约定在寄存器中传递前6个整数参数,其余的在堆栈上。堆栈参数已经有内存地址;编译器不会发出代码将它们复制到返回地址下面的其他本地变量旁边;那是毫无意义的。被调用方拥有自己的堆栈参数,也就是说,它可以将新值存储到调用方编写参数的堆栈空间中,这样,即使函数要修改参数,空间也可以是参数的真实地址。

这是正确的,但没有解决问题中的一些隐式假设。e、 g.对于未使用的变量,应该有任何指令,或者这是一个未优化的调试模式编译。我认为你的回答很有帮助,但可能只是结合我的回答。也许我错了,这对大多数人来说是一个足够的答案,可以给他们提供正确的方向。为什么我们要在说明书中添加rsp,16。PtrArg2中的pop rbp不应该注意到达调用者的开头吗?@INQUISTIVE add rsp,16在调用完成后清理堆栈,即从堆栈中删除g和h。pop rbp仅恢复开始时推送的rbp的上一个值。@JanWichelmann:A
h、 现在我明白你为什么这样回答了。我的结论是,OP只关注被叫方的分解,而不是被叫方,因为这就是他们在问题中展示的全部。然后逐字逐句地问为什么这两个函数的asm是相同的,根本没有提到呼叫者。也许他们是在看了其他东西之后才知道这一点的,但当被问到这一点时,似乎在理解上存在差距,你的答案无法填补。不要试图在你的答案上胡说八道,只需提供关于答案写作的反馈。我很高兴你发布了它,所以我觉得没有必要在我的文章中包含任何例子:p这是正确的,但没有解决问题中的一些隐含假设。e、 g.对于未使用的变量,应该有任何指令,或者这是一个未优化的调试模式编译。我认为你的回答很有帮助,但可能只是结合我的回答。也许我错了,这对大多数人来说是一个足够的答案,可以给他们提供正确的方向。为什么我们要在说明书中添加rsp,16。PtrArg2中的pop rbp不应该注意到达调用者的开头吗?@INQUISTIVE add rsp,16在调用完成后清理堆栈,即从堆栈中删除g和h。pop rbp只恢复开始时推送的先前rbp值。@JanWichelmann:啊,现在我明白你为什么这样回答了。我的结论是,OP只关注被叫方的分解,而不是被叫方,因为这就是他们在问题中展示的全部。然后逐字逐句地问为什么这两个函数的asm是相同的,根本没有提到呼叫者。也许他们是在看了其他东西之后才知道这一点的,但当被问到这一点时,似乎在理解上存在差距,你的答案无法填补。不要试图在你的答案上胡说八道,只需提供关于答案写作的反馈。我很高兴你发布了它,所以我觉得没有必要在我的文章中加入任何例子:P