Winapi C程序集PUSH不适用于WriteFile函数
我用C-assembly调用(_GetStdHandle@4)函数获取(输出)句柄,然后使用(_WriteFile@20)函数使用我从中获得的句柄在控制台上写入字符串(_GetStdHandle@4). 我在我的源代码中为每个函数使用了(pushl)来传递参数,但有些地方出错了,因为(WriteFile))函数返回错误(6),这是无效的句柄,但句柄是有效的。。。所以,传递论点有点不对劲。。。对我的问题是使用(pushl)将参数传递给(_WriteFile)函数。。。在这段代码中,我对每个参数使用(g),因为没有理由将参数移动到寄存器,然后推动寄存器。。。所以我没有使用(r),但是如果我使用(r),程序工作没有任何问题(首先将参数移动到寄存器,然后推动寄存器(我希望推动参数而不将其移动到寄存器中) 这段代码没有显示任何内容,问题出在(WriteFile)函数上,如果我对(WriteFile)参数使用(r),打印就会完成,但为什么我不能使用“g”将参数移动到寄存器Winapi C程序集PUSH不适用于WriteFile函数,winapi,gcc,assembly,x86,inline-assembly,Winapi,Gcc,Assembly,X86,Inline Assembly,我用C-assembly调用(_GetStdHandle@4)函数获取(输出)句柄,然后使用(_WriteFile@20)函数使用我从中获得的句柄在控制台上写入字符串(_GetStdHandle@4). 我在我的源代码中为每个函数使用了(pushl)来传递参数,但有些地方出错了,因为(WriteFile))函数返回错误(6),这是无效的句柄,但句柄是有效的。。。所以,传递论点有点不对劲。。。对我的问题是使用(pushl)将参数传递给(_WriteFile)函数。。。在这段代码中,我对每个参数使用
typedef void * HANDLE;
#define GetStdHandle(result, handle) \
__asm ( \
"pushl %1\n\t" \
"call _GetStdHandle@4" \
: "=a" (result) \
: "g" (handle))
#define WriteFile(result, handle, buf, buf_size, written_bytes) \
__asm ( \
"pushl $0\n\t" \
"pushl %1\n\t" \
"pushl %2\n\t" \
"pushl %3\n\t" \
"pushl %4\n\t" \
"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\0"
.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,而不是直接调用它们。您可以使用
\uuuuu属性(stdcall))
如果您不需要使用内联程序集,则不应该使用。GCC的内联程序集很难纠正。如果出错,代码可能会一直运行到某一天,尤其是在启用了优化的情况下。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
。这可防止生成内存操作数。如果通过寄存器传递指针,则还需要添加“内存”
clobber
允许函数销毁EAX、ECX和EDX(也称为易失性寄存器)。可能GetStdHandle
和WriteFile
会对ECX和EDX进行缓冲,并在EAX中返回一个值。您需要确保ECX和EDX也被列为缓冲(或具有将其标记为输出的约束),否则编译器可能会假定这些寄存器中的值在内联汇编块完成之前和之后是相同的。如果它们不同,则可能会导致细微的错误
通过这些更改,您的代码可能看起来像:
#define INVALID_HANDLE_VALUE (void *)-1
typedef void *HANDLE;
#define GetStdHandle(result, handle) \
__asm ( \
"pushl %1\n\t" \
"call _GetStdHandle@4" \
: "=a" (result) \
: "g" (handle) \
: "ecx", "edx")
#define WriteFile(result, handle, buf, buf_size, written_bytes) \
__asm __volatile ( \
"pushl $0\n\t" \
"pushl %1\n\t" \
"pushl %2\n\t" \
"pushl %3\n\t" \
"pushl %4\n\t" \
"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
,这样,如果优化器认为没有使用\uuuu volatile
,就无法删除整个内联程序集。编译器不知道函数的副作用是显示被更新。请将函数标记为volatile以防止内联程序集从被完全移除result
对潜在内存操作数没有问题,因为在初始的GetStdHandle
之后没有进一步使用约束。您遇到的问题只是修改ESP时的问题(通过push/POP或直接更改为ESP)之后,在内联程序集中可能会使用内存约束push%1
pushl 12(%esp)正是这样的情况
。由于您更改了esp
,因此不再引用正确的位置,因此句柄无效。不清楚为什么要这样做。如果要汇编,请使用一个单独的汇编模块,在该模块中您可以完全控制,以便根据需要访问参数。如果坚持使用内联asm,则不允许使用内存操作数(但是你已经知道了)。有一些原始版本的更新,因为我有几个变体,并且发布了一个错误的版本(有一个bug)。为什么不使用\uuuu属性(stdcall))声明这些函数呢
并正常调用它们?@fuz:因为在他的评论中,他说我只是想了解调用WINAPI函数时WINAPI函数会发生什么情况,我问了他这个问题,他给出了回答。出于某种原因,他希望知道它在引擎盖下是如何工作的。我个人会在GCC之外的汇编中编写它,但他没有这样做他打算使用内联汇编来解决这个问题。我不确定你的评论是针对我提供的解决方案还是针对Jason(你没有标记他)。我的序言已经暗示我质疑使用内联汇编的动机。@PeterCordes:我应该指出,当我说我只使用了那个约定时,我的意思是说我只使用了那个约定,因为