C ++ GCC内联汇编似乎不起作用 [英] c++ gcc inline assembly does not seem to work
问题描述
我正在尝试找出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屋!