在本课中,我们将用汇编语言写一个 Windows 程序,程序运行时将弹出一个消息框并显示"Win32 fasm"。
理论:
Windows 为我们编程人员提供了大量的资源。其中最重要的就是Windows API (Application Programming Interface)。Windows API
是一组强大的函数。它们本身驻扎在Windows中供人们随时使用。这些函数大部分被包含在几个动态链接库中,譬如 “Kernel32.dll”, “user32.dll”, “GDI32.DLL”,Kernel32.dll 中的函数主要供我们处理内存管理和进程调度。user32.dll中的函数主要供我们控制用户界面。GDI32.DLL中的函数主要供我们处理图形方面。除了以上这3个动态链接库,你可以包含其他的动态链接库中的函数。当然你必须要有足够的这些动态链接库的资料。
动态链接库,顾名思义,这些API的代码本身不包含在Windows可执行文件中,而是在使用时才被加载。为了让应用程序在运行的时候找到这些函数。就必须首先把有关重定位的信息嵌入到可执行文件中。这些信息存在引入库中。由链接器在链接程序的时候将相关信息找出嵌入到可执行文件中。
我们FASM是通过宏来构建引入表的,所以我们需要自己通过Fasm的提供宏来包含相关的DLL和引入相应的函数。
当我们的应用程序在被加载时,PE Loader会读取相应引入表结构的成员来绝对能够读入的DLL,和相应DLL中的函数,最后将DLL加载到内存后,然后将相应的函数地址重定位,以便我们调用函数。
如果从字符集的相关性来分我们的API分为两类,一类是处理ANSI字符集的,一类是处理UNICODE字符集的。前一类的尾部带有“A”字符。 后一类的尾部带有“W”字符。(我想:W是代表宽字符的意思吧)。我们比较熟悉的ANSI字符串是以NULL结尾的一串字符数组。每一个ANSI字符是一个BYTE宽。对于欧洲体系ASNI已经足够了。但是对于成千上万的唯一字符的几种东方语言体系来说只能用UNICODE字符集了。每一个UNICODE字符占2个字节宽。这样就就可以在一个字符串中使用65336个不同字符了。
这也是为了增加UNICODE的原因。在大多数情况下,我们都可以包含一个头文件,在其中定义一个宏,然后在实际调用函数的时候就不用在函数名称后面加“A”和“W”字符了。
如
<#ifdef UNICODE
#define fool() foolW()
#else
#define fool()
#endif
>
先来个FASM的框架
format PE GUI 4.0
entry start
section '.data' data readable
section '.import' import data readable writeable
section '.code' code readable executable
start:
我们的应用程序是从entry指定的入口点处开始执行的。程序逐条指令开始执行,因为我们cpu是通过读取eip寄存器的值来绝定读入相应的数据执行的,所以我们程序一直执行直到遇到jmp jnz je 等跳转语句后将程序控制权转移其他语句,最后程序若要退出WINDOWS则必须调用ExitProcess函数来退出程序。。
这里是函数的原型。函数原型会告诉编译器该函数的属性。这样在编译链接的时候编译器就会进行相应的类型检查。
FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
这个就是fasm函数的原型。
在前面的ExitProcess函数有一个dword类型参数。当你调用高层invoke的时候,你可以简单的认为invoke有一个参数类型检查的语句。
例如你这样call ExitProcess
但你实现没有将dword类型的参数压入堆栈,编译链接的时候不会出错,程序在运行的时候显然出错。
但是你可以这样写 invoke ExitProcess ,那么编译器则会进行类型检查,并提示错误。。
invoke语句格式
INVOKE expression [,arguments]
expression 既可以是一个函数名也可以是一个函数指针。参数由逗号隔开。大多数api原型放在头文件中。我们fasm的头文件在include目录下。
现在我们回到ExitProcess。 其中uExitCode是退出码,用来退出程序返回给windows的。你可以这样写。
invoke ExitProcess, 0
把这一行放到开始的标示符下。
format PE GUI 4.0
include 'win32a.inc'
entry start
section '.data' data readable
section '.import' import data readable writeable
library, kernel32, 'kernel32.dll'
include 'api/kernel32.inc'
section '.code' code readable executable
start:
invoke ExitProcess, 0
我们的应用程序从win32a.inc头文件中得到相关变量结构体的定义,还需要从其他的头文件中得到函数原型。上面我们调用的函数在kernel32.dll中,我们必须通过fasm提供library宏来包含相应的dll,并且需要包含相应的头文件。因为这里是相应函数的原型。
接下来我们来调用一个消息框,
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hWnd 是父窗口的句柄。句柄代表您引用的窗口的一个地址指针。它的值对您编 Windows 程序并不重要(译者注:如果您想成为高手则是必须的),您只要知道它代表一个窗口。当您要对窗口做任何操作时,必须要引用该窗口的指针。
lpText 是指向您要显示的文本的指针。指向文本串的指针事实上就是文本串的首地址。
lpCaption 是指向您要显示的对话框的标题文本串指针。
uType 是显示在对话框窗口上的小图标的类型。
下面是完整的程序
include 'win32a.inc'
entry start
offset equ
section '.data' data readable
szCaption db 'test',0
szText db 'win32 fasm',0
section '.code' code readable executable
start:
xor ecx, ecx
invoke MessageBox,eax, offset szText, offset szCaption,MB_OK
invoke ExitProcess,ecx
section '.import' import data readable writeable
library kernel32, 'kernel32.dll',
user32, 'user32.dll'
include 'apikernel32.inc'
include 'apiuser32.inc'
你编译链接上面的程序,呵呵出现一个消息框。 “win32 fasm ” 。 由于fasm中没有offset操作符,因为fasm是自动取全局变量的地址的,但是我们可以通过声明一个无值的符号常量offset。这样编译器在编译的时候将我们的offset是抛弃的,但是我们的源代码中看起来可读性比较好。。library是fasm提供我们的宏,因为fasm就是靠宏来构建输入表的。library的格式是macro library [name,string] .
ExitProcess proto uExitCode: DWORD