当前位置:天才代写 > tutorial > C语言/C++ 教程 > Win32 Asm教程

Win32 Asm教程

2017-11-06 08:00 星期一 所属: C语言/C++ 教程 浏览:820

副标题#e#

这是我的Win32汇编教程。它老是在建设中,我会不断地添加内容。通过上面的next和prev链接,你可以转到后头和前面一页。

导言

先来对这个教程做个小小的先容。Win32Asm不是一个很是风行的编程语言,并且只有为数不多(但很好)的教程。大大都教程都会合在编程的win32部门(譬喻,WinAPI,尺度Windows编程技能的利用等),而不是汇编语言自己,譬喻伪代码(opcodes),寄存器(registers)的利用等。固然你能在其他教程中找到这些内容,但那些教程凡是是表明Dos编程的。它虽然可以帮你进修汇编语言,但在Windows中编程,你不再需要相识Dos间断(interrupt)和端口(port)In/Out函数。在Windows中,WindowsAPI提供了你可在你的措施中利用的尺度函数,后头还会对此有更多内容。这份教程的目标是在表明用汇编编Win32措施的同时进修汇编语言自己。

1.0-先容汇编语言

汇编语言是缔造出来取代原始的只能由处理惩罚器领略的二进制代码的。好久以前,尚没有任何高级语言,措施都是用汇编写的。汇编代码直接描写处理惩罚器可以执行的代码,譬喻:

add eax,edx

add这条指令把两个值加到一起。eax和edx被称为寄存器,它们可以在处理惩罚器内部生存值。这条代码被转换为66 03 c2(16进制)。处理惩罚器阅读这行代码,并执行它所代表的指令。像C这样的高级语言把它们本身的语言翻译为汇编语言,而汇编器又把它转换为二进制代码:

C 代码
a = a + b; >> C编译器 >> 汇编语言
add eax, edx >>汇编器>> 原始输出(十六进制)
66 03 C2

(留意该处的汇编语言的代码被简化了,实际输出抉择于C代码的上下文)

1.1-为什么?(Why?)
既然用汇编写措施很坚苦,那么为什么你用A汇编而不是C可能此外什么??-汇编发生的措施更小并且更快。在像如有人工智能一般的很是高级编程语言中,编译器要发生输出代码比起汇编来更坚苦。固然编译器变得越来越好,编译器仍然必需指出最快(或最小)的方法发生汇编代码。并且,你本身来写(汇编)代码(包罗可选的代码优化)能生成更小更快的代码。可是,虽然,这比利用高级语言难多了。尚有另一个与某些利用运行时dll的高级语言差异的处所,它们在大大都时运行精采,但有时由于dll(dll hell)而发生问题,用户老是要安装这些Dll。对付Visual C++,这不是一个问题,它们是与Windows一同安装的。而Visual Basic甚至不把本身的语言转换为汇编语言(固然5以及更高的版本举办了一些这样的转换,但不完全)。它高度依赖msvbvm50.dll-Visual Baisc虚拟机。由VB发生的exe文件仅仅存在简朴的代码和很多对这些dll的挪用。这就是vb慢的原因。汇编是所有中最快的。它仅仅用系统的dll如Kernel32.dll, User32.dll等。

译者注:dll hell是指由于dll新的版本被旧的版本给取代了。由于利用了dll新版本的措施仍然挪用新的函数,导致了致命的错误。

另一个误解是很多人认为汇编不行能用来编程。虽然,它难,但不是不行能。用汇编建设大的工程简直很难,我只是用它来写小措施,用于需要速度的代码被写在能被其他语言导入的dll中。并且,Dos和Windows尚有一个很大的区别。Dos措施把间断当“函数”用。像间断10用于显示,间断13用于文件存储等。在Windows中,API函数只有名字(好比MessageBox, CreateWindowsEx)。你能导入库(DLL)并利用个中的函数。这使得用asm写措施简朴多了。你将在下一章中进修更多关于这方面的常识。

2.0-开始前的筹备

先容已经够多了,此刻让我们开始吧。要用汇编写措施,你需要一些东西。下面,你能看到我将在本教程中利用哪些东西。我发起你安装同样的东西,因为这样你能随着教程试验文中的例子。我也给出其他的一些选择,固然个中的大部门你都可以选择,可是要告诫的是在汇编器(masm,tasm和nasm)中有很大的区别。在这个教程中,将利用masm,因为它有很多很有用的成果(譬喻invoke),它使得编程更容易。虽然,你可以本身选择你更喜欢的汇编器,但这将使你随着教程走难一些并且你不得不把教程中的例子举办转换使它可以在你用的汇编器中运行。

汇编器

我的选择:Masm(在win32asm包中)

网址:win32asm.cjb.net

描写:一个把伪代码(opcodes)翻译为给处理惩罚器读的原始输出(object文件)的汇编器

相关内容:Masm,宏(macro)汇编器,是一个有许多有用的特色的汇编器。像“invoke”,它可以简化对API函数的挪用并对数据范例举办查抄。你将在本教程的后头进修这些。假如你读了上面的文字你就知道本教程推荐利用masm。

供选择:Tasm[dl],nasm[dl]

链接器

我的选择:微软Incremental链接器(link.exe)

网址:win32asm.cjb.net(在win32asm包中)

描写:链接器把方针(object)文件和库文件(用于导入DLL中的函数)“链接”到一起输出最终的可执行文件。

关于:我用Iczelion的Win32asm包中的link.exe。但大大都的链接器都可以用。

供选择:Tasm linker[dl]


#p#副标题#e#

资源编辑器

我的选择:Borland Resource Workshop

网址:www.crackstore.com

描写:用于建设资源(图形,对话框,位图,菜单等)的资源编辑器。

#p#分页标题#e#

关于:大大都的编辑器都行。我小我私家喜好是resource workshop但你可以用你喜欢的。留意由于resource workshop建设的资源文件有时给资源编译带来贫苦,假如你想利用这个编辑器,你该当把tasm一起下下来,他内里包括了用于编译borland式资源的brc32.exe。

供选择:Symantec资源编辑器,Resource Builder等等

文本编辑器

我的选择:ultraedit

网址:www.ultraedit.com

描写:一个文本编辑器需要说明吗?

关于:文本编辑器的选择是十分本性化的。我很是喜欢ultraedit。你可以下载我为ultraedit写的语法文件,它可以使汇编代码语法高亮。但至少,选一个支持语法高亮的文本编辑器(要害字会自动标色)。这很是有用并且它使你的代码更容易读和写。Ultraedit尚有一个可以使你在代码中快速跳转到某一个函数的函数列表。

供选择:数百万的文本编辑器中的一个

参考手册

我的选择:win32措施员参考手册

网址:www.crackstore.com(或搜索互联网)

描写:你需要参考一些API函数的用法。最重要的是“win32措施员参考手册”(win32.hlp)。这是个大文件,约莫24mb(一些版本是12mb,但不全)。在这个文件中,对所有系统dll的函数(kernel,user,gdi,shell等)都做了说明。你至少需要这个文件,其他的参考(sock2.hlp, mmedia.hlp, ole.hlp等)也是有辅佐的但不必然需要。

供选择:N/A

(译者注:该教程写成较早,此刻有极好的MSDN供选择)

2.1-安装东西

此刻你已经获得这些东西了,把它们安装到你硬盘的某个角落吧。这有几个值得留意的处所:

把masm包安装到你规划写汇编源措施的谁人分区。这担保了包括文件路径的正确性。把masm(和tasm)的bin目次加到autoexec.bat的path中,并从头启动。

假如你用的是ultraedit,利用你可以在前面下载的语法文件并启用function-listview(函数列表视图)。

2.2-为你的源文件筹备目次

