Переменная C ++ сбрасывается в 0 после вызова функции сборки x64 - PullRequest
1 голос
/ 23 января 2020

Я пытаюсь вызвать функцию сборки x64 из кода C ++ с четырьмя параметрами, и функция сборки каждый раз сбрасывает первый параметр на ноль. Ниже приведен фрагмент кода.

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 код сборки: 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® G3240 @ 3.10 ГГц × 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.

1 Ответ

4 голосов
/ 23 января 2020

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.

(как обычно, g cc произошло обнуление старших 32 битов RDI, в то время как 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 (чего вы не делаете на вашем бюджетном процессоре Pentium, к сожалению), вы можете избежать копирования всех регистров и быть более эффективными на процессорах Intel (SHL / RX составляет всего 1 моп вместо 3 для shl/r reg, cl из-за устаревшей семантики x86 FLAGS без изменений для cl = 0 кейс)

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