use*_*371 1 assembly x86-64 masm calling-convention
所以,我有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);
Run Code Online (Sandbox Code Playgroud)
反汇编这个调用过程:
mov r8d,100h
lea rdx,[rbp+0A0h]
lea rcx,[rbp-60h]
call ASM_Method
Run Code Online (Sandbox Code Playgroud)
是否已初始化,程序运行正常.但是,当我这样做时:
float* src = new float[64];
float* dest = new float[64];
int radius[3];
init_method(1.5, radius);
ASM_Method(src, dest, 64);
Run Code Online (Sandbox Code Playgroud)
调用时,RCX设置为非正确地址的值,但RDX是正确的.程序崩溃了.
反汇编这个调用过程:
mov r8d,100h
mov rdx,rbx
mov rcx,rdi
call ASM_Method
Run Code Online (Sandbox Code Playgroud)
除非我将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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
到底是怎么回事?
我[仍然!]喜欢案例2 的完全反汇编.但是,我会猜测.
(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是奇怪的鸭子或通常的嫌疑人).例如,with gcc/clang,rdi持有第一个参数并且是 volatile
这是应该突出大多数ABI的维基链接:https://en.wikipedia.org/wiki/X86_calling_conventions
更新#2:
但为什么在调用之前引用堆栈(即float src [64])而另一个引用寄存器(new float [64])?
因为编译器优化.为了解释,我们将"关闭"优化一点.
所有函数范围变量在函数的堆栈帧中都有一个"保留槽".所有这些"槽"在堆栈帧内具有固定的偏移量,该偏移量由编译器计算.如果函数有一个堆栈框架[某些叶子函数可以忽略它],那么所有变量都有它们的插槽,无论是否使用了优化.坚持这个想法......
如果情况1中有固定大小的数组,则该数组的整个空间(即数据)都在帧内.因此,给定数组的地址是帧指针+数组的偏移量.因此,lea rcx,[rbp + offset_of_src]
标量变量也有插槽.这包括"指向数组的指针"之类的内容,这就是我们在案例2中所拥有的内容.
[请记住,暂时优化已关闭 ]案例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
Run Code Online (Sandbox Code Playgroud)
注意,这里,我们不想"推" 指针变量的地址,我们想"推" 指针变量的内容.
现在,让我们重新开启优化器.仅仅因为函数变量在堆栈帧上有一个槽并不意味着生成的代码有义务使用它.对于像情况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
Run Code Online (Sandbox Code Playgroud)
编译器选择的特定非挥发性物质是任意的.在这种情况下,他们恰好是rsi,rdi但还有其他人可供选择.
编译器/优化器非常聪明地选择这些寄存器而其他寄存器保存数据值.它可以看到给定函数何时不再需要寄存器中的值,并且可以重新分配它以保留另一个[不相关]值(如果它选择).
好的,还记得"坚持这个想法"吗?是时候呼气了.通常,一旦给变量赋予寄存器赋值,编译器就会尝试将其保留,直到不再需要它为止.但是,有时候,没有足够的寄存器来同时保存所有活动变量.
例如,如果一个函数有[比较]四个嵌套for循环并使用20个不同的变量,则没有足够的寄存器可供使用.因此,编译器可能必须生成将寄存器中的值"转储"回相应变量的堆栈帧槽的代码.这是"注册溢出".
这就是为什么在标量的堆栈帧中总是有一个插槽,即使它从未使用[由于优化寄存器的值].它使编译过程更简单,并且偏移量相同.
另外,我们讨论的是被调用者保存的寄存器.但是,调用者保存的寄存器呢.虽然大多数功能在进入时推动非挥发性物质并在退出时弹出它们(即它们为其呼叫者保留非挥发性物质).
给定函数(例如A)可以使用易失性寄存器来保存r10变量(例如)的某些内容(例如)sludge.如果它调用另一个函数(例如B),则B可能是垃圾A的值.
所以,如果A希望在保存的值r10 跨越调用B,A必须保存,调用B,然后将其还原:
mov [rbp + offset_of_sludge],r10
call B
mov r10,[rbp + offset_of_sludge]
Run Code Online (Sandbox Code Playgroud)
因此,可以使用堆栈帧插槽.
有时,该函数有很多变量,为其中一些变量生成的代码看起来像非优化版本:
mov rax,[rbp + offset_of_foo]
add rax,rdx
sub rax,rdi
mov [rbp + offset_of_foo],rax
Run Code Online (Sandbox Code Playgroud)
因为foo访问/使用太少而不值得进行非易失性寄存器分配
| 归档时间: |
|
| 查看次数: |
276 次 |
| 最近记录: |