代码之家  ›  专栏  ›  技术社区  ›  Tom

c++gcc内联程序集似乎不工作

  •  -2
  • Tom  · 技术社区  · 7 年前

    我试图在c++上找出gcc内联程序集。下面的代码可以在没有%和其他操作数的visualc++上运行,但是我不能使它与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++工作的

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

    2 回复  |  直到 7 年前
        1
  •  0
  •   Peter Cordes    7 年前

    你的当前版本 pusha popa 看起来正确(缓慢但安全),除非调用约定依赖于保持16字节堆栈对齐。

    如果它崩溃了,你真正的问题就在其他地方,所以你应该使用调试器找出原因 它崩溃了

    宣布重拳出击 eax / ecx / edx ,或者在其中两个寄存器中请求指针,然后删除第三个寄存器,都会让您避免 普沙 波帕 . (或任何与您所使用的调用约定相关的call clobbed regs。)

    .intel_syntax noprefix . 你已经依赖于编译 -masm=intel ,因为您不会恢复以前的模式,以防它在&t;t。(很遗憾,我认为没有办法保存/恢复旧模式,但确实有 a dialect-alternatves mechanism 对于不同的语法模式使用不同的模板。)


    您不需要也不应该为此使用内联asm

    (在这种情况下:堆栈参数处于32位模式,这通常是默认模式)。

    它是一个有效的C++,它将一个整数转换为函数指针。 ,如果在那个地址上真的有一个函数的话,这甚至不是未定义的行为。

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

    另外,与asm版本相比,MSVC的编译效率也更高。

    可以在函数指针上使用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输出 on the Godbolt compiler explorer . 我使用了“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
    

    但是使用内联asm的版本要糟糕得多 :

    ;; 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语法基本上很糟糕,因为与gnucasm语法不同的是,输入总是必须在内存中,而不是在寄存器或立即器中。因此,您可以更好地使用gnuc,但不如完全避免内联asm那么好。 https://gcc.gnu.org/wiki/DontUseInlineAsm

    通常要避免从内联asm调用函数;当编译器知道发生了什么时,这样更安全、更高效。

        2
  •  -4
  •   A Koscianski    7 年前

    下面是一个使用gcc的内联汇编的示例。

    例程“vazio”承载例程“rotina”的汇编代码(vazio和rotina只是标签)。注意,Intel语法是通过指令使用的;gcc默认为AT&T。

    我从一个旧的子目录中恢复了这段代码;汇编代码中的变量以“U”作为前缀,这是标准的C约定。我承认,此时此地,我不知道为什么编译器接受“str”。。。不管怎样:

    使用gcc/g++版本5和7正确编译!希望这有帮助。如果您想查看asm结果,只需调用“gcc main.c”或“gcc-S main.c”,对于intel输出,则调用“gcc-smasm=intel main.c”。

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