首页
论坛
专栏
课程

[原创]windows下32位汇编语言学习笔记 第三章 使用MASM

2009-5-3 02:48 10624

[原创]windows下32位汇编语言学习笔记 第三章 使用MASM

2009-5-3 02:48
10624
windows下32位汇编语言学习笔记 第三章  使用MASM

本章讲述的是masm 汇编的程序结构,基本语法,定义等,本章这些内容只是汇编指令里比较常用的,在下面的章节将要用到的指令。实际上汇编指令远不止这些。感兴趣可以参照其他的汇编书籍了解一下。不过对于本书下面的章节来说,这些指令基本上够用了。


Win32汇编程序的基本结构

从例子可以看出来,Win32汇编的结构很简单,下面简单分析下。

模式定义
.386
.model falt,stdcall
option casemap:none

这个地方书上已经将的很清楚了。关于.386 .486 .586 .686 之类的指令集,我没找到资料,试验了一下写成.686也没什么问题。

include includelib语句
include windows.inc
includelib kernel32.lib

这里的include 和C语言里的include 头文件一个道理,都是导入预先声明好的函数,包括定义好的各种结构。

includelib 就是指定连接的时候告诉连接器从那个lib里找你通过include引入并使用的函数,win32API都是以动态链接库的形式提供的,所以这里就需要对你使用的winAPI包含在那个dll里做到心中有数,不知道的就查msdn,每个API说明后面都有这个API包含在那个头文件中,比如:
Header: Declared in Winuser.h; include Windows.h.
winAPI是C语言写的,所以头文件都是.h的,汇编的头文件声明是.inc的,打开kernel32.inc 找找Exitprocess 的申明 ExitProcess PROTO :DWORD
你也可以不用预定义的.inc头文件,自己定义。

如果你使用了函数确没有包含对应的.lib,比如使用了ExitProcess函数,没有includelib kernel32.lib,连接时就会报错:
error LNK2001: 无法解析的外部符号 __imp__ExitProcess@4
这个外部符号名就是你要调用的函数,名字很诡异吧,这里先有个了解,讲到调用约定的时候再详细说明。


段定义,程序结束和入口.
.data ;全局变量段
szTest    db '消息窗内容',0
szCaption db '消息窗标题',0
.code  代码段
start:
        invoke        MessageBox,NULL,offset szText,offset szCaption,MB_OK
end start

这里的段就是PE格式里的Section (区块),一个PE文件(可执行)最少包含2个区块,代码块和数据块,区块的名只是方面记忆,对于系统来说是无关紧要的。区块是按内存页对齐的(0x1000 4K)。区块的类型很多,比如.idata包含导入表,.rsrc,包含资源文件等等。但是,任何时候不要通过区块名字来定位区块,从PE结构的IMAGE_SECTION_HEADER来定位区块才是正确的做法,因为区块名字是可以任意的。关于PE结构的说明,我见过的最详细的就是“加密与解密第三版”第10章的介绍,大家可以去看看。

编译本章的hello.asm 用OD打开exe可以看见有3个区段,.text .rdata 和 .data ,.text 就是代码里的.code段,.data就是代码里的.data段,.rdata没人定义怎么自己冒出来了,其实这就是hello.exe的导入表,因为程序里用到了2个外部dll函数,MessageBox,ExitProcess。这个段就是编译器自动生成的。至于为什么叫.rdata,刚才说了,名字不是重要的,只是帮助记忆,导入表区段有的名字可能就是.idata。

另外还需要注意,程序的入口必须自己指定,汇编里没有Main这样的程序执行起点,这点别忘了。

变量名,变量,数据结构
这个地方没啥好说的,多看,多写,慢慢就习惯了,值得注意的地方就是,变量的命名方式一定要按照后面代码风格所说,按照匈牙利表示法来命名,从一开始就养成一个好习惯。

子程序,函数的定义和使用

调用约定和名称修饰符

除了书上将的_cdecl,_stdcall等,还有一种c++builder里常用的_fastcall调用,__fastcall调用也是被调用的函数负责清栈,参数的传递规则是,从左边开始不大于4字节的参数分别用edx,ecx传递,其他参数遵循从又右到左的顺序通过堆栈传递。

c c++在内部是通过函数修饰符来识别函数的,由编译器在编译时生成函数名称修饰符,而且,不同的调用约定不同的语言生成的修饰符定义名称不同,所以有必要了解一下函数的名称修饰符。

例子函数:int max(int,int);

对于C语言
_cdecl调用
名称修饰符是在函数前加一个下划线:_max
_fastcall调用
名称修饰符在函数前加一个@后面加一个@紧跟参数字节数:@max@8
_stdcall调用
名称修饰符在函数前加一个_后面加一个@紧跟参数字节数:_max@8

