汇编语言
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
main PROC 00000020 00000025 ESP
2007年10月
call SumOf mov ebx,eax 00000025 EIP 00000040
汇编语言
add eax,ecx ret SumOf ENDP ESP 00000025 EIP 00000025
11
局部标号和全局标号 默认情况下,代码标号(以单个冒号结尾)有一个局部域,使 得它对其所在过程内的语句可见,这阻止了跳转或循环语句 转移到当前过程之外的标号。在极少数情况下,如果必须将 控制转移到当前过程之外的标号处,标号必须被声明为全局 的。声明全局标号,要在标号后跟两个冒号。例如:
theSum DWORD ? .code main PROC mov eax,10000h mov ebx,20000h mov ecx,30000h call SumOf mov theSum,eax exit main ENDP 汇编语言
;参数 ;参数 ;参数 ;EAX=(EAX+EBX+ECX) ;保存和 ;启动过程结束来自百度文库
压栈之后
00001000
00000006
000000A6
00000FFC
00000FF8 00000FF4 00000FF0
ESP
偏移
00001000 00000FFC 00000FF8 00000FF4
出栈之前 00000006 000000A5
偏移
00001000 00000FFC 00000FF8
PUSHF POPF
2007年10月
汇编语言
8
PUSHAD,PUSHA,POPAD和POPA指令 PUSHAD指令在堆栈上按下列顺序压入所有的32位通用寄存器 :EAX,ECX,EDX,EBX,ESP的原始值,EBP,ESI和EDI; POPAD指令以相反的顺序从堆栈中弹出这些通用寄存器。 与之类似,80286处理器引入的PUSHA指令以同样的顺序压入 所有的16位寄存器(AX,CX,DX,BX,SP的原始值,BP,SI和 DI);POPA指令则以相反顺序弹出这些寄存器。
假设合适的整数在调用过程之前已经存放在EAX,EBX和ECX寄存器中 了,函数将在EAX中返回相加的和: SumOf PROC add eax,ebx add eax,ecx ret SumOf ENDP
2007年10月
汇编语言
10
CALL和RET指令 CALL指令指示处理器在新的内存地址执行指令,以实现对过 程的调用。在过程中使用RET(return from procedure)指令 使处理器返回到程序中调用过程的地方继续执行。 从底层细节角度来讲,CALL指令将返回地址压入堆栈并将被 调用过程的地址拷贝到指令指针寄存器中;当程序返回时, RET指令从堆栈中弹出地址并送到指令指针寄存器中。CPU总 是执行指令指针寄存器EIP(在16位模式下是IP)所指向的内存 地址处的指令。 SumOf PROC add eax,ebx 调用和返回的例子 00000040
出栈之后 00000006 000000A5 00000001
00000001
0000002
ESP
ESP
汇编语言
00000FF4 00000FF0
5
00000FF0
2007年10月
在程序中堆栈有几种重要的用途: 寄存器在用做多种用途的时候,堆栈可方便地作为其临时保 存区域,在寄存器使用完毕之后,可恢复其原始值。 CALL指令执行的时候,CPU用堆栈保存当前过程的返回地址。 调用过程的时候,可以通过堆栈传递输入值(参数)。 过程内部的局部变量在堆栈上创建,过程结束时,这些变量 被丢弃。
main PROC jmp L2 L1:: ;错误! ;全局标号
exit
main ENDP sub PROC L2: jmp L1 ret sub ENDP
2007年10月 汇编语言 12
;局部标号 ;正确
向过程传递寄存器参数 如果过程执行某些诸如整数数组求和之类的标准操作,那么 在过程之内引用特定的变量名并不是什么好主意。如果准备 那么做的话,该过程就不可能用于其他数组了。 一个较好的方法是向过程传递数组的偏移,再传递一个整数 来表示数组元素的数目。我们称这些为参数(arguments)或输 入参数(input arguments)。在汇编语言中,常通过通用寄存 器来传递参数。 例子: .data
保护模式下的立即数总是32位的。在实地址模式下,如果未使用 .386(或更高)处理器伪指令,默认的立即数是16位的。
POP指令首先将ESP所指的堆栈元素拷贝到16位或32位的目的 操作数中,然后增加ESP的值。如果操作数是16位的,ESP值 将加2;如果操作数是32位的,ESP值将加4。其格式如下:
2007年10月
调用ArraySum:
.data Array DWORD 0000h,20000h,30000h,40000h theSum DWORD .code main PROC mov esi,OFFSET array mov ecx,LENGTHOF array call ArraySum mov theSum,eax; exit main ENDP
偏移
00001000 00000FFC 00000FF8 00000006
ESP
NASM汇编编译器 允许PUSH指令使 用特定的寄存器 ,如EAX。
00000FF4
00000FF0
2007年10月 汇编语言 4
压栈操作和出栈操作
偏移
压栈之前 00000006
偏移 ESP
00001000
00000FFC 00000FF8 00000FF4 00000FF0
;将每个整数加入和 ;指向下一个整数 ;循环 ;还原ECX和ESI的值
ecx esi
;通过EAX返回结果
汇编语言
14
保存和恢复寄存器 ArraySum过程的开始处ECX和ESI被压入堆栈,过程结束的时 候又被弹出,绝大多数修改寄存器的过程都是用这种方式。 修改寄存器值的过程应该总是保存和恢复寄存器值,以确保 调用程序本身的寄存器值不被覆盖。 与PROC伪指令配套使用的USES操作符允许列出被过程修改的 所有寄存器,它只是编译器做两件事:首先,在过程的开始 处生成PUSH指令在堆栈上保存寄存器值;其次,在过程的结 束处生成POP指令恢复这些寄存器值。USES操作符应紧跟PROC 伪指令,其后跟由空格或制表符(不是逗号)分隔的寄存器列 表。 例外:在过程要使用寄存器作为返回值得时候,千万不要将 用于返回值得寄存器压栈和弹出,否则过程的返回值很可能 丢失。
2007年10月 汇编语言 15
USES操作符例子
ArraySum PROC USES esi ecx ArraySum PROC push push mov L1: add add loop L2: pop pop ret ArraySum ENDP esi ecx eax,0 eax,[esi] esi,4 L1 ecx esi
2007年10月
汇编语言
6
PUSH和POP指令 PUSH指令首先减小ESP的值,然后将一个16位或32位的源操作 数拷贝至堆栈上。对于16位操作数,ESP值将减2;对于32位 操作数,ESP值将减4。PUSH指令有以下三种格式:
PUSH r/m16 PUSH r/m32 PUSH imm32
汇编语言
2007年10月
汇编语言
1
第5章 过程
2007年10月
汇编语言
2
堆栈操作
堆栈(stack)也被称为后进先出结构 (LIFO structure, last-in, firstout),这是因为最后压入堆栈的值总 是最先被取出。 堆栈数据结构遵循相同的规则:新值 总是被加到堆栈的顶端,数据也总是 从堆栈的最顶端取出。
2007年10月
汇编语言
17
Irvine32.lib链接库中包含的过程 Irvine32.inc Irvine32.asm
2007年10月
汇编语言
18
使用过程进行程序设计
任何稍微复杂一点的程序应用都会包含一些不同的步骤。把 所有的程序代码都写在一个过程之内是可能的,但是这样的 程序很难阅读和维护,相反我们最好把各种编程任务划分为 独立的过程。所有的过程可在同一源文件中,也可在多个文 件中。 开始写程序时,用一份说明书来详细列出程序究竟要做什么 是非常有帮助的,这通常是仔细分析要解决的问题的结果。 一说明书为起点,就可以开始设计升序了。 一种标准的设计方法是将整体的问题分割成独立的任务,每 个任务都可以在一个过程中实现。将问题细分为任务的过程 通常称为功能分解(functional decomposition),或自顶向 下的设计(up-down design)。
POP r/m16 POP r/m32
2007年10月
汇编语言
7
PUSHFD,PUSHF,POPFD和POPF指令 PUSHFD指令在堆栈上压入32位的EFLAGS寄存器的值,POPFD指 令将堆栈顶部的值弹出并送至EFLAGS寄存器:
PUSHFD POPFD
实地址模式程序使用PUSHF指令在堆栈上压入16位的FLAGS寄 存器的值,使用POPF指令从堆栈顶部弹出16位值并送到FLAGS 寄存器:
例子:反转字符串RevStr.asm。
2007年10月
汇编语言
9
过程的定义和使用
PROC伪指令 过程使用PROC和ENDP伪指令来声明,另外还必须给过程起一 个名字(一个有效的标识符)。创建除了程序启动过程之外的 其他过程时应以RET结束,以强制CPU返回到过程被调用的地 方。 例子:三个整数之和
13
2007年10月
例子:对整数数组求和
;----------------------------------------------------ArraySum PROC ; ; Calculates the sum of an array of 32-bit integers. ; Receives: ESI points to the array, ECX = array size ; Returns: EAX = sum of the array elements ;----------------------------------------------------push esi ;保存ESI和ECX的值 push ecx mov eax,0 ;将和设置为0 L1: add eax,[esi] add esi,4 loop L1 pop pop ret ArraySum ENDP
8 7 6 5 4 3 2 1
顶部
底部
2007年10月
汇编语言
3
运行时栈(runtime stack)是由CPU内部硬件直接支持的,也 是实现过程调用和过程返回机制的基本组成部分。 运行时栈是由CPU直接管理的内存数组,它使用两个寄存器: SS和ESP。在保护模式下,SS寄存器存放的是段选择器,用户 模式程序不应对其进行修改。ESP寄存器存放的是指向堆栈内 特定位置的一个32位偏移值。 我们很少需要直接操纵ESP的值,相反,ESP寄存器的值通常 是由CALL,RET,PUSH和POP等指令间接修改的。
mov L1:
eax,0
add eax,[esi] add esi,4 loop L1
ret ArraySum ENDP
2007年10月
汇编语言
16
与外部库链接
链接库(link library)是包含已经编译成机器码的过程的文件。一 个或多个包含过程、常量和变量的源文件被编译成目标文件,然后 这些目标文件被插入到库中。 假设程序要调用名为WriteString的过程在控制台上显示字符串,那 么程序就必须包含一条PROTO伪指令声明要调用的程序。在 Irvine32.inc中可以找到如下伪指令: WriteString PROTO 接下来,用一条CALL指令执行WriteString过程: call WriteString 在程序被编译的时候,编译器为CALL指令的目的地址留出空白,该 空白随后由链接器填充。链接器在链接库中查找WriteString这个名 字,并从库中把合适的机器指令拷贝到程序的可执行文件中,并把 WriteString的地址插入到CALL指令中。
2007年10月
call SumOf mov ebx,eax 00000025 EIP 00000040
汇编语言
add eax,ecx ret SumOf ENDP ESP 00000025 EIP 00000025
11
局部标号和全局标号 默认情况下,代码标号(以单个冒号结尾)有一个局部域,使 得它对其所在过程内的语句可见,这阻止了跳转或循环语句 转移到当前过程之外的标号。在极少数情况下,如果必须将 控制转移到当前过程之外的标号处,标号必须被声明为全局 的。声明全局标号,要在标号后跟两个冒号。例如:
theSum DWORD ? .code main PROC mov eax,10000h mov ebx,20000h mov ecx,30000h call SumOf mov theSum,eax exit main ENDP 汇编语言
;参数 ;参数 ;参数 ;EAX=(EAX+EBX+ECX) ;保存和 ;启动过程结束来自百度文库
压栈之后
00001000
00000006
000000A6
00000FFC
00000FF8 00000FF4 00000FF0
ESP
偏移
00001000 00000FFC 00000FF8 00000FF4
出栈之前 00000006 000000A5
偏移
00001000 00000FFC 00000FF8
PUSHF POPF
2007年10月
汇编语言
8
PUSHAD,PUSHA,POPAD和POPA指令 PUSHAD指令在堆栈上按下列顺序压入所有的32位通用寄存器 :EAX,ECX,EDX,EBX,ESP的原始值,EBP,ESI和EDI; POPAD指令以相反的顺序从堆栈中弹出这些通用寄存器。 与之类似,80286处理器引入的PUSHA指令以同样的顺序压入 所有的16位寄存器(AX,CX,DX,BX,SP的原始值,BP,SI和 DI);POPA指令则以相反顺序弹出这些寄存器。
假设合适的整数在调用过程之前已经存放在EAX,EBX和ECX寄存器中 了,函数将在EAX中返回相加的和: SumOf PROC add eax,ebx add eax,ecx ret SumOf ENDP
2007年10月
汇编语言
10
CALL和RET指令 CALL指令指示处理器在新的内存地址执行指令,以实现对过 程的调用。在过程中使用RET(return from procedure)指令 使处理器返回到程序中调用过程的地方继续执行。 从底层细节角度来讲,CALL指令将返回地址压入堆栈并将被 调用过程的地址拷贝到指令指针寄存器中;当程序返回时, RET指令从堆栈中弹出地址并送到指令指针寄存器中。CPU总 是执行指令指针寄存器EIP(在16位模式下是IP)所指向的内存 地址处的指令。 SumOf PROC add eax,ebx 调用和返回的例子 00000040
出栈之后 00000006 000000A5 00000001
00000001
0000002
ESP
ESP
汇编语言
00000FF4 00000FF0
5
00000FF0
2007年10月
在程序中堆栈有几种重要的用途: 寄存器在用做多种用途的时候,堆栈可方便地作为其临时保 存区域,在寄存器使用完毕之后,可恢复其原始值。 CALL指令执行的时候,CPU用堆栈保存当前过程的返回地址。 调用过程的时候,可以通过堆栈传递输入值(参数)。 过程内部的局部变量在堆栈上创建,过程结束时,这些变量 被丢弃。
main PROC jmp L2 L1:: ;错误! ;全局标号
exit
main ENDP sub PROC L2: jmp L1 ret sub ENDP
2007年10月 汇编语言 12
;局部标号 ;正确
向过程传递寄存器参数 如果过程执行某些诸如整数数组求和之类的标准操作,那么 在过程之内引用特定的变量名并不是什么好主意。如果准备 那么做的话,该过程就不可能用于其他数组了。 一个较好的方法是向过程传递数组的偏移,再传递一个整数 来表示数组元素的数目。我们称这些为参数(arguments)或输 入参数(input arguments)。在汇编语言中,常通过通用寄存 器来传递参数。 例子: .data
保护模式下的立即数总是32位的。在实地址模式下,如果未使用 .386(或更高)处理器伪指令,默认的立即数是16位的。
POP指令首先将ESP所指的堆栈元素拷贝到16位或32位的目的 操作数中,然后增加ESP的值。如果操作数是16位的,ESP值 将加2;如果操作数是32位的,ESP值将加4。其格式如下:
2007年10月
调用ArraySum:
.data Array DWORD 0000h,20000h,30000h,40000h theSum DWORD .code main PROC mov esi,OFFSET array mov ecx,LENGTHOF array call ArraySum mov theSum,eax; exit main ENDP
偏移
00001000 00000FFC 00000FF8 00000006
ESP
NASM汇编编译器 允许PUSH指令使 用特定的寄存器 ,如EAX。
00000FF4
00000FF0
2007年10月 汇编语言 4
压栈操作和出栈操作
偏移
压栈之前 00000006
偏移 ESP
00001000
00000FFC 00000FF8 00000FF4 00000FF0
;将每个整数加入和 ;指向下一个整数 ;循环 ;还原ECX和ESI的值
ecx esi
;通过EAX返回结果
汇编语言
14
保存和恢复寄存器 ArraySum过程的开始处ECX和ESI被压入堆栈,过程结束的时 候又被弹出,绝大多数修改寄存器的过程都是用这种方式。 修改寄存器值的过程应该总是保存和恢复寄存器值,以确保 调用程序本身的寄存器值不被覆盖。 与PROC伪指令配套使用的USES操作符允许列出被过程修改的 所有寄存器,它只是编译器做两件事:首先,在过程的开始 处生成PUSH指令在堆栈上保存寄存器值;其次,在过程的结 束处生成POP指令恢复这些寄存器值。USES操作符应紧跟PROC 伪指令,其后跟由空格或制表符(不是逗号)分隔的寄存器列 表。 例外:在过程要使用寄存器作为返回值得时候,千万不要将 用于返回值得寄存器压栈和弹出,否则过程的返回值很可能 丢失。
2007年10月 汇编语言 15
USES操作符例子
ArraySum PROC USES esi ecx ArraySum PROC push push mov L1: add add loop L2: pop pop ret ArraySum ENDP esi ecx eax,0 eax,[esi] esi,4 L1 ecx esi
2007年10月
汇编语言
6
PUSH和POP指令 PUSH指令首先减小ESP的值,然后将一个16位或32位的源操作 数拷贝至堆栈上。对于16位操作数,ESP值将减2;对于32位 操作数,ESP值将减4。PUSH指令有以下三种格式:
PUSH r/m16 PUSH r/m32 PUSH imm32
汇编语言
2007年10月
汇编语言
1
第5章 过程
2007年10月
汇编语言
2
堆栈操作
堆栈(stack)也被称为后进先出结构 (LIFO structure, last-in, firstout),这是因为最后压入堆栈的值总 是最先被取出。 堆栈数据结构遵循相同的规则:新值 总是被加到堆栈的顶端,数据也总是 从堆栈的最顶端取出。
2007年10月
汇编语言
17
Irvine32.lib链接库中包含的过程 Irvine32.inc Irvine32.asm
2007年10月
汇编语言
18
使用过程进行程序设计
任何稍微复杂一点的程序应用都会包含一些不同的步骤。把 所有的程序代码都写在一个过程之内是可能的,但是这样的 程序很难阅读和维护,相反我们最好把各种编程任务划分为 独立的过程。所有的过程可在同一源文件中,也可在多个文 件中。 开始写程序时,用一份说明书来详细列出程序究竟要做什么 是非常有帮助的,这通常是仔细分析要解决的问题的结果。 一说明书为起点,就可以开始设计升序了。 一种标准的设计方法是将整体的问题分割成独立的任务,每 个任务都可以在一个过程中实现。将问题细分为任务的过程 通常称为功能分解(functional decomposition),或自顶向 下的设计(up-down design)。
POP r/m16 POP r/m32
2007年10月
汇编语言
7
PUSHFD,PUSHF,POPFD和POPF指令 PUSHFD指令在堆栈上压入32位的EFLAGS寄存器的值,POPFD指 令将堆栈顶部的值弹出并送至EFLAGS寄存器:
PUSHFD POPFD
实地址模式程序使用PUSHF指令在堆栈上压入16位的FLAGS寄 存器的值,使用POPF指令从堆栈顶部弹出16位值并送到FLAGS 寄存器:
例子:反转字符串RevStr.asm。
2007年10月
汇编语言
9
过程的定义和使用
PROC伪指令 过程使用PROC和ENDP伪指令来声明,另外还必须给过程起一 个名字(一个有效的标识符)。创建除了程序启动过程之外的 其他过程时应以RET结束,以强制CPU返回到过程被调用的地 方。 例子:三个整数之和
13
2007年10月
例子:对整数数组求和
;----------------------------------------------------ArraySum PROC ; ; Calculates the sum of an array of 32-bit integers. ; Receives: ESI points to the array, ECX = array size ; Returns: EAX = sum of the array elements ;----------------------------------------------------push esi ;保存ESI和ECX的值 push ecx mov eax,0 ;将和设置为0 L1: add eax,[esi] add esi,4 loop L1 pop pop ret ArraySum ENDP
8 7 6 5 4 3 2 1
顶部
底部
2007年10月
汇编语言
3
运行时栈(runtime stack)是由CPU内部硬件直接支持的,也 是实现过程调用和过程返回机制的基本组成部分。 运行时栈是由CPU直接管理的内存数组,它使用两个寄存器: SS和ESP。在保护模式下,SS寄存器存放的是段选择器,用户 模式程序不应对其进行修改。ESP寄存器存放的是指向堆栈内 特定位置的一个32位偏移值。 我们很少需要直接操纵ESP的值,相反,ESP寄存器的值通常 是由CALL,RET,PUSH和POP等指令间接修改的。
mov L1:
eax,0
add eax,[esi] add esi,4 loop L1
ret ArraySum ENDP
2007年10月
汇编语言
16
与外部库链接
链接库(link library)是包含已经编译成机器码的过程的文件。一 个或多个包含过程、常量和变量的源文件被编译成目标文件,然后 这些目标文件被插入到库中。 假设程序要调用名为WriteString的过程在控制台上显示字符串,那 么程序就必须包含一条PROTO伪指令声明要调用的程序。在 Irvine32.inc中可以找到如下伪指令: WriteString PROTO 接下来,用一条CALL指令执行WriteString过程: call WriteString 在程序被编译的时候,编译器为CALL指令的目的地址留出空白,该 空白随后由链接器填充。链接器在链接库中查找WriteString这个名 字,并从库中把合适的机器指令拷贝到程序的可执行文件中,并把 WriteString的地址插入到CALL指令中。