【问题标题】:C++ variable reset to 0 after calling x64 assembly function调用 x64 汇编函数后 C++ 变量重置为 0
【发布时间】:2020-05-09 15:28:02
【问题描述】:

我正在尝试从具有四个参数的 C++ 代码调用 x64 汇编函数,并且汇编函数每次都将第一个参数重置为零。请在下面找到代码 sn-p。

C++ 代码:test.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 汇编代码:test.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)
处理器 - Intel® Pentium(R) 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 之间的关系。

【问题讨论】:

  • 这没有多大意义。 a 是按值传递的。尝试将其设为const 看看会发生什么。
  • @Resurrection 使a 不断解决了这个问题,但你能解释一下原因吗?如何将a 的值更改为另一个值并再次调用汇编函数。
  • @Resurrection 将a 设为常量的建议只是让编译器将其用作对cout.operator&lt;&lt;(int) 的调用的立即数,而不是将其存储/重新加载到您的错误asm 可以破坏的堆栈中它。这不是对 asm 错误的修复,只是有明显的效果。 (如果您知道编译器如何在反优化调试模式下工作,则很明显:P)

标签: c++ assembly x86-64 nasm


【解决方案1】:

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

你做了两个 qword 存储,mov [rsi], raxmov [rdx], rax,它们存储在指向对象之外。

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


大概你编译时禁用了优化,所以调用者实际上从堆栈中重新加载了a。它在堆栈框架中将a_shra_shl 放在a 旁边,您的一家商店将您的调用者的a 副本归零。

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

更简单的实现:

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(不幸的是,您的预算 Pentium CPU 上没有),您可以避免所有寄存器复制,并且在 Intel CPU 上更高效(SHL/RX 仅为 1 uop 而不是3 for shl/r reg, cl 因为 cl=0 情况下的旧版 x86 FLAGS 未修改语义)

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

【讨论】:

  • 感谢您的回答,但是如果我将变量 a 设为 const ,您能否说明为什么变量不会重置为 0?
  • @Vencat:我刚刚在你的问题下回复了@Resurrection 的奇怪评论。它改变了 GCC 编译调用者的方式,因此错误不会踩到你注意到的任何东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-27
  • 2021-09-22
  • 2012-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多