【1】我们使用的教材是机械工业出版社的《32位汇编语言程序设计第二版》。
指导老师是福州大学的倪一涛老师。
这门课程教授的是Intel 80*86系列处理器的32位汇编。我们现在的处理器都兼容这个处理器。
这篇博客只是大二下汇编语言学习的总结,用于基础入门。
【2】环境和工具:
电脑系统:Windows11
VC6和Devc++:用于看c语言对应的汇编,便于研究(也可以直接使用里面的g++)
汇编器:masm6.15--编译汇编,将汇编程序执行
调试器:ollydbg
工具的使用这里省略。
【3】汇编指令的执行过程:
程序设计语言是人与计算机沟通的语言,程序员利用它进行软件开发。通常人们习惯使用类似自然语言的高级程序设计语言,如C,C++,Basic,Java等。
高级语言需要翻译为计算机能够识别的指令(机器语言),才能被计算机执行。机器语言是一串0和1组成的二进制代码,如果直接用它来编写程序太过于晦涩难懂且开发效率极低,称为低级语言。
于是我们将二进制底阿妈的指令和数据用便于记忆的符号(助记符,Mnemonic)表示就形成汇编语言(Assembly),所以汇编语言是一种面向机器的低级程序设计语言,也称为低层语言。
【1】存储有分为外存储器和内存储器。外存储器就是磁盘。而内存储器就是寄存器,内存(分为主存和缓存)。这里重点介绍一下寄存器。
【2】寄存器分类
【3】通用寄存器(重点)
32为兼容16位和8位。所以我们寄存器写EAX或AX都可以。EAX和AX可以分为AH和AL也就是高低两个寄存器。其他也是这样。
地址指针寄存器尽量不要用,很容易导致程序出错。
数据寄存器的习惯用法:
【4】段寄存器和专用寄存器(一般在系统内核用到,我们用不到,了解即可)
汇编语言由以下3类组成:
1.汇编指令(机器码的助记符)
2.伪指令(由编译器执行)
3.其它符号(由编译器识别)
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
具体语法:
汇编语言分号后面是注释。
例:
- include irvine32.inc ; 包含 Irvine32 库,提供 32 位输入输出功能
- .data ; 定义数据段,这里定义的数据是存储在内存中
- ... ; 数据定义,这里省略
- .code ; 定义代码段
- main proc ; 主过程开始
- ... ; 执行过程,这里省略
- exit ; 调用 Irvine32 库中的 exit 函数,程序退出
- main endp ; 主过程结束
- end main ; 程序结束
在汇编语言中,不同的数据类型用来表示不同长度的数据。常见的数据类型,如:
BYTE:一个字节(8位)
WORD:一个字(16位)
DWORD:双字(32位)【也可以是dd】
QWORD:四字(64位)
- .data
- a dword 1
如果数值不确定,可以用?代替值
- .data
- a dword ?
传送指令MOV把一个字节,字或双字的操作数从源位置传送至目的位置,可以实现常数,通用寄存器,主存(内存)之间的数据的传送。
例:
但是mov的两个参数不能都是常数或内存。
基本格式也是add 目的操作数 源操作数。上面指令的两个参数也一样不能都是常数或内存。
例:inc eax
【1】汇编中也有函数,将一系列操作进行封装。
函数调用指令:Call f
【2】比如输入输出整数的函数:
(1)输入输出整数--ReadInt和WriteInt
格式:
注:这里我们使用函数之前,要将输出的目标整数先存入eax中。另外这里我们输入输出使用的函数是Irvine32 库,它提供 32 位输入输出功能。
(2)输出字符--WriteString,输出edx中存储内存地址指向的变量
案例:打印hello world
- include irvine32.inc ; 包含 Irvine32 库
-
- .data
- hello_msg byte "Hello World!", 0 ; 存储要显示的字符串
- .code
- main proc
- mov edx,offset hello_msg ; 将要显示的字符串地址存储在 edx 中
- call WriteString ; 调用 Irvine32 函数 WriteString 输出字符串
- exit ; 退出程序
- main endp
-
- end main
注:.data 部分用于声明和初始化数据段(存储在内存中的变量)。
在这里,hello_msg db "Hello World!", 0 这行代码的含义是:
(1)hello_msg:变量名称。
(2)byte:byte是一个伪指令,表示内存开辟一个字节的空间存储变量。
(3)"Hello World!":这是要存储的字符串数据,即 "Hello World!"。
(4)0:这里的 , 0 表示以 0 结尾,用于表示字符串的结束。在汇编语言中,定义字符串要在后面自己加上0当作终止符。(5)offset:offset是来获取内存地址的。因为writeString需要的是变量地址,而不是变量,所以这里字符串不能直接mov edx, hello_msg。而应该使用offset关键字来获取变量的地址放入edx中,这里存的是地址而不是值。
当然mov edx, hello_msg也不会成功,因为字符串“hello world”是byte类型,只有8位,所以mov要用movzx。【还有一个movsx是带符号位的】
这行代码的作用是定义了一个以 "Hello World!" 结尾的字符串并将其存储在内存中,然后将其地址放在edx中,然后调用WriteString函数去内存中找到地址对应的字符串将其输出。
(3)输出换行
call Crlf ;输出换行
- .data
- a dd 1,2,3,4,5,6,7
如果要数组一次性赋值:
- .data
- a dd 10 dup(0)
访问第一个元素
- include irvine32.inc
- .data
- a dword 1, 2, 3, 4, 5, 6, 7
- .code
- start:
- mov eax, a
- call writeint
- exit
- end start
注:writeint的本质是读取eax的四个字节,然后将这四个字节的数据转化为int值。
mov eax, a是将第一个元素的值放入eax中。
数组第i个元素访问:
- include irvine32.inc
- .data
- a dword 1, 2, 3, 4, 5, 6, 7
- .code
- start:
- mov eax, a+i*4
- call writeint
- exit
- end start
还有一种a+i*4更加好看更接近c++的写法就是a[i*4],一般我们都这样写。
注:[]这个加不加知识规范的问题,a+i*4和[a+i*4]实际上和a[i*4]是一致的。
- include irvine32.inc
- .data
- a dword 1, 2, 3, 4, 5, 6, 7
- .code
- start:
- mov eax, a[i*4]
- call writeint
- exit
- end start
一般使用比较和跳转实现分支结构
比较指令: cmp x,y
跳转指令: jmp, jXXX(ja, jb, jz)
cmp x, y:执行操作 x-y (x与y的值不变),根据操作结果改变EFLAG相应的位。
jmp是无条件跳转,而jXXX是有条件跳转。
比如
ja loc: 若x与y是无符号数(程序员定义)且 x>y,则程序跳转到地址loc处执行。
jz/je loc: 若x与y是无符号数(程序员定义)且 x==y,则程序跳转到地址loc处执行
jb loc: 若x与y是无符号数(程序员定义)且xjg loc: 若x与y是有符号数(程序员定义)且 x>y,则程序跳转到地址loc处执行
jz/je loc: 若x与y是符号数(程序员定义)且 x==y,则程序跳转到地址loc处执行
jl loc: 若x与y是无符号数(程序员定义)且 xjge loc 有符号数x>=y
jle loc 有符号树x<=y
分支结构案例:求整数 a与b最大值,并在屏幕中输出最大值
算法设计:
if a > b then max=a else max=b分支具体思路:
c++中的写法 if(a<b){ Block1 }else{ Block2 } 转化为比较和跳转指令实现 if a<b goto L1 Block2 goto final L1: Block1 final: 但是上面并没有Block1和Block2的顺序并没有和我们c++的正常逻辑一一对应 当我们真正去写的时候会发现有点麻烦,一般写的时候会将比较条件反转,也就是像下面这样 if a>=b goto L1 Block1 goto final L1: Block2 final:代码实现题目:
include irvine32.inc ; 包含 Irvine32 库 .data a DWORD 0x10 ;DWORD,双字32位,0x10是16进制的10 b DWORD 0x20 .code main PROC mov eax, a cmp eax, b jna maxb jmp final maxb: mov eax, b final : call writeint exit main ENDP END main
循环结构案例:
在内存中存有10个整数,求这10整数 最大值,并在屏幕中输出最大值
算法设计:
循环结构具体思路:
c语言中: for(i=0;i<n;i++){ Block; } 循环的本质是if判断: if(i<n){ Block; i++; } i=0 L0: if(i<n) goto L1 goto final L1: Block i++ jmp L0 final: 将比较条件反转,更简单书写,简化代码 i=0; L0: if(i>=n) goto final Block i++ jmp L0 final:代码实现:
main proc mov eax, arr[0];eax存放最大值 mov esi, 0; esi存放数组元素下标 L0: cmp esi, 10 jge final cmp eax, arr[esi*4] jge L1 mov eax,arr[esi*4]; L1: add esi, 1; jmp L0 final: call writeint exit main endp
1.用于模块化、是重要的封装机制
2.函数定义方式与执行逻辑
3.参数传递方法:
【1】内存变量(数据段)方式--简单说就是在.data下定义全局变量,所有函数都可以用。一般定义全局变量不好。
【2】寄存器方式--简单说就是将数据存放在寄存器中,然后函数要用去寄存器取。
【3】栈方式--将数据放在栈中,pop和push。
一般的话不用第一种,用第二和第三种。考试一般用第二种。
案例:插入排序
这里就是采用第二种方式,每次调用函数之前先将参数存入寄存器中,后面函数执行的时候再去寄存器取。和ReadInt等差不多。
- include irvine32.inc
- .data
- arr1 dword 10 dup(?)
- arr2 dword 20 dup(?)
- .code
- main proc
- mov edx,offset arr2
- mov ecx,20
- call buildArray
-
- mov edx,offset arr2
- mov ecx,20
- call outputArray
- call crlf
-
- mov edx,offset arr2
- mov edi,20
- call insertSort
-
- mov edx,offset arr2
- mov ecx,20
- call outputArray
- exit
- main endp
-
- insertSort proc
- mov ebx,1
- startFor:
- cmp ebx,edi
- jge endFor
- mov ecx,[edx+4*ebx]
- mov esi,ebx
- sub esi,1
- startWhile:
- cmp esi,0
- jl endWhile
- cmp [edx+4*esi],ecx
- jle endWhile
- mov eax,[edx+4*esi];eax=arr[j]
- mov [edx+4*esi+4],eax
- sub esi,1
- jmp startWhile
- endWhile:
- mov [edx+4*esi+4],ecx
- add ebx,1
- jmp startFor
- endFor:
- ret
- insertSort endp
-
- outputArray proc
- mov esi,0
- startFor:
- cmp esi,ecx
- jge endFor
- mov eax,[edx+4*esi]
- call writedec
- mov al,' '
- call writechar
- add esi,1
- jmp startFor
- endFor:
- ret
- outputArray endp
-
- buildArray proc
- mov esi,0
- startFor:
- cmp esi,ecx
- jge endFor
- mov eax,1000
- call randomRange
- mov [edx+4*esi],eax
- add esi,1
- jmp startFor
- endFor:
- ret
- buildArray endp
-
- end main
注:ret是函数返回,后面可以接参数。而exit是程序退出。
proc代表函数开始,endp代表函数结束。