c++ gcc inline assembly does not seem to work

2019-09-21 04:05发布

问题:

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

回答1:

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.



回答2:

Here's an example of inline assembly with gcc.

Routine "vazio" hosts assembly code for routine "rotina" (vazio and rotina are simply labels). Note the use of Intel syntax by means of a directive; gcc defaults to AT&T .

I recovered this code from an old sub-directory; variables in assembly code were prefixed with "_" , as "_str" - that's standard C convention. I confess that, here and now, I have no idea as why the compiler is accepting "str" instead... Anyway:

compiled correctly with gcc/g++ versions 5 and 7! Hope this helps. Simply call "gcc main.c", or "gcc -S main.c" if you want to see the asm result, and "gcc -S masm=intel main.c" for Intel output.

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