为 Windows x86 编写程序集
Posted
技术标签:
【中文标题】为 Windows x86 编写程序集【英文标题】:Writing Assembly for Windows x86 【发布时间】:2012-06-03 05:42:18 【问题描述】:我想使用 x86 程序集为 Windows 编写简单的程序(控制台输入/输出),主要是因为我只是好奇。如果有人能指出我正确的方向,那就太好了。我已经对一些更简单的 x86 指令、寄存器的功能等有了相当好的理解,但对我来说程序如何与操作系统交互以及如何使用标准输入和输出仍然是个谜。我知道这些事情与诸如 advapi32.dll 和 kernel32.dll 之类的库有关,并且有关联的静态库 .lib 文件使编译器能够使用这些动态链接的库,但除此之外,我几乎不知道这是怎么回事发生。我什至对 C 等语言中的头文件如何使用 .lib 文件感到困惑。
【问题讨论】:
先用C写代码,看看编译器生成的程序集。 嘿,这是一个非常时髦的想法。你认为这真的有可能吗? 当然,所有 C 编译器都支持以汇编格式生成代码的选项。调试器还可以显示机器代码,它内置了反汇编器。 我会试一试的。从经验中学习一直是我的强项。我学习的第一种语言是 VB,我通过拖动表单上的元素,双击它们以访问它们的事件处理程序,然后在显示所有命名空间、函数、子例程的小弹出框的帮助下输入有意义的代码,以及可以使用的其他对象。前一年我看过一些 VB 代码,所以我知道如何使用“.”。等等。是的,花了一段时间。最后我用谷歌搜索了一些东西,比如如何在屏幕上画线。我 12 岁的时候编程要简单得多。 哈哈,系统?我四天前刚高中毕业。 【参考方案1】:也许最简单的方法是为构建一些简单的程序提供指导,并让您从那里进行推断。首先你需要一些源代码:
.386
.MODEL flat, stdcall
; This is what would come from a header -- a declaration of a the Windows function:
MessageBoxA PROTO near32 stdcall, window:dword, text:near32,
windowtitle:near32, style:dword
.stack 8192
.data
message db "Hello World!", 0
windowtitle db "Win32 Hello World.", 0
.code
main proc
invoke MessageBoxA, 0, near32 ptr message, near32 ptr windowtitle, 0
ret
main endp
end main
要构建它,我们会调用masm
,例如:
ml hello32.asm -link -subsystem:windows user32.lib
这告诉它要组装的文件,当它链接时,告诉它链接它作为 Windows 子系统(主要替代方案是-subsystem:console
)并链接到user32.lib
。后者为我们提供了MessageBoxA
的定义。
写入控制台的类似程序(奇怪的是)稍微复杂一点:
.386
.MODEL flat, stdcall
getstdout = -11
WriteFile PROTO NEAR32 stdcall, \
handle:dword, \
buffer:ptr byte, \
bytes:dword, \
written: ptr dword, \
overlapped: ptr byte
GetStdHandle PROTO NEAR32, device:dword
ExitProcess PROTO NEAR32, exitcode:dword
.data
message db "Hello World!", 13, 10
msg_size dd $ - offset message
.data?
written dd ?
.code
main proc
invoke GetStdHandle, getstdout
invoke WriteFile, \
eax, \
offset message, \
msg_size, \
offset written, \
0
invoke ExitProcess, 0
main endp
end main
构建几乎相同,只是它使用控制台,因此我们指定控制台子系统,并且我们正在使用的功能在内核中定义:
ml hello_console.asm -link -subsystem:console kernel32.lib
标头将包含我上面为MessageBoxA
、GetStdHandle
、WriteFile
等提供的声明的等效项。但每个标头通常会有更多的声明——例如,所有kernel32 中的函数可能位于单个标头中。
就图书馆而言,所涉及的机制有些涉及,但大多无关紧要。要完成这项工作,您可以查看(例如)MSDN,查看要链接的库,然后将其添加到命令行中。
更复杂的解释是,至少当您链接到静态库时,它会简单地找到您调用的任何函数,并将每个函数的副本放入您的可执行文件/DLL 中。如果(如上)您使用的是 DLL 中的代码,它基本上只是将一条记录放入可执行文件中,该记录告诉它所依赖的 DLL 中的哪个函数。然后,当您加载/运行程序时,加载程序会查找您的程序所依赖的所有 DLL,并加载它们(当然,也递归加载它们所依赖的任何内容)。然后加载程序修复这些引用,因此对 DLL 中函数的引用将填充为该 DLL 中已分配给该函数的任何地址。
【讨论】:
这就是装配体的样子? mov、add、jne、call、ret、pop 和其他类似的简单指令发生了什么?我认为“调用”调用过程是否正确?它似乎执行了大量指令,将几个值推送到堆栈然后执行调用。这根本不是我认为的组装...... @BigEndian:是的,invoke 是一个宏,它先进行一些推送然后调用。如果您不想使用,则不必使用,但也没有太多理由避免使用它。至于其他说明,是的,它们仍然存在,如果你要做很多事情,你可以使用它们。我们不在这里,而且比我们使用 DOS “hello world” 加载偏移量到 BX 并调用 DOS 函数 09h 的情况更多。 我认为 09h 是执行标准输出的函数的地址? @BigEndian:哎呀,对不起。不,这是函数的编号。在 DOS 下,每个函数都有一个数字,您将其加载到 AH 中,然后执行 int 21h 来调用该函数。以上是关于为 Windows x86 编写程序集的主要内容,如果未能解决你的问题,请参考以下文章
跨平台程序集 ((x64 || x86) && (Microsoft x64 || SystemV))