汇编语言-寄存器
IA-32位通用寄存器
-
8个32位寄存器
EAX, EBX, ECX, EDX,
ESI, EDI , EBP, ESP
-
8个16位寄存器
AX,BX,CX,DX SI,DI,BP,SP
-
8个8位寄存器
AH,BH,CH,DH AL,BL,CL,D、L
汇编子程序
-
子程序举例
TestProc proc ;名为TestProc的子程序 local @loc1:dword, @loc2:word local @loc3:byte ;用local语句定义了3个变量 mov eax, @loc1 ;对应类型进行存储,然后返回 mov ax, @loc2 mov al, @loc3 ret TestProc endp
反汇编后: :00401000 55 push ebp :00401001 8BEC mov ebp, esp :00401003 83C4F8 add esp, FFFFFFF8 :00401006 8B45FC mov eax, dword ptr [ebp-04] :00401009 668B45FA mov ax, word ptr [ebp-06] :0040100D 8A45F9 mov al, byte ptr [ebp-07] :00401010 C9 leave :00401011 C3 ret
-
子程序详解
子程序定义:
子程序名 proc loacl 局部变量; ret 子程序名 endp
子程序汇编解释:
:00401000 55 push ebp :00401001 8BEC mov ebp, esp :00401003 83C4F8 add esp, FFFFFFF8 :00401010 C9 leave
汇编多出来这几句,
当调用者执行了call TestProc指令后,CPU把返回的地址(当前地址)压入堆栈,再转移(jmp)到子程序执行。
esp在程序的执行过程中可能随时用到,不可能用esp来随时存取局部变量,ebp寄存器是以堆栈段为默认数据段的,所以,可以用ebp做指针指向堆栈替代esp
于是,在初始化前,先用一句push ebp指令把原来的ebp保存起来,然后把esp的值放到ebp中。
由于上面的例子子程序,申请了7个字节的空间,而堆栈是向下增长的。所以要在esp中加一个负值,FFFFFFF8就是-8。
因为在80386处理器中,以dword (32位)为界对齐时存取内存速度最快,所以MASM宁可浪费一个字节,使用8个字节的空间,而不使用7个字节。
leave命令相当于 mov esp , ebp pop ebp
-
局部变量的使用
注意: ebp寄存器是关键
因为它起到保存原始esp的作用,并随时用做存取局部变量的指针基址,所以在任何时刻,不要尝试把ebp用于别的用途,否则会带来意想不到的后果
- 局部变量定义的时候
局部变量在定义的时候,loacl指令知识单纯的留出空间,而没有存放值,变量里面是其他程序在堆栈执行后留下的垃圾(因为我们知道,腾出空间只是改变栈指针esp),所以对局部变量的值一定要初始化。
- 局部变量填0函数
RtlZeroMemory
汇编变量
-
变量的大小
-
汇编不会强制转换类型
在汇编中,要注意变量的大小
例如,以db方式定义一个缓冲区: szBuffer db 1024 dup (?) 然后 mov ax,szBuffer 编译器会报一个错: error A2070: invalid instruction operands 意思是无效的指令操作,为什么呢?因为szBuffer是用db定义的,而ax的 尺寸是一个word,等于两个字节,尺寸不符合。
-
汇编变量的使用–以不同的类型访问变量
.data bTest1 db 12h wTest2 dw 1234h dwTest3 dd 12345678h …… .code mov al, bTest1 mov ax, word ptr bTest1 mov eax, dword ptr bTest1
解释与注意;
在这里要注意的是,指定类型的参数访问并不会去检测长度是否溢出
在MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样编译器忽略语法上的长度检验,仅使用变量的地址。
使用的方法是: 类型 ptr 变量名
类型可以是byte, word, dword, fword, qword, real8和real10。汇编解释:
上面的程序片断,每一句执行后寄存器中的值是什么呢?
mov al, bTest1 这一句很显然使 al 等于 12h,下面的两句呢,ax 和 eax难道等于 0012h 和00000012h吗?
实际运行结果是 3412h 和 78123412h,为什么呢?;.data段中的变量 :00403000 12 34 12 78 56 34 12 ... │ │ │ │ │ └──→ dwTest3 │ └──────→ wTest2 └─────────→ bTest1 ;.code段中的代码 :00401000 A000304000 mov al, byte ptr [00403000] :00401005 66A100304000 mov ax, word ptr [00403000] :0040100B A100304000 mov eax, dword ptr [00403000]
刚才这个例子说明了汇编中用ptr强制覆盖变量长度的时候,实质上是只用了变量的地址而禁止编译器进行检验。
编译器并不会考虑定界的问题,程序员在使用的时候必须对内存中的数据排列有个全局概念,以免越界存取到意料之外的数据。 -
汇编
movzx
指令作用:
把bTest1的一个字节扩展到一个字或一个双字再放到ax 或 eax中,高位保持0而不是越界存取到其他的变量.
例子;
.data bTest1 db 12h wTest2 dw 1234h dwTest3 dd 12345678h .code movzx ax,bTest1 ; ax == 0012h movzx eax,bTest1 ; eax == 00000012h movzx eax,cl ; eax == 000000(cl) movzx eax,ax ; eax == 0000(ax)
-
变量的使用–变量的尺寸和数量
sizeof和lengthof伪指令
使用格式:
sizeof 变量名、数据类型或数据结构名 lengthof 变量名
他们的区别是:
sizeof 伪指令可以取得变量、数据类型或数据结构以字节为单位的长度,
然而 lengthof 则可以取得变量中数据的项数。
例子:
.data stWndClass WNDCLASS <> szHello db ‘Hello,world!’,0 dwTest dd 1,2,3,4 …… .code …… mov eax, sizeof stWndClass mov ebx, sizeof WNDCLASS mov ecx, sizeof szHello mov edx, sizeof dword mov esi, sizeof dwTest
执行结果;
执行后eax 的值是stWndClass 结构的长度:40 ebx同样是:40 ecx的值是Hello,world! 字符串的长度加上一个字节的0结束符:13 edx的值是一个双字的长度:4 esi等于4个双字的长度:16 假设 如果把所有的sizeof 换成 lengthof,那么eax会等于1,因为只定义了1项WNDCLASS 而ecx同样等于13 esi则等于4、 lenghof WNDCLASST 和 lengthof dword 是非法的用法,编译程序会报错。
注意:
sizeof 和lengthof 的数值是编译时产生的,由编译器传递到指令中去,上边的指令最后产生的代码就是: mov eax, 40 mov ebx, 40 mov ecx, 13 mov edx, 4 mov esi, 16
- MASM 中的变量定义只认一行
如果为了把Hello和World分两行定义,szHello是这样定义的: szHello db ‘Hello’, odh, oah db ‘World’, 0 那么 sizeof szHello 是多少呢? 注意!是7,而不是13。MASM 中的变量定义只认一行,后一行db ‘World’, 0 实际上是另一个没有名称的数据定义,编译器认为sizeof szHello 是第一行字符的数量
虽然把 szHello 的地址当参数传给 MessageBox 等函数显示时会把两行都显示出来,但严格地说这是越界使用变量。
虽然在实际的应用中这样定义长字符串的用法很普遍,因为如果要显示一屏幕帮助,一行是不够的。
但要注意的是:要用到这种字符串的长度时,千万不要用 sizeof 去表示,最好是在程序中用lstrlen 函数去计算。