所以我有一个包含3个参数ASM_Method(void*, void*, int)
和init_method(float, int*)
的汇编例程。感兴趣的是前者的无效指针。
当我从C ++文件中调用方法时,参数为:
float src[64];
float dest[64];
int radius[3];
init_method(1.5, radius);
ASM_Method(src, dest, 64);
反汇编此调用过程:
mov r8d,100h
lea rdx,[rbp+0A0h]
lea rcx,[rbp-60h]
call ASM_Method
初始化与否,程序运行正常。但是,当我这样做时:
float* src = new float[64];
float* dest = new float[64];
int radius[3];
init_method(1.5, radius);
ASM_Method(src, dest, 64);
调用时,RCX设置为非正确地址的值,但RDX是正确的。程序崩溃了。
反汇编此调用过程:
mov r8d,100h
mov rdx,rbx
mov rcx,rdi
call ASM_Method
除非我将src初始化为某些值,否则在调用时RCX会更改为无效地址(在本例中为1)。
ASM_Method的汇编代码:
mov rax, rdx
add rax, r8
shr r8, 4
inc r8
xor r9, r9
movdqu xmm1, [rax]
MainLoop:
movdqu xmm0, [rcx + r9]
movdqu [rdx + r9], xmm0
add r9, 16
dec r8
jnz MainLoop
movdqu [rax], xmm1
ret
init_method的汇编代码:
mulss xmm0, xmm0
mov ecx, 4
cvtsi2ss xmm1, ecx
mulss xmm0, xmm1
shr ecx, 2
cvtsi2ss xmm2, ecx
addss xmm2, xmm0
sqrtss xmm2, xmm2
stmxcsr roundFlags
or roundFlags, 2000h
ldmxcsr roundFlags
cvtss2si ecx, xmm2
stmxcsr roundFlags
and roundFlags, 0DFFFh
ldmxcsr roundFlags
mov eax, ecx
dec eax
bt ecx, 0
cmovnc ecx, eax
mov eax, 3
cvtsi2ss xmm1, eax
mulss xmm0, xmm1
cvtsi2ss xmm3, ecx
movss xmm2, xmm3
movss xmm4, xmm3
mulss xmm2, xmm2
mulss xmm2, xmm1
mov eax, 12
cvtsi2ss xmm1, eax
mulss xmm3, xmm1
mov eax, -4
cvtsi2ss xmm1, eax
mulss xmm4, xmm1
addss xmm4, xmm1
mov eax, 9
cvtsi2ss xmm1, eax
subss xmm0, xmm2
addss xmm3, xmm1
subss xmm0, xmm3
divss xmm0, xmm4
cvtss2si eax, xmm0
mov esi, ecx
add esi, 2
mov edi, ecx
cmp eax, 0
cmovle edi, esi
shr edi, 1
mov dword ptr [edx], edi
mov edi, ecx
cmp eax, 1
cmovle edi, esi
shr edi, 1
mov dword ptr [edx + 4], edi
mov edi, ecx
cmp eax, 2
cmovle edi, esi
shr edi, 1
mov dword ptr [edx + 8], edi
ret
发生了什么事?
答案 0 :(得分:5)
我[仍然!]喜欢案例2的完整反汇编。但是,我会猜测。
(1)编译器用{[1}}填充rdi
。它是src
的地址[可能来自new
和/或malloc
]。
在MS ABI中,rdi
被视为"非易失性"。它必须由被调用者
(2)案例2然后调用init_method
。但是,init_method
确实不保留rdi
[因为必须]。它将其用于自己的目的(例如edi
)。因此,在返回时,rdi
已被删除!
(3)当程序从init_method
返回时,编译器期望 rdi
将具有与步骤(1)之后相同的值。 (即)编译器不知道init_method
已损坏rdi
,因此它使用其值来设置rcx
[ASM_Method
的第一个参数]。这个 应该是src
值,但它实际上是 值init_method
设置的值(即垃圾值,相对而言)
<强>更新强>
ABI对于各种平台[通常只是编译器]是不同的。 gcc
和clang
具有与MS不同的呼叫约定(即MS是奇怪的鸭子或通常的嫌疑人)。例如,对于gcc/clang
,rdi
包含第一个参数,是 volatile
这里应该突出显示大部分ABI的维基链接:https://en.wikipedia.org/wiki/X86_calling_conventions
更新#2:
但是为什么在调用之前会引用堆栈(即float src [64])而另一个引用寄存器(new float [64])?
由于编译器优化。为了解释,我们将关闭&#34;有点优化。
所有函数作用域变量都有一个&#34;保留的插槽&#34;在函数的堆栈框架中。所有这些&#34;插槽&#34;在堆栈帧中有一个固定的偏移量,由[由编译器计算]已知。如果函数完全具有堆栈帧[某些叶子函数可以忽略它],那么所有变量都有它们的插槽,无论是否使用了优化。坚持这个想法......
如果您拥有固定大小的数组(如情况1),则该数组的整个空间(即数据)都在帧内。因此,给定数组的地址是帧指针+数组的偏移量。因此,lea rcx,[rbp + offset_of_src]
标量变量也有插槽。这包括&#34;指向数组&#34;的内容,这就是我们在案例2中所拥有的内容。
[请记住,暂时优化 off ]案例2中缺少的部分代码与[简化]类似:
// allocate src
call malloc
mov [ebp + offset_of_src],rax
// allocate dest
call malloc
mov [ebp + offset_of_dest],rax
// push arguments for init_method and call it
call init_method
// call ASM_Method
mov r8d,64
mov edx,[ebp + offset_of_dest]
mov ecx,[ebp + offset_of_src]
call ASM_Method
注意,在这里,我们不想&#34;推动&#34;指针变量的地址,我们希望&#34; push&#34;指针变量的内容。
现在,让我们重新开启优化器。仅仅因为函数变量具有堆栈帧上的插槽并不意味着生成的代码有义务使用它。对于像情况2那样的简单函数,优化器意识到它可以使用非易失性寄存器来存储src
和dest
值,并且可以消除它们的堆栈访问/存储。
因此,通过优化,案例2看起来像:
// allocate src
call malloc
mov rdi,rax
// allocate dest
call malloc
mov rsi,rax
// push arguments for init_method and call it
call init_method
// call ASM_Method
mov r8d,64
mov edx,rsi
mov ecx,rdi
call ASM_Method
编译器选择的特定非挥发性物质是任意的。在这种情况下,它们恰好是rsi
和rdi
,但还有其他人可以选择。
编译器/优化器非常聪明地选择这些寄存器而其他寄存器保存数据值。它可以看到给定函数何时不再需要寄存器中的值,并且如果它选择的话,可以重新分配它以保存另一个[不相关]值。
好的,记住&#34;坚持这个想法&#34;?是时候呼气了。通常,一旦变量被赋予寄存器赋值,编译器就会尝试不加考虑,直到不再需要它为止。但是,有时候,没有足够的寄存器来同时保存所有活动变量。
例如,如果一个函数有[比较]四个嵌套for
循环并使用20个不同的变量,那么就没有足够的寄存器可供使用。因此,编译器可能必须生成&#34;转储&#34;寄存器中的值返回到相应变量的堆栈帧槽。这是&#34;注册溢出&#34;。
这就是为什么总是标量的堆栈帧中的一个插槽,即使它从未使用[由于优化寄存器的值]。它使编译过程更简单,并使偏移量保持不变。
另外,我们讨论的是 callee 保存的寄存器。但是,调用者保存的寄存器呢?虽然大多数功能在进入时会推动非挥发性物质并在退出时弹出它们(即它们为他们的来电者保留非挥发性物质)。
给定函数(例如A
)可以使用 volatile 寄存器来保存变量(例如r10
)的某些内容(例如sludge
)。如果它调用其他功能(例如B
),则B
可能会废弃A
的值。
因此,如果A
希望保留r10
中的值1>,则B
的来电,A
必须保存,致电{ {1}},然后将其恢复:
B
因此,可以使用堆栈帧插槽。
有时候,该函数有很多变量,为其中一些变量生成的代码看起来像非优化版本:
mov [rbp + offset_of_sludge],r10
call B
mov r10,[rbp + offset_of_sludge]
因为mov rax,[rbp + offset_of_foo]
add rax,rdx
sub rax,rdi
mov [rbp + offset_of_foo],rax
访问/使用过于频繁而无法进行非易失性寄存器分配