встроенная сборка c ++ gcc, похоже, не работает - PullRequest
0 голосов
/ 03 июля 2018

Я пытаюсь выяснить встроенную сборку gcc на c ++. Следующий код работает на Visual C ++ без% и других операндов, но я не мог заставить его работать с gcc

void function(const char* text) {
    DWORD addr = (DWORD)text;
    DWORD fncAddr = 0x004169E0;
        asm(
        "push %0" "\n"
        "call %1" "\n"
        "add esp, 04" "\n"
        : "=r" (addr) : "d" (fncAddr)
    );
}

Я внедряю dll в процесс во время выполнения, а fncAddr - это адрес функции. Это никогда не меняется. Как я уже сказал, он работает с Visual C ++

VC ++ эквивалент этой функции:

void function(const char* text) {
    DWORD addr = (DWORD)text;
    DWORD fncAddr = 0x004169E0;
    __asm {
        push addr
        call fncAddr
        add esp, 04
    }
}

Edit: Я изменил свою функцию на это: теперь он падает

void sendPacket(const char* msg) {
    DWORD addr = (DWORD)msg;
    DWORD fncAddr = 0x004169E0;

        asm(
        ".intel_syntax noprefix" "\n"
        "pusha" "\n"
        "push %0" "\n"
        "call %1" "\n"
        "add esp, 04" "\n"
        "popa" "\n"
        :
        : "r" (addr) , "d"(fncAddr) : "memory"
    );
}

Edit:

004169E0  /$ 8B0D B4D38100  MOV ECX,DWORD PTR DS:[81D3B4]
004169E6  |. 85C9           TEST ECX,ECX
004169E8  |. 74 0A          JE SHORT client_6.004169F4
004169EA  |. 8B4424 04      MOV EAX,DWORD PTR SS:[ESP+4]
004169EE  |. 50             PUSH EAX
004169EF  |. E8 7C3F0000    CALL client_6.0041A970
004169F4  \> C3             RETN

функция, которую я вызываю, находится выше. Я изменил его на приведение указателя функции

char_func_t func = (char_func_t)0x004169E0;
func(text); 

вот так, и он тоже разбился, но на удивление иногда он работает. Я подключил отладчик, и он выдал нарушение прав доступа по какому-либо адресу, которого не существует

в стеке вызовов последний вызов таков:

004169EF  |. E8 7C3F0000    CALL client_6.0041A970

ПОСЛЕДНИЕ РЕДАКТИРОВАТЬ:

Я отказался от встроенной сборки, вместо этого я написал инструкции, я хотел побайтно, и это работает как шарм

void function(const char* text) {
    DWORD fncAddr = 0x004169E0;

    char *buff = new char[50]; //extra bytes for no reason
    memset((void*)buff, 0x90, 50);
    *((BYTE*)buff) = 0x68; // push
    *((DWORD*)(buff + 1)) = ((DWORD)text);
    *((BYTE*)buff+5) = 0xE8; //call
    *((DWORD*)(buff + 6)) = ((DWORD)fncAddr) - ((DWORD)&(buff[5]) + 5);
    *((BYTE*)(buff + 10)) = 0x83; // add esp, 04
    *((BYTE*)(buff + 11)) = 0xC4;
    *((BYTE*)(buff + 12)) = 0x04;
    *((BYTE*)(buff + 13)) = 0xC3; // ret
    typedef void(*char_func_t)(void);
    char_func_t func = (char_func_t)buff;
    func();
    delete[] buff;
}

Спасибо всем

Ответы [ 2 ]

0 голосов
/ 04 июля 2018

Ваша текущая версия с pusha / popa выглядит правильно (медленно, но безопасно), если ваше соглашение о вызовах не зависит от поддержания выравнивания стека в 16 байтов.

Если происходит сбой, ваша настоящая проблема в другом месте, поэтому вы должны использовать отладчик и выяснить , где он падает .

