printf(char* format, ...)
之类的C变量自变量函数在反汇编时会是什么样子?总是通过调用约定来标识它,还是有更多的方法来标识它?
#1 楼
在某些架构中,它非常简单,而在另一些架构中,则不是很明显。我将介绍一些我熟悉的东西。SystemV x86_64(Linux,OS X,BSD)
可能最容易识别。由于决定在
al
中指定已使用的XMM寄存器的数目的决定很笨拙,因此大多数vararg函数都是这样开始的: push rbp
mov rbp, rsp
sub rsp, 0E0h
mov [rbp+var_A8], rsi
mov [rbp+var_A0], rdx
mov [rbp+var_98], rcx
mov [rbp+var_90], r8
mov [rbp+var_88], r9
movzx eax, al
lea rdx, ds:0[rax*4]
lea rax, loc_402DA1
sub rax, rdx
lea rdx, [rbp+var_1]
jmp rax
movaps xmmword ptr [rdx-0Fh], xmm7
movaps xmmword ptr [rdx-1Fh], xmm6
movaps xmmword ptr [rdx-2Fh], xmm5
movaps xmmword ptr [rdx-3Fh], xmm4
movaps xmmword ptr [rdx-4Fh], xmm3
movaps xmmword ptr [rdx-5Fh], xmm2
movaps xmmword ptr [rdx-6Fh], xmm1
movaps xmmword ptr [rdx-7Fh], xmm0
loc_402DA1:
请注意它是如何使用
al
来确定多少xmm的寄存器溢出到堆栈上。Windows x64又名AMD64
在Win64中不太明显,但这是一个信号:与椭圆参数相对应的寄存器总是溢出到堆栈上堆栈以及与堆栈上传递的其余参数对齐的位置。例如。这是
printf
的序言: mov rax, rsp
mov [rax+8], rcx
mov [rax+10h], rdx
mov [rax+18h], r8
mov [rax+20h], r9
这里,
rcx
包含固定的format
参数,并且椭圆参数在rdx
,r8
和r9
中传递,然后在堆栈上。我们可以观察到,rdx
,r8
和r9
准确地一个接一个地存储,并且刚好在以rsp+0x28
开头的其余参数的下方。区域[rsp + 8..rsp + 0x28]正是为此目的而保留的,但是non-vararg函数通常不会在其中存储所有寄存器参数,也不会将该区域用于局部变量。例如,这是一个非易失性函数的序言: mov [rsp+10h], rbx
mov [rsp+18h], rbp
mov [rsp+20h], rsi
您可以看到它正在使用保留区来保存非易失性寄存器,而不溢出寄存器参数。
ARM
ARM调用约定的第一个参数使用
R0
-R3
,因此vararg函数需要将它们溢出到堆栈上,以与在堆栈上传递的其余参数保持一致。因此,您将看到R0
-R3
(或R1
-R3
或R2
-R3
或仅R3
)被压入堆栈,这通常在非可变参数函数中不会发生。这不是100%可靠的指标-例如Microsoft的编译器有时会将R0
-R1
压入堆栈,并使用SP
访问它们,而不是移至其他寄存器并使用它们。但是我认为这对海湾合作委员会是一个非常可靠的信号。这是一个由GCC编译的函数的示例:您不能在此之前执行其他任何操作,因为在vsprintf
处存在潜在的堆栈参数,因此R0
-R3
必须在它们之前。)#2 楼
(我的答案是特定于x86的)。在函数内部,它看起来与其他任何函数一样。唯一的区别是,在函数执行过程中的某个时刻,它将采用最后一个不变变量的(堆栈)地址,并在平台上增加字长。然后将其用作指向变量参数基础的指针。在函数的外部,您将观察到不同数量的参数作为参数传递给函数(通常,非变量参数之一将是变量参数函数的明显指示,例如硬编码格式字符串或相似的东西)。可变参数函数不能是
__stdcall
,因为__stdcall
依赖于预编译的ret XXh
指令,而可变参数函数的要点是可以传递未知数量的参数。因此,这些函数必须为__cdecl
,即调用者必须更正堆栈以删除所有推送的参数。
评论
太棒了,感谢您通过示例分解了不同的场景!
–滑翔
13年3月21日在17:39