注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

★ ★ ★ 卡多 - K.D

DELPHI

 
 
 

日志

 
 

第二课 经典汇编教程Win32Asm教程  

2011-01-02 12:35:38|  分类: ‖ 杂文 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
7.1-标志寄存器 
标志寄存器有一套标志。它们设不设置取决于计算或其他时间。我不会讨论它们的全部。只拣几个重要的说: 
ZF(零标志) 当计算结果是零时该标志被设置(compare实际上是只设置标志不保存结构的减法) 
SF(符号标志) 结果为负就设置 
CF(carry标志) Carry标志中存放计算后最右的位。 
OF(溢出标志) 标明一个溢出了的计算。如,结构和目标不匹配。 
还有更多的标志(Parity, Auxiliary, Trap, Interrupt, Direction, IOPL, Nested Task, Resume, & Virt l Mode)但因为我们不用它们,所以我不解释。 
7.2-跳转系列 
有一整套的条件跳转,而且它们跳转与否均取决于标志的状态。但由于大部分跳转指令有明白的名字,你甚至无需知道哪个标志要设置,例如:“如果大于等于就跳转”(jge)和“符号标志=溢出标志”一样,而“如果零就跳转”和“如果零标志=1就跳转”一样。 
在下表中,“意思”指的是什么样的计算结果该跳转。“如果大于就跳转”意为: 
cmp x, y 
jmp 如果 x 比 y大 
伪代码 意思 条件 
JA Jump if above CF=0 & ZF=0 
JAE Jump if above or eq l CF=0 
JB Jump if below CF=1 
JBE Jump if below or eq l CF=1 or ZF=1 
JC Jump if carry CF=1 
JCXZ Jump if CX=0 register CX=0 
JE (is the same as JZ) Jump if eq l ZF=1 
JG Jump if greater (signed) ZF=0 & SF=OF 
JGE Jump if greater or eq l (signed) SF=OF 
JL Jump if less (signed) SF != OF 
JLE Jump if less or eq l (signed) ZF=1 or SF!=OF 
JMP Unconditional Jump - 
JNA Jump if not above CF=1 or ZF=1 
JNAE Jump if not above or eq l CF=1 
JNB Jump if not below CF=0 
JNBE Jump if not below or eq l CF=1 & ZF=0 
JNC Jump if not carry CF=0 
JNE Jump if not eq l ZF=0 
JNG Jump if not greater (signed) ZF=1 or SF!=OF 
JNGE Jump if not greater or eq l (signed) SF!=OF 
JNL Jump if not less (signed) SF=OF 
JNLE Jump if not less or eq l (signed) ZF=0 & SF=OF 
JNO Jump if not overflow (signed) OF=0 
JNP Jump if no parity PF=0 
JNS Jump if not signed (signed) SF=0 
JNZ Jump if not zero ZF=0 
JO Jump if overflow (signed) OF=1 
JP Jump if parity PF=1 
JPE Jump if parity even PF=1 
JPO Jump if paity odd PF=0 
JS Jump if signed (signed) SF=1 
JZ Jump if zero ZF=1 
所有的跳转指令需要一个参数:要跳往的offset。 
8.0-关于数的一些事情 
在大多数的编程语言中使用整数还是浮点数只取决于变量的声明。在汇编语言中,完全的不同。浮点数的计算是由特别的伪代码和FPU协处理器(浮点单 元)完成的。浮点指令将会在后面讨论。先来看看一些关于整数的事情。在c语言中有signed(有符号)整数和unsigned(无符号)整数。 Signed是意为数有符号(+或-)。Unsigned总是正。找出下表中的不同(再一次的,这是一个byte的例子,它在其他大小时也同样工 作)。 
值 00 01 02 03 ... 7F 80 ... FC FD FE FF 
无符号意义 00 01 02 03 ... 7F 80 ... FC FD FE FF 
有符号意义 00 01 02 03 ... 7F -80 ... -04 -03 -02 -01 
因此,在有符号数中,一个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跳转 
关于栈的伪代码   
在我讲栈的伪代码之前,我会先解释什么是栈。栈是内存的一个地方,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(最先压入,最后弹出) 
为了说明再压栈和弹栈时,内存中发生了什么,看下图: 
Offset 1203 1204 1205 1206 1207 1208 1209 120A 120B 
值 00 00 00 00 00 00 00 00 00 
ESP 
(栈在这里是初始化为0,但实际上并不是这样。ESP表示ESP指向的offset) 
mov ax, 4560h 
push ax 
Offset 1203 1204 1205 1206 1207 1208 1209 120A 120B 
值 00 00 60 45 00 00 00 00 00 
ESP 
mov cx, FFFFh 
push cx 
Offset 1203 1204 1205 1206 1207 1208 1209 120A 120B 
值 FF FF 60 45 00 00 00 00 00 
ESP 
pop edx 
Offset 1203 1204 1205 1206 1207 1208 1209 120A 120B 
值 FF FF 60 45 00 00 00 00 00 
ESP 
edx现在是 4560FFFFh 了. 
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 
.CONTIN 
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 
但可以更简洁些: 
.IF (eax==1 && ecx!=2) 
; eax = 1 而且 ecx 不是 2 
.ENDIF 
这些是你可以使用的操作符: 
== 等于 
!= 不等于 
> 大于 
< 小于 
>= 大于等于 
<= 小于等于 
& 位测试 
! 逻辑非 
&& 逻辑与 
|| 逻辑或 
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循环将停止 
contin指令使repeat或While跳过下面的代码块,重新执行循环。 
10.2-invoke 
这是胜过tasm和nasm最大的优点。Invoke简化了过程和call的使用。 
一般的格式: 
push parameter3 
push parameter2 
push parameter1 
call procedure 
Invoke 格式: 
invoke procedure, parameter1, parameter2, parameter3 
汇编后的代码是一摸一样的,但invoke格式更简单而且更可靠。对一个过程使用invoke,你要这样定义prototype: 
PROTO STDCALL testprocWORD, WORD, WORD 
声明了名为testproc,需三个DWORD大小的参数的过程。现在,如果你这么做…… 
invoke testproc, 1, 2, 3, 4 
……masm会给你一个testproc过程需要三个参数而不是四个的错误。Masm还会做类型检查。它检查参数是否为正确的类型(即大小) 
在一个invoke语句中,你可以用ADDR代替offset。这会使地址在汇编时是正确的。 
过程这样定义: 
testproc PROTO STDCALL WORD, WORD, WORD 
.code 
testproc proc param1WORD, param2WORD, param3WORD 
ret 
testproc endp 
这会创建一个名为testproc,带三个参数的过程。Prototype是用来调用过程的。 
testproc PROTO STDCALL WORD, WORD, WORD 
.code 
testproc proc param1WORD, param2WORD, param3WORD 
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 param1WORD, param2WORD, param3WORD 
LOCAL var1WORD 
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的incl?lib语句装载一个库。 
译者注:注意,win32asm.cjb.net被中国电信封了ip。访问请使用代理。 
Incl?lib C:\masm32\lib\kernel32.lib 
这将载入库kernel32.lib。在例子中,用这种格式: 
Incl?lib \masm32\lib\kernel32.lib 
现在你可以看到为什么汇编源文件要和masm在同一个区的原因了。你可以不改动路径为正确的区就能在其他的电脑上编译你的程序。 
但你不只是需要包含库。包含文件(.inc)也是必须的。这些可以用l2inc工具由库文件自动生成。包含文件这样装载: 
incl? \masm32\incl?\kernel32.inc 
在包含文件中,定义了dll中函数的原型(prototypes),因而你能使用invoke。 
kernel32.inc: 
... 
MessageBoxA proto stdcall WORD, WORD, WORD, WORD 
MessageBox textequ 
... 
你能看到包含文件内有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包中的包含文件: 
incl? \masm32\incl?\windows.inc 
11.4-框架 
.486 
.model flat, stdcall 
option casemap:none 
incl?lib \masm32\lib\kernel32.lib 
incl?lib \masm32\lib\user32.lib 
incl?lib \masm32\lib\gdi32.lib 
incl? \masm32\incl?\kernel32.inc 
incl? \masm32\incl?\user32.inc 
incl? \masm32\incl?\gdi32.inc 
incl? \masm32\incl?\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” 
incl?lib 
前面讨论了 
incl? 
前面也讨论了 
.data 
开始data部分(看前面章节) 
.code 
开始code部分(看前面章节) 
start: 
end start 
表示一个程序的开始的标签。它不是非得叫“start”。你可以使用任何和“end”语句后相同的标签: 
startofprog: 
end startofprog
  评论这张
 
阅读(164)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017