今天想学习win32汇编编程,在taowen博客中阅读了其翻译的Win32Asm教程,摘抄了一些我自己需要学习的要点。原教程的博客地址是:http://www.cnblogs.com/taowen/articles/11237.html 。很高兴网上有这么多爱学习的朋友们奉献资料。
在Windows中编程,你不再需要了解Dos中断(interrupt)和端口(port)In/Out函数。在Windows中,WindowsAPI提供了你可在你的程序中使用的标准函数
需要参考一些API函数的用法,现在有极好的MSDN供选择
eax (ax/ah/al) 加法器
ebx (bx/bh/bl) 基(base)
ecx (cx/ch/cl) 计数器
edx (dx/dh/dl) 数据
段寄存器定义了哪一段内存被使用。你可能在win32asm中用不着它们,因为windows有一个平坦(flat)的内存系统。在windows中,段有4GB的大小,所以你在Windows中不需要段。段总是16位寄存器。
CS 代码段
DS 数据段
SS 栈段
ES 扩展段
FS (only 286+) 全功能段
GS (only 386+) 全功能段
你可以把指针寄存器当作全功能寄存器来使用(除了eip),只要你保存并恢复它们的原始值。指针寄存器之所以这么叫是因为它们经常被用来存储内存地址。一些伪代码(movb,scasb等)也要用它们。
esi (si) 源索引
edi (di) 目标索引
eip (ip) 指令指针
2个栈寄存器:esp和ebp。esp装有内存中当前栈的位置。Ebp在函数中被用成指向局部变量的指针。
在运行于Dos和Win3.xx的16位程序中,内存被分成许多个段。这些段的大小为64kb。最多有65536个段。为了指向段中的位置,需要使用offset。一个offset是段内部的一个位置。每个段最多有65536个offset。在32位Windows(95及以上),你仍然有段,但不用管他们了。因为它们不再是64kb,而是4GB。你如果尝试着改变段寄存器中的一个,windows甚至会崩溃。这称为平坦(flat)内存模式。只有offset,而且是32位的,因而范围从0到4,294,967,295。内存中的每一个地址都是用offset表示的。
32位的值也就是4字节大小。
十六进制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 (记住反序), 因为存储在内存中的值使用了little endian格式。这意味着越靠右的字节位数越高:字节顺序被反转了
大小有时不是必须的。
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。
汇编源文件被分成了几个部分。这些部分是code,data,未初始化data,constants,resource和relocations,资源部分是资源文件创建的。
在你的源文件(*.asm)中,你可以用部分标识符定义各部分:
.code;代码部分由此开始
.data;数据部分由此开始
.data?;未初始化数据部分由此开始
.const;常量部分由此开始
可执行文件(*.exe,*.dll和其他)是(在win32中)可移植执行格式(PE)。部分(Sections)的一些属性定义在PE头中:
Section名,RVA,offset,原始大小,虚拟大小和标志。Rva(相对虚拟地址)是将要装入的section部分的相对内存地址。这里相对的意思是相对于程序载入的基地址。这个地址也在PE头中,但可以由PE-loader改变(使用relocation部分)。Offset是初始化数据所在的exe文件本身的原始offset。虚拟大小是程序在内存中将达到的大小。标志是读/写/可执行等。
你和处理器都不能看出一个值是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大。
只要记住这点:一个数字是有符号还是无符号取决于你怎样对待这个数。
Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器。结果本身不会保存。Test用来测试一个位,例如寄存器:
test eax, 100b;b后缀意为二进制
jnz bitset
如果eax右数第三个位被设置了,jnz将会跳转。Test的一个非常普遍的用法是用来测试一方寄存器是否为空:
test ecx, ecx
jz somewhere
如果ecx为零,Jz跳转
栈是内存的一个地方,esp为指向栈的指针。栈是用来保存临时数值的地方,有两个指令来放入一个指和再把它取出来:push和pop。Push把一个指压入栈。Pop再把它弹出来。最后一个放入的值最先出来。一个值被放入栈中,栈指针步减,当它移出来的时候,栈指针步增。栈顶总是栈中地址最小的位置。
Call跳转到某段代码而且一发现RET指令就返回。你可以把它们看成在其他编程语言中的函数或子程序。call把EIP(指向将要执行指令的指针)压入栈,而ret指令在它返回的时候把它弹出来。你也可以给一个call指定的参数。这是由压栈来完成的:
push something
push something2
call procedure
在一个调用的内部,参数从栈中读出并使用。注意,只在过程中需要的局部变量也储存在栈中。只要记住你可以写过程,而且它们可以由参数。一个重要的地方:
eax几乎总是用来装一个过程的返回值。
对于windows函数也是如此。但然,你可以在你的过程使用其他的寄存器,但这是标准。