我正在尝试从具有四个参数的代码中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], rax
和mov [rdx], rax
它们存储在指向的对象之外。
C 等价物将是一个接受unsigned int*
参数并执行*(unsigned long)a_shr = a>>count;
的函数。 这当然是 UB,像这样的行为(覆盖其他变量(几乎是您所期望的。
大概您在禁用优化的情况下进行了编译,因此调用方实际上从堆栈中重新加载了a
。 它将a_shr
或a_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