在某个处所建设一个win32文件夹(或其他你喜欢的名字),并为你建设的每一个工程建设一个子文件夹。

3.0-汇编基本常识

这章将教你汇编语言的基本常识

#p#副标题#e#

3.1-伪代码(opcodes)

汇编措施是用伪代码建设的。一个伪代码是一条处理惩罚器可以领略的指令。譬喻:

ADD

Add指令把两个数加到一起。大部门伪代码有参数

ADD eax, edx

ADD有两个参数。在加法的环境下,一个源一个方针。它把源值加到方针值中,并把功效生存在方针中。参数有许多差异的范例:寄存器,内存地点,直接数值(immediate values)拜见下文。

3.2-寄存器

有几种巨细的寄存器:8位,16位,32位(在MMX处理惩罚器中有更多)。在16位措施中,你仅能利用16位和8位的寄存器。在32位的措施中,你可以利用32位的寄存器。

一些寄存器是此外寄存器的一部门:譬喻,假如EAX生存了值EA7823BBh这里是其他寄存器的值。

EAX EA 78 23 BB
AX EA 78 23 BB
AH EA 78 23 BB
AL EA 78 23 BB

ax,ah,al是eax的一部门。eax是一个32位的寄存器(仅在386以上存在),ax包括了eax的低16位(2字节),ah包括了ax的高字节,而al包括了ax的低字节。因而ax是16位的,al和ax是8位的。在上面的例子中,这些是那些寄存器的值:

eax = EA7823BB (32-bit)
ax = 23BB (16-bit)
ah = 23 (8-bit)
al = BB (8-bit)

利用寄存器的例子(不要管那些伪代码,只看寄存器的说明)

