C++变量在调用 x64 程序集函数后重置为 0



我正在尝试从具有四个参数的代码中C++调用 x64 程序集函数,并且程序集函数每次都将第一个参数重置为零。请在下面找到代码片段。

C++代码:测试.cpp

#include <iostream>
extern "C" int IntegerShift_(unsigned int a, unsigned int* a_shl, unsigned int* a_shr, unsigned int count);
int main(int argc, char const *argv[])
{
unsigned int a = 3119, count = 6, a_shl, a_shr;
std::cout << "a value before calling " << a << std::endl;
IntegerShift_(a, &a_shl, &a_shr, count);
std::cout << "a value after calling " << a << std::endl;
return 0;
}

x64 汇编代码:测试.asm

section .data
section .bss
section .text
global IntegerShift_
IntegerShift_:
;prologue
push rbp
mov rbp, rsp
mov rax, rdi
shl rax, cl
mov [rsi], rax
mov rax, rdi
shr rax, cl
mov [rdx], rax
xor rax,rax
;epilogue
mov rbp, rsp
pop rbp
ret

我正在处理以下环境。

操作系统- Ubuntu 18.04 64 位
汇编程序- NASM(2.13.02(C++编译器- G++ (7.4.0(

处理器- 英特尔®奔腾® CPU G3240 @ 3.10GHz × 2

我正在编译我的代码,如下所示

$ nasm -f elf64 -g -F dwarf test.asm
$ g++ -g -o test test.cpp test.o
$ ./test
$ a value before calling 3119
$ a value after calling 0

但是,如果我从汇编函数中注释掉mov [rdx], rax行,它不会重置variable a的值。我是 x64 汇编编程的新手,找不到 rdx 寄存器和变量 a 之间的关系。

unsigned int* a_shl, unsigned int* a_shr是指向unsigned int的指针,这是一种32位(dword(类型。

您执行两个 qword 存储,mov [rsi], raxmov [rdx], rax它们存储在指向的对象之外。

C 等价物将是一个接受unsigned int*参数并执行
*(unsigned long)a_shr = a>>count;的函数。 这当然是 UB,像这样的行为(覆盖其他变量(几乎是您所期望的。


大概您在禁用优化的情况下进行了编译,因此调用方实际上从堆栈中重新加载了a。 它将a_shra_shl放在堆栈框架中a旁边,您的一家商店将调用者的a副本归零。

(像往常一样,gcc 碰巧将 RDI 的前 32 位归零,同时它将a放入 EDI 作为第一个参数。 写入 32 位寄存器零扩展至完整寄存器。 所以你的另一个错误;右移高垃圾到低 32 位a_shr,没有用这个来电者咬你。

更简单的实现:

global IntegerShift    ; why the trailing underscore?  That's weird for no reason.
IntegerShift:
;prologue not needed, we don't even use the stack
; so don't waste instructions making a frame pointer.
mov   eax, edi
shl   rax, cl              ; a<<count
mov   [rsi], eax           ; 32-bit store
;mov rax, rdi       ; we can just destroy our local a, we're done with it
shr    edi, cl             ; a>>count
mov   [rdx], edi           ; 32-bit store
xor   eax, eax             ; return 0
ret

xor eax, eax是将 64 位寄存器归零的最有效方法(不浪费 REX 前缀(。 而且你的返回值无论如何都只有 32 位,因为你声明了它int,所以使用 64 位寄存器是没有意义的。

顺便说一句,如果您有可用的 BMI2(不幸的是,您的预算奔腾 CPU 中没有(,您可以避免所有寄存器复制,并在英特尔 CPU 上更有效率(SHL/RX 只有 1 uop 而不是 3 对于shl/r reg, cl因为传统的 x86 标志 - cl=0 情况的未修改语义(

shlx   eax, edi, ecx
shrx   edi, edi, ecx
mov   [rsi], eax
mov   [rdx], edi
xor   eax, eax
ret

相关内容

最新更新