C ++ GCC内联汇编似乎不起作用 [英] c++ gcc inline assembly does not seem to work

查看:119
本文介绍了C ++ GCC内联汇编似乎不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试找出c ++上的gcc内联汇编.以下代码适用于不带%和其他操作数的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
    }
}

我将功能更改为此:现在它崩溃了

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"
    );
}

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;
}

谢谢大家

解决方案

您当前的pusha/popa版本看起来是正确的(缓慢但安全),除非您的调用约定依赖于维持16字节堆栈对齐. /p>

如果崩溃了,您真正的问题出在其他地方,因此您应该使用调试器并找出哪里崩溃.

eax/ecx/edx上声明Clobbers,或在其中两个寄存器中请求指针并破坏第三个寄存器,可以避免pusha/popa. (或者您正在使用的呼叫约定所使用的所有被呼叫弄乱的规则.)

您应该删除.intel_syntax noprefix.您已经依赖使用-masm=intel进行编译,因为如果AT& T,您不会还原以前的模式. (很遗憾,我认为没有办法保存/恢复旧模式,但是有一种方言替代机制,用于针对不同的语法模式使用不同的模板.)


您不需要,也不应该为此使用嵌入式asm

编译器知道使用标准的调用约定时已经进行了函数调用(在这种情况下:32位模式下的args堆栈通常是默认的).

将整数转换为函数指针是有效的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)) 可以明确指定堆栈参数和调用者-pops使用该函数指针进行的调用.等效的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输出,我将其放在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

此测试调用程序使编译器设置了args,而不仅是尾部调用,而且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可以做得更好,但是不能完全避免使用内联汇编,因此不如您做的好. https://gcc.gnu.org/wiki/DontUseInlineAsm .

通常应避免从内联asm进行函数调用;当编译器知道发生了什么时,它会更安全,更有效.

I am trying to figure out gcc inline assembly on c++. The following code works on visual c++ without % and other operands but i could not make it work with 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)
    );
}

I am injecting a dll to a process on runtime and fncAddr is an address of a function. It never changes. As I said it works with Visual C++

VC++ equivalent of that function:

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

Edit: I changed my function to this: now it crashes

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

the function im calling is above. I changed it to function pointer cast

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

like this and it crashed too but surprisingly somethimes it works. I attacted a debugger and it gave access violation at some address it does not exist

on callstack the last call is this:

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

LAST EDIT:

I gave up inline assembly, instead i wrote instructions i wanted byte by byte and it works like a charm

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;
}

Thank you all

解决方案

Your current version with pusha / popa looks correct (slow but safe), unless your calling convention depends on maintaing 16-byte stack alignment.

If it's crashing, your real problem is somewhere else, so you should use a debugger and find out where it crashes.

Declaring clobbers on eax / ecx / edx, or asking for the pointers in two of those registers and clobbering the third, would let you avoid pusha / popa. (Or whatever the call-clobbered regs are for the calling convention you're using.)

You should remove the .intel_syntax noprefix. You already depend on compiling with -masm=intel, because you don't restore the previous mode in case it was AT&T. (I don't think there is a way to save/restore the old mode, unfortunately, but there is a dialect-alternatves mechanism for using different templates for different syntax modes.)


You don't need and shouldn't use inline asm for this

compilers know how to make function calls already, when you're using a standard calling convention (in this case: stack args in 32-bit mode which is normally the default).

It's valid C++ to cast an integer to a function pointer, and it's not even undefined behaviour if there really is a function there at that address.

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

As a bonus, this compiles more efficiently with MSVC than your asm version, too.

You can use GCC function attributes on function pointers to specify the calling convention explicitly, in case you compile with a different default. For example __attribute__((cdecl)) to explicitly specify stack args and caller-pops for calls using that function pointer. The MSVC equivalent is just __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);
}


To see the compiler's asm output, I put this on the Godbolt compiler explorer. I used the "intel-syntax" option, so gcc output comes from 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

This test caller makes the compiler set up args and not just a tail-call, but function can inline into it.

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's -Ox output for caller is essentially the same:

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

But a version using your inline asm is much worse:

;; 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 inline asm syntax basically sucks, because unlike GNU C asm syntax the inputs always have to be in memory, not registers or immediates. So you could do better with GNU C, but not as good as you can do by avoiding inline asm altogether. https://gcc.gnu.org/wiki/DontUseInlineAsm.

Making function calls from inline asm is generally to be avoided; it's much safer and more efficient when the compiler knows what's happening.

这篇关于C ++ GCC内联汇编似乎不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