Если объявить клобберы на eax / ecx / edx или запросить указатели в двух из этих регистров и перекрыть третий, это позволит вам избежать pusha / popa. (Или независимо от того, что reg-clobbered'ы относятся к соглашению о вызовах, которое вы используете.)

Вы должны удалить .intel_syntax noprefix. Вы уже зависите от компиляции с -masm=intel, потому что вы не восстанавливаете предыдущий режим, если это был AT & T. (Я не думаю, что есть способ сохранить / восстановить старый режим, к сожалению, но есть механизм диалекта-альтернативы для использования различных шаблонов для различных режимов синтаксиса.)


Для этого вам не нужно и не следует использовать встроенный ассемблер

компиляторы уже знают, как выполнять вызовы функций, когда вы используете стандартное соглашение о вызовах (в этом случае: стек аргументов в 32-битном режиме, который обычно используется по умолчанию).

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

void function(const char* text) {
    typedef void (*char_func_t)(const char *);
    char_func_t func = (char_func_t)0x004169E0;
    func(text);
}

В качестве бонуса он более эффективно компилируется с MSVC, чем ваша версия asm.

Вы можете использовать атрибуты функции GCC в указателях функций, чтобы явно указать соглашение о вызовах , если вы компилируете с другим значением по умолчанию. Например, __attribute__((cdecl)) для явного указания аргументов стека и вызовов для вызовов с использованием этого указателя функции. Эквивалент MSVC составляет всего __cdecl.

#ifdef __GNUC__
  #define CDECL   __attribute__((cdecl))
  #define STDCALL __attribute__((stdcall))
#elif defined(_MSC_VER)
  #define CDECL   __cdecl
  #define STDCALL __stdcall
#else
  #define CDECL   /*empty*/
  #define STDCALL /*empty*/
#endif

// With STDCALL instead of CDECL, this function has to translate from one calling convention to another
// so it can't compile to just a jmp tailcall
void function(const char* text) {
    typedef void (CDECL *char_func_t)(const char *);
    char_func_t func = (char_func_t)0x004169E0;
    func(text);
}

Чтобы увидеть вывод asm компилятора , я поместил это в проводник компилятора Godbolt . Я использовал опцию «intel-syntax», поэтому вывод gcc приходит с gcc -S -masm=intel

# gcc8.1 -O3 -m32   (the 32-bit Linux calling convention is close enough to Windows)
#  except it requires maintaing 16-byte stack alignment.

function(char const*):
        mov     eax, 4286944
        jmp     eax            # tail-call with the args still where we got them

Этот вызывающий тест заставляет компилятор устанавливать аргументы, а не просто хвостовой вызов , но function может встроить его в него.

int caller() {
    function("hello world");
    return 0;
}


.LC0:
        .string "hello world"
caller():
        sub     esp, 24             # reserve way more stack than it needs to reach 16-byte alignment, IDK why.
        mov     eax, 4286944        # your function pointer
        push    OFFSET FLAT:.LC0    # addr becomes an immediate
        call    eax
        xor     eax, eax            # return 0
        add     esp, 28             # add esp, 4 folded into this
        ret
Вывод

MSVC -Ox для caller практически одинаков:

caller PROC
    push     OFFSET $SG2661
    mov      eax, 4286944       ; 004169e0H
    call     eax
    add      esp, 4
    xor      eax, eax
    ret      0

Но версия с вашим встроенным ассемблером намного хуже :

;; MSVC -Ox on a caller() that uses your asm implementation of function()
caller_asm PROC
    push     ebp
    mov      ebp, esp
    sub      esp, 8
                           ; store inline asm inputs to the stack
    mov      DWORD PTR _addr$2[ebp], OFFSET $SG2671
    mov      DWORD PTR _fncAddr$1[ebp], 4286944 ; 004169e0H

    push     DWORD PTR _addr$2[ebp]      ; then reload as memory operands
    call     DWORD PTR _fncAddr$1[ebp]
    add      esp, 4

    xor      eax, eax
    mov      esp, ebp             ; makes the add esp,4 redundant in this case
    pop      ebp
    ret      0

Встроенный синтаксис MSVC asm в основном отстой, потому что в отличие от синтаксиса GNU C asm входные данные всегда должны находиться в памяти, а не в регистрах или непосредственно. Таким образом, вы могли бы добиться большего успеха с GNU C, но не так хорошо, как вы могли бы, вообще избегая inline asm. https://gcc.gnu.org/wiki/DontUseInlineAsm.

Как правило, следует избегать вызовов функций из встроенного asm; гораздо безопаснее и эффективнее, когда компилятор знает, что происходит.

0 голосов
/ 03 июля 2018

Вот пример встроенной сборки с gcc.

Рутина "vazio" содержит код сборки для рутины "rotina" (vazio и rotina - просто метки) Обратите внимание на использование синтаксиса Intel с помощью директивы; GCC по умолчанию AT & T.

Я восстановил этот код из старого подкаталога; переменные в ассемблерном коде начинаются с префикса "_", как "_str" - это стандартное соглашение C. Признаюсь, здесь и сейчас я понятия не имею, почему компилятор принимает вместо этого "str" ​​... В любом случае:

правильно скомпилировано с gcc / g ++ версий 5 и 7! Надеюсь это поможет. Просто вызовите «gcc main.c» или «gcc -S main.c», если вы хотите увидеть результат asm, и «gcc -S masm = intel main.c» для вывода Intel.

    #include <stdio.h>

    char str[] = "abcdefg";

// C routine, acts as a container for "rotina"
void vazio (void) {

    asm(".intel_syntax noprefix");
    asm("rotina:");
    asm("inc eax");

   //  EBX = address of str
    asm("lea ebx, str");

   // ++str[0]
   asm("inc byte ptr [ebx]");

    asm("ret");
    asm(".att_syntax noprefix");
}


// global variables make things simpler
int a;

int main(void) {

    a = -7;

    puts ("antes");
    puts (str);
    printf("a = %d\n\n", a);

    asm(".intel_syntax noprefix");

   asm("mov eax, 0");

    asm("call rotina");

    // modify variable a
    asm("mov a, eax");

    asm(".att_syntax noprefix");

    printf("depois: \n a = %d\n", a);

   puts (str);

    return 0;
}
...