为Windows x86编写汇编代码

4

我想使用x86汇编语言为Windows写一些简单的程序(控制台输入/输出),主要是因为我很好奇。如果有人能指点我正确的方向那就太好了。目前我已经对一些比较简单的x86指令、寄存器功能等有了相当不错的理解,但对程序如何与操作系统接口并使用标准输入和输出还是个迷。我知道这些东西与advapi32.dll和kernel32.dll等库有关,并且有相关的静态库.lib文件可以使编译器动态链接这些库,但除此之外我对此一无所知。我甚至模糊地知道像C语言中的头文件如何使用.lib文件。


先用C语言编写代码,再查看编译器生成的汇编代码。 - Hans Passant
嘿,那是一个相当棒的想法。不过你认为它真的可能吗? - Void Star
1
当然,所有的C编译器都支持生成汇编格式的代码选项。调试器也可以显示机器码,它内置了反汇编器。 - Hans Passant
1
我来试试。通过经验学习一直是我的强项。我学的第一种语言是VB,我是通过在表单上拖动元素,双击它们以进入它们的事件处理程序,然后输入有意义的代码并借助小弹出框显示所有命名空间、函数、子程序和其他对象来学习的。我在前一年看过一些VB代码,所以我知道如何使用“.”等符号。是的,这花了一段时间。最终,我通过谷歌搜索了一些关于如何在屏幕上画线的东西。当我12岁时,编程要简单得多。 - Void Star
出于好奇,你正在使用哪些系统?你的用户名给人一种大型机的感觉,就像IBM主机。 - zarchasmpgmr
3
哈哈,系统?我四天前刚刚从高中毕业。 - Void Star
1个回答

4
也许最好是给出一些简单程序的构建指南,然后让您从中推导出更复杂的应用。首先,您需要一些源代码:
.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

一个头文件将包含我以上为MessageBoxAGetStdHandleWriteFile等指定的声明的等效内容。每个头文件通常都会有更多这样的声明,例如 kernel32 中的所有函数可能在一个头文件中。

至于库,涉及的机制有些复杂,但大多数情况下并不相关。要完成任务,您可以查看 MSDN(例如),看看哪个库要求链接,然后将其添加到命令行上。

更复杂的解释是,至少当您链接静态库时,它会简单地查找您调用的任何函数,并将每个函数的副本放入可执行文件或 DLL 中。如果(如上所述)您正在使用 DLL 中的代码,则基本上只需将一条记录放入可执行文件中,告诉它依赖于哪个 DLL 中的哪个函数。然后,当您加载/运行程序时,加载器会查找您的程序依赖的所有 DLL,并加载它们(当然还递归地加载它们依赖的任何东西)。然后,加载器会修复这些引用,使对 DLL 中函数的引用填充为已分配给该函数在该 DLL 中的任何地址。


那就是汇编代码?mov、add、jne、call、ret、pop等简单指令去哪了?我想问一下,“invoke”是不是调用一个过程?它似乎执行了大量的指令,将多个值推入堆栈然后执行调用。这完全不是我所认为的汇编语言…… - Void Star
@BigEndian:是的,invoke是一个宏,它会进行一些推送然后调用。如果你不想使用它,你不必使用它,但也没有太多理由避免使用它。至于其他指令,是的,它们仍然存在,如果你要做很多事情,你就会使用它们。我们这里不使用它们,就像我们在DOS的“hello world”中加载偏移量到BX并调用DOS函数09h一样。 - Jerry Coffin
我理解09h是一个指向执行标准输出功能的地址? - Void Star
@BigEndian:哎呀,抱歉。不是的,它是函数编号。在DOS下,每个函数都有一个号码,你需要将其加载到AH中,然后使用int 21h来调用该函数。 - Jerry Coffin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接