mov eax, 12345678h
;Mov把一个值载入寄存器(留意:12345678h是一个十六进制值,因为h这个后缀。

mov cl, ah
;把ax的高字节移入cl

sub cl, 10
;从cl的值中减去10(十进制)

mov al, cl
;并把cl存入eax的最低字节

让我们来阐明上面的代码:

#p#分页标题#e#

mov指令可以把一个值从寄存器,内存和直接数值移入另一个寄存器。在上面的例子中,eax包括了12345678h,然后ah的值(eax左数第三个字节)被复制入了cl中(ecx寄存器的最低字节)。然后,cl减10并移回al中(eax的最低字节)

寄存器的差异范例:

全成果(General Purpose)

这些32位(它们的构成部门为16/8位)寄存器可以用来做任何工作:

eax (ax/ah/al) 加法器
ebx (bx/bh/bl) 基(base)
ecx (cx/ch/cl) 计数器
edx (dx/dh/dl) 数据

固然它们有名字,可是你可以用它们做任何事。

段(Segment)寄存器

段寄存器界说了哪一段内存被利用。你大概在win32asm顶用不着它们,因为windows有一个平坦(flat)的内存系统。在Dos中,内存被分为64kb的段,因而假如你想要定一个内存地点。你指定一个段,并用一个offset(偏移址)(像0172:0500(segment:offset))。在windows中,段有4GB的巨细,所以你在Windows中不需要段。段老是16位寄存器。

CS 代码段
DS 数据段
SS 栈段
ES 扩展段
FS (only 286+) 全成果段
GS (only 386+) 全成果段

指针寄存器

实际上,你可以把指针寄存器看成全成果寄存器来利用(除了eip),只要你生存并规复它们的原始值。指针寄存器之所以这么叫是因为它们常常被用来存储内存地点。一些伪代码(movb,scasb等)也要用它们。

esi (si) 源索引
edi (di) 方针索引
eip (ip) 指令指针

eip(在16位编程中为ip)包括了指向处理惩罚器将要执行的下一条指令的指针。因而你不能把eip看成全成果寄存器来用。

栈寄存器

有2个栈寄存器:esp和ebp。esp装有内存中当前栈的位置(在下章中,对此有更多的内容)。Ebp在函数中被用成指向局部变量的指针。

esp (sp) 栈指针
ebp (bp) 基(base)指针

4.0-内存

这部门将表明在Windows中内存是如何被打点的。

在运行于Dos和Win3.xx的16位措施中,内存被分成很多个段。这些段的巨细为64kb。为了存储内存,需要一个段指针和一个偏移址指针。段指针标明要利用的是哪个段,offset(偏移址)指针标明在段位置。看下图:

                          内存 
段 1 (64kb) 段 2 (64kb) 段 3 (64kb) 段 4(64kb) 更多

留意下面关于16位措施的表明,后头有更多关于32位的内容(但不要跳过这部门,要领略32位的内存打点,这部门很重要)上表是全部的内存,被分别成了多个64kb的段。最多有65536个段。此刻取出一段:

                         段 1(64kb) 
Offset 1 Offset 2 Offset 3 Offset 4 Offset 5 更多

为了指向段中的位置,需要利用offset。一个offset是段内部的一个位置。每个段最多有65536个offset。内存中地点的记法是:

SEGMENT:OFFSET

譬喻:

0030:4012(均为16进制)

它的意思是:段30,offset4012。为了查察谁人地点中有什么。你先要到段30,然后到该段的offset4012。在前一章中,你已经学过了段和指针寄存器。譬喻,段寄存器有:

CS 代码段
DS 数据段
SS 栈段
ES 扩展段
FS (only 286+) 全成果段
GS (only 386+) 全成果段

顾名思义:代码段(CS)包罗了当前的代码执行到了哪部门。数据段是用来标明在哪段中取出数据。栈指栈段(后头有更多)。ES,FS, GS是全成果的寄存器,而且可以用于任何段(固然在Windows中不是如此)。

指针寄存器大大都时装有offset,但全成果寄存器(ax, bx, cx, dx等)也可以这么用。IP标明当前指令执行到了哪个offset。Sp生存了当前栈的在ss(栈段中)的offset。

#p#副标题#e#

4.2-32位Windows

你大概已经留意到了关于段的一切是乏味的。在16位编程中,段是必不行少的。幸运的是,这个问题已经在32位Windows(95及以上)中获得办理。你仍然有段,但不消管他们了因为它们不再是64kb,而是4GB。你假如实验着改变段寄存器中的一个,windows甚至会瓦解。这称为平坦(flat)内存模式。只有offset,并且是32位的,因而范畴从0到4,294,967,295。内存中的每一个地点都是用offset暗示的。这真是32位胜于16位的最大利益。所以,你此刻可以忘了段寄存器并把精力会合在其他的寄存器上。

5.0-伪代码

伪代码是给处理惩罚器的指令,它实际上是原始十六进制代码的可读版。因此,汇编是最初级的编程语言。汇编中的所有对象被直接翻译为十六进制码。换句话说,你没有把高级语言翻译为初级语言的编译器上的烦恼,汇编器仅仅把汇编代码转化为原始数据。

本章将接头一些用来运算,位操纵等的伪代码。尚有跳转指令,较量等伪代码在后头先容。

5.1-一些根基的计较伪代码

MOV

#p#分页标题#e#

这条指令用来把一个处所移往(事实上是复制到)另一个处所。这个处所可以是寄存器,内存地点或是直接数值(虽然只能作为源值)。Mov指令的语法是:

mov 方针,源

你可把一个寄存器移往另一个(留意指令是在复制谁人值到方针中,尽量“mov”这个名字是移的意思)

mov edx, ecx

上面的这条指令把ecx的内容复制到了ecx中,源和方针的巨细应该一致。譬喻这个指令是犯科的:

mov al, ecx;犯科

这条伪代码试图把一个DWORD(32位)值装入一个字节(8位)的寄存器中。这不能个由mov指令来完成(有其他的指令干这事)。但这些指令是答允的因为源和方针在巨细上并没有什么差异:

mov al, bl
mov cl, dl
mov cx, dx
mov ecx, ebx

内存地点由offset指示(在win32中,前一章中有更多信息)你也能从地点的某一个处所得到一个值并把它放入一个寄存器中。下面有一个例子:

offset 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42
data 0D 0A 50 32 44 57 25 7A 5E 72 EF 7D FF AD C7

每一个块代表一个字节

offset的值这里是用字节的形式暗示的,但它事实上是32位的值,好比3A(这不是一个常见的offset的值,但假如不这样简写表格装不下),这也是一个32位的值:0000003Ah。只是为了节减空间,利用了一些不常见的低位offset。所有的值均为16进制。

看上表的offset 3A。谁人offset的数据是25, 7A, 5E, 72, EF等。譬喻,要把这个位于3A的值用mov放入寄存器中:

mov eax, dword ptr[0000003Ah]

(h后缀表白这是一个十六进制值)

mov eax, dword ptr[0000003Ah]这条指令的意思是:把位于内存地点3A的DWORD巨细的值放入eax寄存器。执行了这条指令后,eax包括了值725E7A25h。大概你留意到了这是在内存中时的反转功效:25 7A 5E 72。这是因为存储在内存中的值利用了little endian名目。这意味着越靠右的字节位数越高:字节顺序被反转了。我想一些例子可以使你把这个搞清楚。

十六进制dword(32位)值放在内存中时是这样:40, 30, 20, 10(每个值占一个字节(8位))

十六进制word(16位)值放在内存中时是这样:50, 40

回到前面的例子。你也可以对其他巨细的值这么做:

mov cl, byte ptr [34h] ; cl获得值0Dh(参考上表)

mov dx, word ptr [3Eh] ; dx将获得值 7DEFh (看上表,记着反序)

巨细有时不是必需的。

#p#副标题#e#

Mov eax,[00403045h]

因为eax是32位寄存器,编译器假定(也只能这么做)它应该从地点403045(十六进制)取个32位的值。

可以直接利用数值:

mov edx, 5006

这只是使得edx寄存器装有值5006,综括号[和]用来从括号间的内存地点处取值,没有括号就只是这个值。寄存器和内存地点也可以(他应该是32位措施中的32位寄存器):

mov eax,403045h;使eax装有值403045h(十六进制)

mov cx,[eax];把位于内存地点eax的word巨细的值(403045)移入cx寄存器。

在mov cx, [eax]中,处理惩罚器会先查察eax装有什么值(=内存地点),然后在谁人内存地点中有什么值,并把这个word(16位,因为方针-cx-是个16位寄存器)移入cx。

ADD, SUB, MUL, DIV

很多伪代码做计较事情。你可以猜出它们中的大大都的名字:add(加),sub(减),mul(乘),div(除)等。

Add伪代码有如下语法:

Add 方针,源

执行的运算是 方针=方针+源。下面的名目是答允的。

方针      源                例子 
Register Register add ecx, edx
Register Memory add ecx, dword ptr [104h] / add ecx, [edx]
Register Immediate value add eax, 102
Memory Immediate value add dword ptr [401231h], 80
Memory Register add dword ptr [401231h], edx

这条指令很是简朴。它只是把源值加到方针值中并把功效生存在方针中。其他的数学指令有:

sub 方针,源(方针=方针-源)
mul 方针,源(方针=方针×源)
div 源(eax=eax/源,edx=余数)

减法和加法一样做,乘法是方针=方针×源。除法有一点差异,因为寄存器是整数值(留意,绕回数不是浮点数)除法的功效被分为商和余数。譬喻:

28/6->商=4,余数=4
30/9->商=3,余数=3
97/10->商=9,余数=7
18/6->商=3,余数=0

此刻,取决于源的巨细,商(一部门)被存在eax中,余数(一部门)在edx:

源巨细          除法              商存于 余数存于 
BYTE(8-bits) ax / source AL AH
WORD (16-bits) dx:ax* / source AX DX
DWORD (32-bits) edx:eax* / source EAX EDX

#p#分页标题#e#

*:譬喻,假如dx=2030h,而ax=0040h,dx:ax=20300040h。dx:ax是一个双字值。个中高字代表dx,低字代表ax,Edx:eax是个四字值(64位)其高字是edx低字是eax。

Div伪代码的源可以是

an 8-bit register (al, ah, cl,...)
a 16-bit register (ax, dx, ...)
a 32-bit register (eax, edx, ecx...)
an 8-bit memory value (byte ptr [xxxx])
a 16-bit memory value (word ptr [xxxx])
a 32-bit memory value (dword ptr [xxxx])

源不行以是直接数值因为处理惩罚器不能抉择源参数的巨细。

#p#副标题#e#

位操纵

这些指令都由源和方针,除了“NOT”指令。方针中的每位与源中的每位作较量,并看是谁人指令,抉择是0照旧1放入方针位中。

指令   AND     OR      XOR     NOT 
源位 0 0 1 1 0 0 1 1 0 0 1 1 0 1
方针位 0 1 0 1 0 1 0 1 0 1 0 1 X X
输出位 0 0 0 1 0 1 1 1 0 1 1 0 1 0

假如源和方针均为1,AND把输出位设为1。

假如源和方针中有一个为1,OR把输出位设为1。

假如源和方针位纷歧样,XOR把输出位设为1。

NOT反转源位

一个例子:

mov ax, 3406
mov dx, 13EAh
xor ax,dx
ax=3406(十六进制)是二进制的0000110101001110
dx=13EA(十六进制)是二进制的0001001111101010

对这些位举办xor操纵:

源      0001001111101010 (dx) 
方针 0000110101001110 (ax)
输出 0001111010100100 (new ax)

新dx是0001111010100100 (十进制的7845, 十六进制的1EA4)

另一个例子:

mov ecx, FFFF0000h

not ecx

FFFF0000在二进制中是11111111111111110000000000000000(16个1,16个0)假如反转每位会获得

00000000000000001111111111111111(16个0,16个1)在十六进制中是0000FFFF。因而执行NOT操纵后,ecx是0000FFFFh。

步增/减

有两个很简朴的指令,DEC和INC。这些指令使内存地点和寄存器步增或步减,就是这样:

inc reg -> reg = reg + 1
dec reg -> reg = reg - 1
inc dword ptr [103405] -> 位于103405的值步增
dec dword ptr [103405] -> 位于103405的值步减

NOP

这条指令什么都不干。它仅仅占用空间和时间。它用作填充或给代码打补丁的目标。

移位(Bit Rotation 和 shifiting)

留意:下面的大部门例子利用8位数,但这只是为了使目标清楚。

Shifting函数

SHL 方针,计数(count)
SHR 方针,计数(count)

SHL和SHR在寄存器,内存地点中像左或向右移动必然数目(count)的位。

譬喻:

;这儿al=01011011(二进制)
shr al, 3

它的意思是:把al寄存器中的所有位向右移三个位置。因而al会酿成为00001011。左边的字节用0填充,而右边的字节被移出。最后一个被移出的位生存在carry-flag中。Carry-flag是处理惩罚器符号寄存器的一位,它不是像eax或ecx一样的,你可以会见的寄存器(固然有伪代码干这活),但它的值抉择于该指令的布局。它(carry-flag)会在后头表明,你要记着的独一一件事是carry是符号寄存器的一位且它可以被打开可能封锁。这个位便是最后一个移出的位。

shl和shr一样,只不外是向左移。

;这儿bl=11100101(二进制)
shl bl, 2

执行了指令后bl是10010100(二进制)。最后的两个位是由0填充的,carry-flag是1,因为最后移出的位是1。

尚有两个伪代码:

SAL 方针, 计数(算术左移)

SAR 方针, 计数(算术右移)

SAL和SHL一样,但SAR不完全和SHR一样。SAR不是用0来填充移出的位而是复制MSB(最高位)譬喻:

al = 10100110
sar al, 3
al = 11110100
sar al, 2
al = 11101001
bl = 00100110
sar bl, 3
bl = 00000100

Rotation(轮回移动) 函数

Rol 方针,计数;轮回左移

Ror 方针,计数;轮回右移

Rcl 方针,计数;通过carry轮回左移

Rcr 方针,计数;通过carry轮回右移

轮回移动(Rotation)看上去就像移(Shifting),只是移出的位又到了另一边。

譬喻:ror(轮回右移)

Win32 Asm教程

如你在上图所见,位轮回了。留意,每个被推出的位又移到了另一边。和Shifting一样,carry位装有最后被移出的位。Rcl和Rcr实际上和Rol,Rcr一样。它们的名字体现了它们用carry位来表白最后移出的位,但和Rol和Ror干同样的工作。它们没有什么差异。

#p#副标题#e#

互换

XCHG指令也很是简朴。它同在两个寄存器和内存地点之间互换:

eax = 237h
ecx = 978h
xchg eax, ecx
eax = 978h
ecx = 237h

6.0-文件布局

#p#分页标题#e#

汇编源文件被分成了几个部门。这些部门是code,data,未初始化data,constants,resource和relocations,资源部门是资源文件建设的,后头会有更多的接头。Relocation部门对我们不重要(它包括了使PE-loader可以在内存的差异的位置装载入措施的信息)。重要的部门是code,data,未初始化data和constants。大概你已经猜到,code部门包括了代码。Data装有数据,并有读写权限。整个data部门被包罗在exe文件并可以用数据初始化。

未初始化data在启动时没有内容,甚至没有包罗在exe文件自己。它只是由Windows“保存”的一部门内存。这部门也有读写权限。Constants和data部门一样,但只读。固然这部门可用作常数,但把常数界说在包括文件中更简朴也更快捷,并用作直接数值。

6.1-代表各部门的标记

在你的源文件(*.asm)中,你可以用部门标识符界说各部门:

.code;代码部门由此开始

.data;数据部门由此开始

.data?;未初始化数据部门由此开始

.const;常量部门由此开始

可执行文件(*.exe,*.dll和其他)是(在win32中)可移植执行名目(PE),我不会具体的接头它可是有几点是重要的。部门(Sections)的一些属性界说在PE头中:

Section名,RVA,offset,原始巨细,虚拟巨细和符号。Rva(相对虚拟地点)是将要装入的section部门的相对内存地点。这里相对的意思是相对付措施载入的基地点。这个地点也在PE头中,但可以由PE-loader改变(利用relocation部门)。Offset是初始化数据地址的exe文件自己的原始offset。虚拟巨细是措施在内存中将到达的巨细。符号是读/写/可执行等。

6.2-例子

这有一个示例措施:

.data
Number1 dd 12033h
Number2 dw 100h,200h,300h,400h
Number3 db "blabla",0
.data?
Value dd ?
.code
mov eax, Number1
mov ecx, offset Number2
add ax, word ptr [ecx+4] mov Value, eax

#p#副标题#e#

这个措施不能编译但不要紧。

在你的汇编措施中,你放入“部门”中的所有对象城市进入exe文件并且当措施被载入内存时,位于某个内存地点。在上面的data部门,有3个标签:Number1, Number2, Number3。这些标签会生存它们在措施中的offset因而你可以在你的措施中利用它们来指示位置。

DD直接把一个dword放在那,DW是Word而DB是byte。你也可以用db放字符串,因为它实际上是一串byte值。在例子中,data部门会酿成内存中的这样:

33,20,01,00,00,01,00,02,00,03,00,04,62,6c,61,62,6c,61,00(均为十六进制值)

(每个值位一byte)

我给个中的一些数字上了色。Number1指向byte 33地址的内存地点,Number2指向赤色00的位置,Number3是绿色的62。此刻,假如你在你的措施中这么写:

mov eax, Number1

它实际意为:

mov ecx, dword ptr[12033h地址的内存地点]

但这样:

mov ecx, offset Number1

意为:

mov ecx, 12033h地址的内存地点

在第一个例子中,ecx会获得Number1的内存地点的值。在第二其中,ecx会称为内存地点(offset)自己。下面的两个例子有沟通的结果:

mov ecx, Number1

(2)

mov ecx, offset Number1
mov ecx, dword ptr [ecx] ( or mov ecx, [ecx])

此刻让我们回到前面的例子中:

.data
Number1 dd 12033h
Number2 dw 100h,200h,300h,400h
Number3 db "blabla",0
.data?
Value dd ?
.code
mov eax, Number1
mov ecx, offset Number2
add ax, word ptr [ecx+4] mov Value, eax

标签可以利用像Number1,Number2和Number3等值,但它启动时包括0。因为它在未初始化data部门。这样的利益是,你在.data?中界说的所有对象不在可执行文件中而在内存中。

.data?
ManyBytes1 db 5000 dup (?)
.data
ManyBytes2 db 5000 dup (0)

(5000dup意为:5000个副本。值db 4,4,4,4,4,4,4和值db 7dup(4)一样)

ManyBytes1不会在文件自己,只是5000个预分派在内存中的字节。但Manybytes2会在可执行文件中使文件变大5000个字节。固然你的文件会包括5000个零,但并没有什么用。

Code部门被汇编(翻译为原始代码)并放入可执行文件中去(虽然载入后在内存中)。

#p#副标题#e#

7.0-条件跳转

在Code部门,你可以看到像这样的标签:

.code
mov eax, edx
sub eax, ecx
cmp eax, 2
jz loc1
xor eax, eax
jmp loc2
loc1:
xor eax, eax
inc eax
loc2:

(xor eax, eax意为:eax=0)

让我们来看看这些代码:

mov eax, edx;把edx放入eax中
sub eax, ecx;eax-ecx
cmp eax, 2

#p#分页标题#e#

这有一条新指令:cmp。Cmp意为compare(较量)。它能较量两个值(寄存器,内存,直接数值)并配置Z-flag(零符号)。零符号很像carry,也是内部符号寄存器的一位。

Jz loc1

这也是条新的。它是条件跳转指令。Jz=jump if zero(假如配置了零符号就跳转)。Loc1是一个标志指令“xor eax,eax|inc eax”内存开始处offset的标签。因而jz loc1=假如配置了零符号,跳往位于loc1的指令。

Cmp eax, 2;假如eax=2配置零符号
Jz loc1;假如配置了零符号就跳转
=
假如eax便是2,跳往位于loc1的指令

然后有jmp loc2.这也恰似一个跳转,可是是一个无条件跳转:它老是执行。上面的代码就是:

if ((edx-ecx)==2)
{
eax = 1;
}
else
{
eax = 0;
}

可能Basic版:

IF (edx-ecx)=2 THEN
EAX = 1
ELSE
EAX = 0
END IF

7.1-符号寄存器

符号寄存器有一套符号。它们设不配置取决于计较或其他时间。我不会接头它们的全部。只拣几个重要的说:

ZF(零符号) 当计较功效是零时该符号被配置(compare实际上是只配置符号不生存布局的减法)
SF(标记符号) 功效为负就配置
CF(carry符号) Carry符号中存放计较后最右的位。
OF(溢出符号) 标明一个溢出了的计较。如,布局和方针不匹配。

尚有更多的符号(Parity, Auxiliary, Trap, Interrupt, Direction, IOPL, Nested Task, Resume, & Virtual Mode)但因为我们不消它们,所以我不表明。

#p#副标题#e#

7.2-跳转系列

有一整套的条件跳转,并且它们跳转与否均取决于符号的状态。但由于大部门跳转指令有大白的名字,你甚至无需知道哪个符号要配置,譬喻:“假如大于便是就跳转”(jge)和“标记符号=溢出符号”一样,而“假如零就跳转”和“假如零符号=1就跳转”一样。

在下表中,“意思”指的是什么样的计较功效该跳转。“假如大于就跳转”意为:

cmp x, y
jmp 假如 x 比 y大

Win32 Asm教程

所有的跳转指令需要一个参数:要跳往的offset。

8.0-关于数的一些工作

在大大都的编程语言中利用整数照旧浮点数只取决于变量的声明。在汇编语言中,完全的差异。浮点数的计较是由出格的伪代码和FPU协处理惩罚器(浮点单位)完成的。浮点指令将会在后头接头。先来看看一些关于整数的工作。在c语言中有signed(有标记)整数和unsigned(无标记)整数。Signed是意为数有标记(+或-)。Unsigned老是正。找出下表中的差异(再一次的,这是一个byte的例子,它在其他巨细时也同样事情)。

Win32 Asm教程

因此,在有标记数中,一个byte被分为两段:0~7F用于正值。80~FF用于负值。对付dword值,它也一样:0~7FFFFFFFh为正,80000000~FFFFFFFFh为负,正如你大概已经留意到的一样,负值的最高位有一个荟萃,因为它们比80000000h大。这位被称为标记位。

3.1-有标记或无标记?

你和处理惩罚器都不能看出一个值是signed照旧unsigned。好动静是对付加法和减法来说,一个数是signed照旧unsigned没有干系。

计较:-4+9

FFFFFFFC+00000009=00000005(这是对的)

计较:5-(-9)

00000005-FFFFFFF7=0000000E(这也是对的,5――9=4)

坏动静是对付乘法,除法和较量(compare)并不是这样。因此,对付signed数有非凡的乘除伪代码:imul和idiv

Imul也有一个比mul好的处地址于它可以接管直接数值:

imul src
imul src, immed
imul dest,src, 8-bit immed
imul dest,src
idiv src

它们险些和mul,div一样,只是它们可以计较signed值。较量(compare)可以和unsigned一样用。但符号作差异的配置。因此,对付标记和无标记数字有差异的jump指令:

cmp ax, bx
ja somewhere

ja是一个无标记跳转指令。假如大于就跳转。思量这个ax=FFFFh(无标记时为FFFFh,有标记时为-1)和bx=0005h(无标记时为5,有标记时为5)。由于FFFFh在无标记时比0005大,ja指令会跳转,但假如用的是jg(指一个有标记跳转):

cmp ax, bx
jg somewhere

jg指令不会跳转,因为-1不比5大。

只要记着这点:

一个数字是有标记照旧无标记住决于你奈何看待这个数。

9.0-更多的伪代码

这儿有更多的伪代码

TEST

Test对两个参数(方针,源)执行AND逻辑操纵,并按照功效配置符号寄存器。功效自己不会生存。Test用来测试一个位,譬喻寄存器:

test eax, 100b;b后缀意为二进制
jnz bitset

假如eax右数第三个位被配置了,jnz将会跳转。Test的一个很是普遍的用法是用来测试一方寄存器是否为空:

test ecx, ecx
jz somewhere

假如ecx为零,Jz跳转

关于栈的伪代码

#p#分页标题#e#

在我讲栈的伪代码之前,我会先表明什么是栈。栈是内存的一个处所,esp为指向栈的指针。栈是用来生存姑且数值的处所,有两个指令来放入一个指和再把它取出来:push和pop。Push把一个指压入栈。Pop再把它弹出来。最后一个放入的值最先出来。一个值被放入栈中,栈指针步减,当它移出来的时候,栈指针步增。看这个例子:

(1) mov ecx, 100
(2) mov eax, 200
(3) push ecx ; save ecx
(4) push eax
(5) xor ecx, eax
(6) add ecx, 400
(7) mov edx, ecx
(8) pop ebx
(9) pop ecx

表明

1、 把100放入ecx中

2、 把200放入eax中

3、 把ecx(便是100)压入栈中(第一个压入)

4、 把eax(便是200)压入栈中(最后压入)

5、 /6/7:对ecx执行操纵,使ecx的值改变

8、 弹出ebx:ebx成为200(最后压入,最先弹出)

9、 弹出ecx:ecx又成为100(最先压入,最后弹出)

为了说明再压栈和弹栈时,内存中产生了什么,看下图:

Win32 Asm教程

(栈在这里是初始化为0,但实际上并不是这样。ESP暗示ESP指向的offset)

mov ax, 4560h
push ax

Win32 Asm教程

mov cx, FFFFh
push cx

Win32 Asm教程

pop edx

Win32 Asm教程

edx此刻是 4560FFFFh 了.

#p#副标题#e#

CALL和RET

Call跳转到某段代码并且一发明RET指令就返回。你可以把它们当作在其他编程语言中的函数或子措施。譬喻:

……代码……
call 0455659
……更多代码……

455659处的代码:

add eax, 500
mul eax, edx
ret

当执行这条指令时,处理惩罚器跳到455659处的代码,执行指令一直到ret为止,并返回到挪用处的下一条。Call跳转到的代码被成为进程(procedure)。你可以把你重复利用的代码写进一个进程并在你每次需要它的时候挪用。

更深入的细节:call把EIP(指向将要执行指令的指针)压入栈,而ret指令在它返回的时候把它弹出来。你也可以给一个call指定的参数。这是由压栈来完成的:

push something
push something2
call procedure

在一个挪用的内部,参数从栈中读出并利用。留意,只在进程中需要的局部变量也储存在栈中。我不会在此深入下去,因为它可以在masm和tasm中很等闲的完称。只要记着你可以写进程,并且它们可以由参数。一个重要的处所:

eax险些老是用来装一个进程的返回值。

对付windows函数也是如此。但然,你可以在你的进程利用其他的寄存器,但这是尺度。

10.0-masm的利益

假如你不在利用masm,你可以跳过这章并实验着转换所有的例子,或岂论如何地读一下,并试着说服本身利用masm。虽然,这是你的选择。但masm真的使汇编语言更容易了。

10.1-条件和轮回布局

Masm有一些伪高阶的语法来轻便地建设条件和轮回布局:

.IF, .ELSE, .ELSEIF, .ENDIF
.REPEAT, .UNTIL
.WHILE, .ENDW, .BREAK
.CONTINUE

If

假如你有利用编程语言的履历(你应该有),你大概已经看到了一些像if/else的布局:

.IF eax==1
;eax便是1
.ELSEIF eax=3
; eax便是3
.ELSE
; eax既不是1也不是3
.ENDIF

这种布局很是有用。你不需要和一对跳转搅在一起了,只要一个.IF语句(也不要健忘.IF和.ELSE之前的时期)。嵌套的if是答允的:

.IF eax==1
.IF ecx!=2
; eax= 1 并且 ecx 不是 2
.ENDIF
.ENDIF

#p#副标题#e#

但可以更简捷些:

.IF (eax==1 && ecx!=2)
; eax = 1 并且 ecx 不是 2
.ENDIF

#p#分页标题#e#

这些是你可以利用的操纵符:

==        便是 
!= 不便是
> 大于
< 小于
>= 大于便是
<= 小于便是
& 位测试
! 逻辑非
&& 逻辑与
|| 逻辑或
CARRY? carry bit set
OVERFLOW? overflow bit set
PARITY? parity bit set
SIGN? sign bit set
ZERO? zero bit set

Repeat

这个语句执行一块指令知道条件为真为止:

.REPEAT ;代码在此 .UNTIL eax==1

这块代码重复执行repeat和until之间的代码,知道eax=1。

While

While是repeat语句的反转。它在条件为真时执行代码块:

.WHILE eax==1
;代码在此
.ENDW

你可以利用.BREAK语句来跳出轮回

.WHILE edx==1
inc eax
.IF eax==7
.BREAK
.ENDIF
.ENDW

假如Eax==7,while轮回将遏制

continue指令使repeat或While跳过下面的代码块,从头执行轮回。

10.2-invoke

这是胜过tasm和nasm最大的利益。Invoke简化了进程和call的利用。

一般的名目:

push parameter3
push parameter2
push parameter1
call procedure

#p#副标题#e#

Invoke 名目:

invoke procedure, parameter1, parameter2, parameter3

汇编后的代码是一摸一样的,但invoke名目更简朴并且更靠得住。对一个进程利用invoke,你要这样界说prototype:

PROTO STDCALL testproc:DWORD, :DWORD, :DWORD

声明白名为testproc,需三个DWORD巨细的参数的进程。此刻,假如你这么做……

invoke testproc, 1, 2, 3, 4

……masm会给你一个testproc进程需要三个参数而不是四个的错误。Masm还会做范例查抄。它查抄参数是否为正确的范例(即巨细)

在一个invoke语句中,你可以用ADDR取代offset。这会使地点在汇编时是正确的。

进程这样界说:

testproc PROTO STDCALL :DWORD, :DWORD, :DWORD
.code
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
ret
testproc endp

这会建设一个名为testproc,带三个参数的进程。Prototype是用来挪用进程的。

testproc PROTO STDCALL :DWORD, :DWORD, :DWORD
.code
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
mov ecx, param1
mov edx, param2
mov eax, param3
add edx, eax
mul eax, ecx
ret
testproc endp

此刻,进程做了一下计较,(param1, param2, param3) = param1 * (param2 + param3).功效(返回值)存放在eax中,局部变量这样界说:

testproc proc param1:DWORD, param2:DWORD, param3:DWORD
LOCAL var1:DWORD
LOCAL var2:BYTE
mov ecx, param1
mov var2, cl
mov edx, param2
mov eax, param3
mov var1, eax
add edx, eax
mul eax, ecx
mov ebx, var1
.IF bl==var2
xor eax, eax
.ENDIF
ret
testproc endp

你不行以在进程外利用这些变量。它们储存在栈中并且当进程返回时移出。

10.3-宏

此刻不表明宏。大概在今后的教程中,但此刻它们对我们不重要。

11.0-Windows中的汇编基本

此刻你已经有了一些汇编语言的基本常识,你将要进修在Windows中奈何进修汇编。

11.1-API

Windows编程的基础在于Windows API,应用措施接口。这是由操纵系统提供的一套函数。每个Windows措施员都要用这些函数。这些函数在像kernel, user, gdi, shell, advapi等系统dll中。函数有两类:ANSI和Unicode。这和字符串的存储要领有关。Ansi中,每个字节代表一个标记(ASCI码),并用字节0代表一个字符串的竣事(null-terminated)。Unicode利用宽字符名目。它的每个字节用2个字节。这答允像中文等多字符的语言的利用。宽字符串由两个0字节竣事。Windows通过利用差异的函数名,同时支持Ansi和Unicode。

譬喻:

MessageBoxA(后缀A意为ansi)
MessageBoxW(后缀W意为宽字符-unicode)

我们只利用ansi型

11.2-导入dll

为了利用来自WindowsAPI的函数,你需要导入dll。这是由导入库(.lib)来完成的。这些库是必须的。因为它们使系统(Windows)能在内存的动态基地点处动态的载入dll。在Win32asm包中(win32asm.cjb.net)提供了大大都尺度dll的库。你可以用masm的includelib语句装载一个库。

译者注:留意,win32asm.cjb.net被中国电信封了ip。会见请利用署理。

Includelib C:\masm32\lib\kernel32.lib

这将载入库kernel32.lib。在例子中,用这种名目:

Includelib \masm32\lib\kernel32.lib

此刻你可以看到为什么汇编源文件要和masm在同一个区的原因了。你可以不窜改路径为正确的区就能在其他的电脑上编译你的措施。

但你不可是需要包括库。包括文件(.inc)也是必需的。这些可以用l2inc东西由库文件自动生成。包括文件这样装载:

include \masm32\include\kernel32.inc

在包括文件中,界说了dll中函数的原型(prototypes),因而你能利用invoke。

kernel32.inc:
...
MessageBoxA proto stdcall :DWORD, :DWORD, :DWORD, :DWORD
MessageBox textequ
...

#p#分页标题#e#

你能看到包括文件内有for Ansi的函数并且没有‘A’的函数名字界说为与真实函数名一样:你可以用MessageBox取代MessageBoxA利用。在你包括了库和包括文件后,你可以利用函数了:

invoke MessageBox, NULL, ADDR MsgText, ADDR MsgTitle, NULL

11.3-Windows包括文件

这里有一个出格的包括文件。大大都的时候统称为Windows.inc,个中包括了用于Windows API的所有常量和布局的界说。譬喻,动静框有差异的样式。函数的第四个参数是样式。NULL指的是MB_OK,它只有一个OK按钮。Windows包括文件有这些样式的界说:

> MB_OK equ 0
MB_OKCANCEL equ ...
MB_YESNO equ ...

因此你可以把这些名字当常数来用:

invoke MessageBox, NULL, ADDR MsgText, ADDR MsgTitle, MB_YESNO

例子将利用masm包中的包括文件:

include \masm32\include\windows.inc

#p#副标题#e#

11.4-框架

.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
include \masm32\include\windows.inc
.data
blahblah
.code
start:
blahblah
end start

这是Windows汇编源文件(.asm)的根基框架

.486

汇报汇编器应该生成486处理惩罚器(或更高)的伪代码。你可以利用.386,但大大都环境下用.486

.model flat, stdcall

利用平坦内存模式(在前面章节中接头了)并利用stdcall挪用习惯。它的意思是函数的参数从右往左压入(最后的参数最先压入)并且函数在竣事时本身清栈。这对付险些所有的Windows API函数和dll是尺度

option casemap:none

节制字符的映射为大写。为了Windows.inc文件能正常事情,这个应该为”none”

includelib

前面接头了

include

前面也接头了

.data

开始data部门(看前面章节)

.code

开始code部门(看前面章节)

start:
end start

暗示一个措施的开始的标签。它不长短得叫“start”。你可以利用任何和“end”语句后沟通的标签:

startofprog:
end startofprog

12.0-第一个措施

是建设你的第一个措施的时候了。本章中的指导将这样组织:

12.1-第一步

假如万事具备,你应该在你的masm同一个区上有一个win32(或win32asm)目次。为每个工程,你应该建设一个子目次。

在win32目次中建设一个名为“Firstprogram“的子目次。建设一个新的文本文件并重定名为“first.asm”。

#p#副标题#e#

12.2-第二步

在first.asm中输入一下代码:

.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\windows.inc

因为此刻,我们仅需要kernel32和user32两个dll。

12.3-第三步

我们将要建设著名的“Hello World”措施。要显示“hello World”字符串,我们要用动静对话框。动静对话框由MessageBox函数建设。你可以在《win32 措施员参考》(看第二章)中查找这个函数。这是书上说的:

MessageBox函数建设,显示并操纵动静对话框。动静对话框包括应用措施界说的动静和标题,加上任何预界说的图标与按钮的组合。

int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
Parameters
hWnd
Identifies the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.
lpText
Points to a null-terminated string containing the message to be displayed.
lpCaption
Points to a null-terminated string used for the dialog box title. If this parameter is NULL, the default title Error is used.
uType
Specifies a set of bit flags that determine the contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups of flags.
[--SNIP--]

#p#分页标题#e#

在这段文字后有所有常数和符号的列表(他们界说在windows.inc中)。因为它太长了,我没有在这里列出来。通过查察参考,你就知道MessageBox函数要4个参数:父窗口(owner),指向动静串的指针,指向标题串的指针和动静框的范例。

HWnd可以是Null。因为我们的措施没有窗口。

LpText必需是指向我们文本的指针。这仅仅意为参数是文内地址内存地点的offset。

LpCaption 是标题串的offset。

UType 是参考中表明的像MB_OK,MB_OKCANCEL,MB_ICONERROR等值的组合。

让我们先界说两个用于MessageBox的字符串:

在first.asm中插手:

.data
MsgText db "Hello world!",0
MsgTitle db "This is a messagebox",0

.data 指示data部门的开始。用db,字节直接被插入,并且字符串又只是字节的荟萃,data部门会在包括上面的字符串,附加上末了的0。MsgText装有第一个字符串的offset。MsgTitle有第二个字符串的offset。此刻我们可以利用函数:

invoke MessageBox, NULL, offset MsgText, offset MsgTitle, Null

但因为用的是invoke,你可以利用(更安详)ADDR取代offset:

invoke MessageBox, Null, ADDR MsgText, ADDR MsgTitle, Null

#p#副标题#e#

我们还没有看最后一个参数,但这不会有什么问题。因为MB_OK(有一个ok按钮的动静对话框的样式)便是0(NULL)。但你也可以利用其他的任何样式。Utype(第4个参数)的界说是:

指定一系列抉择对话框内容与行为的位符号。这个参数可以是下面符号组中符号的组合。

此刻以我们要一个有OK按钮与“information”图标的简朴动静对话框为例。MB_OK是OK按钮的样式,MB_ICONINFORMATION是information图标的样式。样式是用“or”操纵符连系的。这不是or伪代码。Masm会在汇编前处理惩罚or操纵。不消or,你可以用+号(加号)取代,但有时对层叠样式有问题(一个样式包括其他一些样式)。但在本例中你也可以用+号。

.code
start:
invoke MessageBox, NULL, ADDR MsgText, ADDR MsgTitle, MB_OK + MB_ICONINFORMATION
end start

把以上的代码插手到你的first.asm文件中。

我们还插手了一个start标签。假如你此刻汇编你的措施并运行它,它将显示一个动静对话框但很有大概在你点OK之后就瓦解了。这是因为措施没有竣事,而处理惩罚器开始执行MessageBox代码后的任何对象。Windows中措施是用ExitProcess函数竣事的:

VOID ExitProcess(
UINT uExitCode //对付所有线程的退出代码 );

我们可以把0用作退出码。

把你的代码改成这样:

.code
start:
invoke MessageBox, NULL, ADDR MsgText, ADDR MsgTitle, MB_OK + MB_ICONINFORMATION
invoke ExitProcess, NULL
end start

12.4-第4步

因此我们最终的措施是:

.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\windows.inc
.data
MsgText db "Hello world!",0
MsgTitle db "This is a messagebox",0
.code
start:
invoke MessageBox, NULL, ADDR MsgText, ADDR MsgTitle, MB_OK or MB_ICONINFORMATION
invoke ExitProcess, NULL
end start

12.5-第5步

此刻我们将从源代码发生可执行文件。

用一下内容新建一个文本文件并定名为make.bat:

@echo off
ml /c /coff first.asm
link /subsystem:windows first.obj
pause>nul

表明:

ml /c /coff first.asm

Ml是宏汇编器(masm)。Masm将从措施建设原始代码。参数的意思是: /c =汇编不链接(因为我们用link.exe来做这项事情) /coff = 发生COFF名目标object(工具)文件,这是Windows可执行文件的尺度名目。 first.asm = a汇编first.asm文件

link /subsystem:windows first.obj

链接器把object文件和所有导入的dll与库链接起来: /subsystem:windows = 建设Windows的可执行文件。 first.obj = 链接 first.obj

假如你把所有的工作都正确的完成了,并运行批处理惩罚文件。将发生first.exe。运行它,看看有什么功效。

#p#副标题#e#

13.0-Windows中的窗口

在本章中,我们将建设一个有窗口的措施

13.1-窗口

你大概已经猜到了Windows之所以称为Windows的原因了。在Windows中,有两种措施:GUI措施和节制台措施。节制台模式的措施看上去就像Dos措施,它们在一个似-dos的窗口中运行。你利用的大大都措施是GUI(图形用户界面)措施,它们有一个用于和用户交互的图形界面。这是由建设窗口来完成的。险些你在Windows中瞥见的每一件对象都是窗口。首先,你建设一个父窗口,然后是像编辑框,静态控件(文本标签-译者注),按钮等的自窗口(控件)。

13.2-窗口类

#p#分页标题#e#

每一个窗口都有名字。你为你的父窗口界说你自有的类。对付控件,你可以利用Windows的尺度类名(譬喻,“Edit”,“Static”,“Button”)

13.3-布局

你措施中的窗口类是用“RegisterClassEx“函数注册的。(RegisterClassEx是RegisterClass的扩展版本,后者已经不太利用了)这个函数的声明是:

ATOM RegisterClassEx(
CONST WNDLCASSEX *lpwcx//有类数据的布局之地点
);

lpwcx:指向WNDCLASSEX布局。在把它通报给函数之前,你必需用适当的类属性填写布局。

独一的参数是指向布局的指针。先来看看一些布局的根基常识:

一个布局是一些变量(数据)的荟萃。它用STRUCT界说:

SOMESTRUCTURE STRUCT
dword1 dd ?
dword2 dd ?
some_word dw ?
abyte db ?
anotherbyte db ?
SOMESTRUCTURE ENDS
(布局名不必然要大写)

你可以用问号把你的变量界说在未初始化data部门。此刻你可以按照界说建设一个布局:

Initialized
Initializedstructure SOMESTRUCTURE <100,200,10,'A',90h>
Uninitialized
UnInitializedstructure SOMESTRUCTURE <>

在第一个例子中,建设了一个新的布局(用初始化了的布局生存它的offset),并且布局的每一个元素用初始化数值填写了。第二个例子只是汇报masm为布局名分派内存,并且每个数据元素用0初始化。在建设了布局之后,你可以在代码中利用它:

mov eax, Initializedstructure.some_word
; eax此刻是 10
inc UnInitializedstructure.dword1
; 布局的dword1步增

布局是这样存在内存中的:

内存地点 内容

offset of Initializedstructure 100 (dword, 4 bytes)
offset of Initializedstructure + 4 200 (dword, 4 bytes)
offset of Initializedstructure + 8 10 (word, 2 bytes)
offset of Initializedstructure + 10 65 or 'A' (1 byte)
offset of Initializedstructure + 11 90h (1 byte)

#p#副标题#e#

12.3-WNDCLASSEX

此刻已经相识了足够多的布局常识,让我们处理惩罚RegisterClassEx吧。在《win32措施员参考》中,你可以查找WNDCLASSEX布局的界说。

typedef struct _WNDCLASSEX { //
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;

表明

cbSize
WNDCLASSEX布局体的巨细。用于Windows的认证。你可以用SIZEOF获得它的巨细: mov wc.cbsize, SIZEOF WNDCLASSEX

style

为类指定一个样式(假如窗口要有转动条,加上重画符号。等等)

lpfnWndProc

指向Windows Procedure的指针(本章后头有更多内容)

cbClsExtra

在Windows类布局后本配几多特别内存。对我们不重要

cbWndExtra

在Windows实例后分派几多特别内存。对我们也不重要

hInstance

你措施的实力句柄。你可以用GetMoudleHandle函数获得这个句柄

hIcon

窗口图标资源的句柄

hCursor

窗口光标资源的句柄

hbrBackground

用于填充配景的画刷句柄,或是尺度刷子范例中的一个,如 COLOR_WINDOW, COLOR_BTNFACE , COLOR_BACKGROUND.

lpszMenuName

指向一个指定菜单类名的零末了字符串

lpszClassName

指向一个指定窗口类名的零末了字符串

hIconSm

一个和窗口类关联的小图标句柄

在你的Win32文件夹中建设一个名为firstWindow的文件夹并在这个文件夹中建设一个名为window.asm的新文件,输入一下内容:

.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc

然后建设一个名为make.bat的.bat文件。把这些文本粘贴进去:

@echo off
ml /c /coff window.asm
link /subsystem:windows window.obj
pause>nul

以后刻开始,为了节减空间,仅显示小段的代码。你可以通过点来显示教程此处的全部代码。完整的代码在新窗口中显示。

译者注:为了利便,我又把这些放返来了。

13.4-注册类

此刻我们在名为WinMain的进程中注册类。该进程中完成窗口的初始化。

把这些插手你的汇编文件:

#p#分页标题#e#

WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD
.data?
hInstance dd ?
.code
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL
end start

这些代码通过GetModuleHandle获得模块句柄,并把模块句柄放入hInstance变量中。这个句柄在Windows API中频繁利用。然后它挪用WinMain进程。这不是一个API函数,而是一个我们将要界说的进程。原型是:WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD,因而是一个带4个参数的函数:

此刻把这些代码放在end start:前

WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
ret
WinMain endp

你基础就不需要用这个winmain进程,但这是一种十分普遍的处世化你的措施的要领。Visual C自动初始化这个函数的参数,但我们必需本身来做。此刻不要管hPrevInst和CmdLine。会合留意在hInst和CmdShow上。Hinst是实例句柄(=模块句柄),CmdShow是界说窗口该如何显示的符号。(你可以在API参考关于ShowWindows部门发明更多)

在前面代码中的"invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL"用正确的实例句柄和显示符号挪用这个函数。此刻我们可以在WinMain中写我们的初始化代码了。

WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
ret
WinMain endp

#p#副标题#e#

这有我们将在进程中要用的两个局部变量

.data
ClassName db "FirstWindowClass",0
.code
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
; now set all the structure members of the WNDCLASSEX structure wc:
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, ADDR wc
ret
WinMain endp

让我们来看看产生了什么:

mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL

初始化了布局的巨细(这是RegisterClassEx要求的)。配置类的样式为”CS_HREDRAW or CS_VREDRAW”,然后配置了窗口进程的offset。你在后头会知道什么是窗口进程,此刻你仅需要记着你需要WndProc进程的地点。该地点可以通过“offset WndProc”得到。Cb.ClsExtra和cb.WndExtra我们没有利用因而设它们为Null。

Push hInst
Pop wc.hInstance

Wc.hInstance设为WinMain的hInst参数。为什么我们不消:mov wc.hInstance, hInst?因为mov指令不答允从一个地点移到另一个地点。通过push/pop,值被压入栈,然后又弹入方针中。

mov wc.hbrBackground, COLOR_WINDOW
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName

类的配景致被设为COLOR_WINDOW,没有界说菜单(null)并且lpszClassName设为一个指向零末了的类名字符串:“FirstWindowClass”它应该是一个在你的措施中界说的独一名字。

invoke LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax

窗口需要一个图标。但又因为我们要一个指向图标的句柄,我们利用LoadIcon来载入图标并得到句柄。LoadIcon有两个参数:hInstance和lpIconName。HInstance是包括图标的可执行文件的模块句柄。LpIconName是一个指向图标资源和图标ID的字符串的指针。假如你用NULL为hInstance,你可以从一些尺度图表中选这一个(这却是是因为我们在这里还没有图标资源)hIconSm是小图标,你可以对它利用沟通的句柄。

invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax

对光标也一样。NULL作hInstance,并用一个尺度光标范例:IDC_ARROW,尺度Windows箭头型光标。

invoke RegisterClassEx, ADDR wc

此刻,最终用RegisterClassEx来注册类,通过一个指向WNDCLASSEX布局的指针作参数。

13.5-建设窗口

此刻,你已经注册了一个类,你可以利用它建设一个窗口:

#p#分页标题#e#

HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);

DwExstyle和dwStyle是两个抉择窗口样式的参数。

LpClassName 是一个指向你注册了的类名的指针。

LpWindowName 是你窗口的名字(假如有的话,这将成为你窗口的标题)

X, Y, nWidth, nHeight 抉择你窗口的位置和巨细

HMenu 是菜单窗口的句柄(在后头接头,此刻为空)

HInstance 是措施实例的句柄

LpPararm 是你能在你的措施中利用的扩展值

.data
AppName "FirstWindow",0
.code
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,400,300,NULL,NULL,\
hInst,NULL
mov hwnd, eax
invoke ShowWindow, hwnd, SW_SHOWNORMAL
invoke UpdateWindow, hwnd

(留意\使汇编器读下一行的时候仿佛还在同一行)

我们的代码将用我们方才注册的类名建设一个新的窗口。标题是“FirstWindow”(措施名,AppName),样式是WS_OVERLAPPEDWINDOW,这是一个建设有标题,系统菜单,可缩放边框和最大化/最小化按钮的窗口样式。CW_USERDEFAULT作为x和y的位置会使Windows为新窗口利用缺省位置。窗口的(初始)巨细是400×300象素。

函数的返回值是窗口句柄,HWND。它储存在局部变量hwnd中。然后窗口用ShowWindow显示。UpdateWindow确保窗口被画出。

13.6-动静轮回

窗口可以通过动静和你的措施以及其他窗口通讯。无论何时,一条动静被发送给指定的窗口。它的窗口进程都要被挪用。每个窗口都有一个动静轮回或动静泵(pump)。这是一个无止尽的查抄是否给有你的窗口的动静的轮回。并且假如有,把动静通报给dispatchMessage函数。这个函数会挪用你的窗口进程。动静轮回和窗口进程是两个完全差异的对象!!!

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG ;<<
........
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

这是动静轮回看上去的样子。.WHILE TRUE, .ENDW轮回到eax为0之前城市继承。假如它接到了WM_QUIT动静,GetMessage返回0,这将封锁窗口因而措施应该在岂论GetMessage返回0时退出。假如不是这样(0),动静被通报给TranslateMessage(这个函数把按键翻译为动静)并且动静被Windows用DispatchMessage函数解包。动静自己在一个动静轮回的构成部门MSG布局中(LOCAL msg: MSG被插手进程,增加了一个称为msg的局部动静布局)你可以在你的所有措施顶用这个动静轮回。

#p#副标题#e#

13.7-窗口进程

动静会被发送往窗口进程。一个窗口进程看上去老是这样:

WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
.code
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==XXXX
.ELSEIF eax==XXXX
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp

窗口进程老是有4个参数

hWnd 包括窗口句柄
uMsg 动静
wParam 动静的第一个参数(由动静界说)
lParam 动静的第二个参数(由动静界说)

窗口不处理惩罚的动静应该通报给DefWindowProc,它会处理惩罚这些。一个窗口进程的例子:

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp

这段代码在窗口初始化时显示措施名称。也要留意我插手了WM_DESTROY动静的处理惩罚。这条动静在窗口将要封锁的时候发送。措施要用PostQuitMessage作出回响。

此刻看看最终的代码:

#p#分页标题#e#

.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
.data?
hInstance dd ?
.data
ClassName db "FirstWindowClass",0
AppName db "FirstWindow",0
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL
invoke ExitProcess, NULL
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW-WS_SIZEBOX-WS_MAXIMIZEBOX,CW_USEDEFAULT,\
CW_USEDEFAULT,400,300,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
end start

 

    关键字:

天才代写-代写联系方式