对于C++语言,不管任何调用约定,描述符都以?开头后边更函数名,然后是根据参数表查出的返回值类型,然后是参数类型,最有以@Z结束
?+函数名+调用规则名+返回类型+参数类型(从左到右)+@Z

其中调用规则名表:_cdecl:@@YA,_stdcall:@@YG,_fastcall:YI

标示符:参数类型
X:void,D:char,E:unsigned char,F:short,H:int,I:unsigned int,J:long,K:unsigned long,M:float,N:double,_N:bool,U:Struct
指针:PA,const指针:PB
对于max函数修饰名称就是:?max@@Y?HHH@Z。这里给了个问号,意思就是不同的调用规则就更具调用规则表变化,其他不变。
很明显,C++的修饰更为详细。
现在回过头看看刚才的错误提示:error LNK2001: 无法解析的外部符号 __imp__ExitProcess@4
__imp_ 这个是代表函数ExitProcess是从外部导入的,后面的_ExitProcess@4很明显参数是四字节的和ExitProcess(UNIT uExitCode)相符

实际上对于C++的类成员函数,描述符的规则又有不同,但是,如果你写的DLL动态链接库使用自定义类,估计没人会用的,使用类了就不能通用了。


MASM的优化

都知道汇编效率高,但是MASM编译出的EXE真的就是最佳优化的么?让我们看看本章中的hello.exe 用OD反汇编看看是不是这样。
反汇编内容:
00011000 >/$  6A 00         PUSH    0                                ; /Style = MB_OK|MB_APPLMODAL
00011002  |.  68 00300100   PUSH    Hello.00013000                   ; |Title = "A MessageBox !"
00011007  |.  68 0F300100   PUSH    Hello.0001300F                   ; |Text = "Hello, World !"
0001100C  |.  6A 00         PUSH    0                                ; |hOwner = NULL
0001100E  |.  E8 07000000   CALL    <JMP.&user32.MessageBoxA>        ; \MessageBoxA
00011013  |.  6A 00         PUSH    0                                ; /ExitCode = 0
00011015  \.  E8 06000000   CALL    <JMP.&kernel32.ExitProcess>      ; \ExitProcess
0001101A   $- FF25 08200100 JMP     NEAR DWORD PTR DS:[<&user32.Mess>;  user32.MessageBoxA
00011020   .- FF25 00200100 JMP     NEAR DWORD PTR DS:[<&kernel32.Ex>;  kernel32.ExitProcess

看看那2个CALL,一个调用MessageBoxA,一个调用ExitProcess,这个JMP产生了额外的代码,并且增加执行时间,产生这样的代码是因为编译器不知道你调用的函数是从外部导入的。如果编译器预先知道这个函数是从外部引入的,编译器就会把CALL后面的地址直接指向,PE文件的IAT(import_address_table)输入表中的函数地址,当程序运行时由系统加载器更新IAT表(如果需要的话),这样就调用了函数在DLL中的正确地址,避免了这种低效能的调用方式。

高级语言,比如C语言在引入外部DLL函数时,再dll头文件里对于每一个函数都有一个描述 __declspec(dllimport),这就是告诉编译器,这个函数是从外部引入的,从而提高空间和时间效率。
看看C写的,功能呢个同样的代码,编译后的反汇编内容:

00401000  /$  6A 00         PUSH    0                                               ; /Style = MB_OK|MB_APPLMODAL
00401002  |.  68 00304000   PUSH    HelloMsg.00403000                               ; |Title = "HelloMsg"
00401007  |.  68 0C304000   PUSH    HelloMsg.0040300C                               ; |Text = "Hello, Windows 98!"
0040100C  |.  6A 00         PUSH    0                                               ; |hOwner = NULL
0040100E  |.  FF15 AC204000 CALL    NEAR DWORD PTR DS:[<&USER32.MessageBoxA>]       ; \MessageBoxA
00401014  |.  33C0          XOR     EAX, EAX
00401016  \.  C2 1000       RETN    10

这个MessageBoxA的CALL才是效率最高的call!

但是悲剧的是在masm里我们无法用任何描述告诉编译器,当前使用的函数是从外部引入的。结果就是使用效率最高的语言确产生了效率最低的外部函数调用...


有没有办法解决,确实有,我google了一下,发现了一段代码。

比如我们调用ExitProcess函数,可以预先这样写

PROTO@4 TYPEDEF PROTO STDCALL :DWORD                   ;定义一个新的类型proto@4
EXTERNDEF STDCALL _imp__ExitProcess@4:PTR PROTO@4        ;定义一个外部变量,类型为上面定义的类型                
ExitProcess EQU <_imp__ExitProcess@4>                  ;定义一个符号ExitProcess

把上面3行代码加到 模式定义后面,注释掉include 'kernel32.inc',重新编译,现在看反汇编的内容:

00011000 >/$  6A 00         PUSH    0                                ; /Style = MB_OK|MB_APPLMODAL
00011002  |.  68 00300100   PUSH    Hello.00013000                   ; |Title = "A MessageBox !"
00011007  |.  68 0F300100   PUSH    Hello.0001300F                   ; |Text = "Hello, World !"
0001100C  |.  6A 00         PUSH    0                                ; |hOwner = NULL
0001100E  |.  E8 09000000   CALL    <JMP.&user32.MessageBoxA>        ; \MessageBoxA
00011013  |.  6A 00         PUSH    0                                ; /ExitCode = 0
00011015  \.  FF15 00200100 CALL    NEAR DWORD PTR DS:[<&kernel32.Ex>; \ExitProcess
0001101B      CC            INT3
0001101C   $- FF25 08200100 JMP     NEAR DWORD PTR DS:[<&user32.Mess>;  user32.MessageBoxA

看见没ExitProcess的调用汇编代码成了最佳调用了。

新加的着3行代码,我也是网上抄下来的,请高手看见的帮忙解释下。

我还发现了一个网站 http://www.japheth.de/JWasm.html 这个网站提供了一套自己修改过的.inc文件,而且使用整个代码里只需要include 他们的windows.inc文件。
编译生成后就是优化了的call代码。

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .686
                .model flat,stdcall
                option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include        windows.inc
includelib        user32.lib
includelib        kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .data
szCaption        db        'A MessageBox !',0
szText                db        'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                .code
start:
                invoke        MessageBox,NULL,offset szText,offset szCaption,MB_OK
                invoke        ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                end        start

编译后的反汇编:
00401000 >/$  6A 00         PUSH    0                                ; /Style = MB_OK|MB_APPLMODAL
00401002  |.  68 00304000   PUSH    Hello.00403000                   ; |Title = "A MessageBox !"
00401007  |.  68 0F304000   PUSH    Hello.0040300F                   ; |Text = "Hello, World !"
0040100C  |.  6A 00         PUSH    0                                ; |hOwner = NULL
0040100E  |.  FF15 08204000 CALL    NEAR DWORD PTR DS:[<&user32.Mess>; \MessageBoxA
00401014  |.  6A 00         PUSH    0                                ; /ExitCode = 0
00401016  \.  FF15 00204000 CALL    NEAR DWORD PTR DS:[<&kernel32.Ex>; \ExitProcess

2个call完全优化了。

不过要注意,用他们这个inc,不能使用vs2008自带的ml否则编译就报错,可以用RadASM里自带的masm 编译。之需要把参数/I 指向下载的win32inc的include就可以了。具体功能怎么实现的,我是不理解,还得请高手们帮忙看看。


星期日, 五月 03, 2009

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (16)
stalker 8 2009-5-3 08:53
2
0
当你要调用n多api,而又想以这种最优的形式调用,像你这样就复杂了点。可以直接在inc文件中做手脚,一劳永逸。
菜鸟乱飞 2009-5-3 09:48
3
0
谢谢楼主,学习了
jasonnbfan 8 2009-5-3 11:46
4
0
stalker,能否指点下具体如何修改INC文件么
billstone 6 2009-5-3 20:39
5
0
看了一下inc文件,感觉挺复杂的
笨笨雄 14 2009-5-4 11:16
6
0
LZ开个导航帖吧,我帮你置顶,方便大家查看,要不帖子沉太快了
蚊香 3 2009-5-4 11:34
7
0
支持导航...查阅方便
sando 2009-5-4 11:56
8
0
学习了。谢谢!
billstone 6 2009-5-4 22:44
9
0
等待第四章。。。
glgxyyxh 2009-5-4 22:59
10
0
好东西,正在学习中。。
billstone 6 2009-5-4 23:04
11
0
版主干脆弄个列表,置顶吧
壹无所有 2009-5-5 00:29
12
0
哪里有前两章??
xiaoak 2009-5-6 19:53
13
0
多谢了..
学习了
guicomeon 2009-5-7 12:03
14
0
很强悍~ 我得努力多学习才行。
glgxyyxh 2009-5-7 22:05
15
0
LZ能不能出些例子。一些基本操作的实现。如复制字符串,追加字符串,把C中那些字符串操作都用MASM32写出来。那对菜鸟们真是受益啊
强烈支持!!!!!
mengqiu 2009-9-12 18:37
16
0
跟着学习中……
rerefrancd 1 2009-10-9 11:46
17
0
边学边玩,不是好的学习.
游客
登录 | 注册 方可回帖
返回