C ++ GCC 内联程序集似乎不起作用

c++ gcc inline assembly does not seem to work

本文关键字:不起作用 程序集 GCC      更新时间:2023-10-16

我正在尝试找出 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 是一个函数的地址。它永远不会改变。正如我所说,它适用于视觉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

IM 调用的函数如上。我将其更改为函数指针转换

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 字节堆栈对齐。

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

eax/ecx/edx上声明 clobbers ,或在其中两个寄存器中请求指针并破坏第三个寄存器,可以让您避免pusha/popa. (或者您正在使用的调用约定的任何调用破坏注册表。

您应该删除.intel_syntax noprefix。 你已经依赖于用-masm=intel编译,因为如果你没有恢复以前的模式,以防它是AT&T。 (不幸的是,我认为没有办法保存/恢复旧模式,但是有一种方言交替机制可以为不同的语法模式使用不同的模板。


您不需要也不应该为此使用内联 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);
}

作为奖励,使用 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 编译器资源管理器上。 我使用了"英特尔语法"选项,所以 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 对caller-Ox输出基本相同:

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语法基本上很糟糕,因为与GNU C asm语法不同,输入总是必须在内存中,而不是寄存器或立即。 所以你可以用GNU C做得更好,但不能完全避免内联asm。 https://gcc.gnu.org/wiki/DontUseInlineAsm。

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

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

例程"vazio">

承载例程"rotina"的汇编代码(vazio和rotina只是标签(。请注意通过指令使用英特尔语法;gcc默认为AT&T。

我从一个旧的子目录中恢复了这段代码;汇编代码中的变量以"_"为前缀,作为"_str" - 这是标准的C约定。我承认,此时此地,我不知道为什么编译器接受"str"而不是......无论如何:

使用 GCC/G++ 版本 5 和 7 正确编译!希望这有帮助。如果您想查看 asm 结果,只需调用 "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 = %dnn", 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 = %dn", a);
puts (str);
return 0;
}