C ++ GCC内联汇编似乎不起作用

时间:2018-07-03 19:48:18

标签: c++ assembly inline intel masm

我试图找出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;
}

谢谢大家

2 个答案:

答案 0 :(得分:1)

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

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

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

您应该删除popa。您已经依靠使用.intel_syntax noprefix进行编译了,因为如果AT&T不恢复以前的模式,您将无法恢复。 (不幸的是,我不认为有一种方法可以保存/恢复旧模式,但是有a dialect-alternatves mechanism可以为不同的语法模式使用不同的模板。)


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

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

将整数转换为函数指针是有效的C ++ ,如果该地址处确实存在函数,这甚至不是未定义的行为。

-masm=intel

作为奖励,使用MSVC进行编译的效率也比您的asm版本高。

如果使用其他默认值进行编译,则可以在函数指针上使用GCC函数属性来明确指定调用约定。例如,__attribute__((cdecl))使用该函数指针为调用显式指定堆栈args和caller-pops。相当于MSVC仅为__cdecl

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

要查看编译器的asm输出,我将其放在on the Godbolt compiler explorer中。我使用了“ intel-syntax”选项,因此gcc输出来自#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); }

gcc -S -masm=intel

此测试调用程序使编译器设置了args,而不仅是尾部调用,而且# 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

MSVC的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 的{​​{1}}输出基本上是相同的:

-Ox

但是使用内联汇编的版本要糟糕得多

caller

MSVC内联asm语法基本上很烂,因为与GNU C asm语法不同,输入始终必须在内存中,而不是寄存器或立即数。因此,使用GNU C可以做得更好,但是不能完全避免使用内联汇编,因此不如您做的好。 https://gcc.gnu.org/wiki/DontUseInlineAsm

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

答案 1 :(得分:-4)

这是使用gcc进行内联汇编的示例。

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

我从一个旧的子目录中恢复了此代码;汇编代码中的变量以“ _str”前缀为“ _str”-这是标准的C约定。我承认,从现在到现在,我都不知道为什么编译器会改为接受“ str” ...无论如何:

已正确编译gcc / g ++版本5和7!希望这可以帮助。如果要查看汇编结果,只需调用“ gcc main.c”或“ gcc -S main.c”,并为英特尔输出调用“ gcc -S masm = 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;
}