我使用c启动来调用(_getstdhandle@4(函数以获取(输出(句柄然后使用(_writeFile@20(函数来使用我在台上写下我的字符串,然后使用我获得的句柄来写下我的字符串来自(_getStdhandle@4(。我在我的源代码中使用(PUSHL(来传递参数,但是出了问题,因为(writeFile((函数返回错误(6(,该函数是无效的句柄,但句柄是有效的...因此,传递参数有问题。。...所以我没有使用(r(,但是如果我使用(r(,则该程序可以毫无问题地工作(将参数移至寄存器,然后推动寄存器(我想推动参数而不将其移动到参数寄存器(此代码一无所有,问题来自(WriteFile(函数,如果我将(r(用于(writeFile(参数,则将完成打印,但是为什么我不能使用" g"将参数移至寄存器?
typedef void * HANDLE;
#define GetStdHandle(result, handle)
__asm (
"pushl %1nt"
"call _GetStdHandle@4"
: "=a" (result)
: "g" (handle))
#define WriteFile(result, handle, buf, buf_size, written_bytes)
__asm (
"pushl $0nt"
"pushl %1nt"
"pushl %2nt"
"pushl %3nt"
"pushl %4nt"
"call _WriteFile@20"
: "=a" (result)
: "g" (written_bytes), "g" (buf_size), "g" (buf), "g" (handle))
int main()
{
HANDLE handle;
int write_result;
unsigned long written_bytes;
GetStdHandle(handle, -11);
if(handle != INVALID_HANDLE_VALUE)
{
WriteFile(write_result, handle, "Hello", 5, & written_bytes);
}
return 0;
}
此程序的汇编代码是:
.file "main.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "Hello "
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB25:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
call ___main
/APP
pushl $-11
call _GetStdHandle@4
# 0 "" 2
/NO_APP
movl %eax, 12(%esp)
cmpl $-1, 12(%esp)
je L2
leal 4(%esp), %eax
/APP
pushl $0
pushl %eax
pushl $5
pushl $LC0
pushl 12(%esp)
call _WriteFile@20
# 0 "" 2
/NO_APP
movl %eax, 8(%esp)
L2:
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE25:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
有什么问题?
我会质疑通过这样的包装来调用Winapi的需求,而不是直接打电话给它们。您可以用
声明stdcall调用约定的原型 __attribute__((stdcall))
如果您不需要使用内联装配,则不应该。海湾合作委员会的内联装配很难正确。弄错了它可以使代码看起来正常工作,直到有一天行不通,尤其是在启用了优化的情况下。大卫·沃尔弗德(David Wohlferd(有一篇很好的文章,说明为什么不需要在线装配。
在生成的代码的这一部分中可以看到主要问题:
pushl $0
pushl %eax
pushl $5
pushl $LC0
pushl 12(%esp)
call _WriteFile@20
GCC已将第一个参数的内存操作数(句柄(计算为12(%esp)
。问题在于,您已经使用了以前的推动更改了ESP ,现在偏移12(%esp)
不再是handle
所在的位置。
要解决此问题,您可以通过寄存器或直接(如果可能的话(传递内存地址。而不是使用包括m
(内存约束(的g
约束,而只需将ri
用于寄存器和即时。这样可以防止内存操作数生成。如果您通过寄存器将指针传递,您还需要添加"memory"
Clobber。
stdcall(winapi(调用惯例允许函数销毁 eax , ecx 和 edx (aka the volatile寄存器(。GetStdHandle
和WriteFile
可能会使 ecx 和 edx 以及返回 eax 中的值。您需要确保 ecx 和 edx 也被列为clobbers(或具有将其标记为输出的约束(,否则编译器可以假设这些寄存器中的值在内联块块完成之前和之后,相同。如果它们与众不同,则可能导致细微的错误。
随着这些更改,您的代码可能看起来像:
#define INVALID_HANDLE_VALUE (void *)-1
typedef void *HANDLE;
#define GetStdHandle(result, handle)
__asm (
"pushl %1nt"
"call _GetStdHandle@4"
: "=a" (result)
: "g" (handle)
: "ecx", "edx")
#define WriteFile(result, handle, buf, buf_size, written_bytes)
__asm __volatile (
"pushl $0nt"
"pushl %1nt"
"pushl %2nt"
"pushl %3nt"
"pushl %4nt"
"call _WriteFile@20"
: "=a" (result)
: "ri" (written_bytes), "ri" (buf_size), "ri" (buf), "ri" (handle)
: "memory", "ecx", "edx")
int main()
{
HANDLE handle;
int write_result;
unsigned long written_bytes;
GetStdHandle(handle, -11);
if(handle != INVALID_HANDLE_VALUE)
{
WriteFile(write_result, handle, "Hello", 5, &written_bytes);
}
return 0;
}
注释:
我将
WriteFile
内联装配标记为__volatile
,以便如果优化器认为不使用result
,则优化器将无法删除整个内线组件。编译器不知道该函数的副作用是显示显示。标记功能挥发性,以防止直列组件被完全删除。GetStdHandle
在潜在内存操作数上没有问题,因为在初始push %1
之后没有进一步使用约束。您遇到的问题仅是一个问题,当已修改了 ESP (通过推送/pop或直接更改为ESP (,并且可能会使用内存约束之后在该内联汇编中。