从入门到精通汇编语言 第七章(高级汇编语言技术)
参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili
一、子程序的另一种写法
1、另一种写法
(1)类似于C语言,汇编语言中也可以将子程序写得像子函数一样可读性更强(C语言中的子函数有花括号做边界)。
(2)具体格式如下,用CALL命令调用子程序时使用子程序名即可。
<子程序名> proc ;子程序起始
<子程序标号>:
<子程序内容>
ret/retf ;子程序返回(ret/retf取决于是近调还是远调)
<子程序名> endp ;子程序结束
2、举例
(1)原汇编程序:
assume cs:code, ss:stack
stack segmentdb 16 dup (0)
stack ends
code segmentstart: mov ax, stackmov ss, axmov sp, 16mov ax, 1000call s ;调用子程序mov ax, 4c00hint 21hs: add ax,ax ;子程序开始ret ;子程序返回
code ends
end start
(2)近调用改写:
assume cs:code, ss:stack
stack segmentdb 16 dup (0)
stack ends
code segment
main proc ;主程序起始start: mov ax, stackmov ss, axmov sp,16mov ax, 1000call subp ;调用子程序mov ax, 4c00hint 21h
main endp ;主程序结束
subp proc ;子程序subp起始s: add ax, axret ;子程序subp返回
subp endp ;子程序subp结束
code ends
end start
(3)远调用改写:
assume cs:code, ss:stack
stack segmentdb 16 dup (0)
stack ends
code segment
main proc ;主程序起始start: mov ax, stackmov ss, axmov sp, 16mov ax,1000call far ptr subp ;调用子程序mov ax, 4c00hint 21h
main endp ;主程序结束
subp proc ;子程序subp起始s: add ax, axretf ;子程序subp返回
subp endp ;子程序subp结束
code ends
end start
二、程序的多文件组织
1、多文件程序的编写和编译/链接
(1)一个程序不一定只能使用一个.asm文件编写,它可以分布在若干个.asm文件中,如同C语言一样。
(2)使用在其它文件中的子程序时,需要在该文件头部声明欲使用的子程序在其它文件已有定义,格式为“extrn <子程序名>:far”。
(3)文件中定义的子程序被外部使用时,需要在该文件头部声明子程序可被外部使用,格式为“public <子程序名>”。
(4)所有文件编写好后,需要对它们分别进行编译,编译完成后要将它们链接在一起。
2、举例
(1)原汇编程序:
assume cs:code, ss:stack
stack segmentdb 16 dup (0)
stack ends
code segmentstart: mov ax, stackmov ss, axmov sp, 16mov ax, 1000call s ;调用子程序mov ax, 4c00hint 21hs: add ax,ax ;子程序开始ret ;子程序返回
code ends
end start
(2)改写的汇编程序:
①编写p1.asm文件:
extrn subp:far ;声明要使用的外部子程序subp
assume cs:code, ss:stack
stack segment stackdb 16 dup (0)
stack ends
code segment
main procstart: mov ax, stackmov ss, axmov sp, 16mov ax, 1000call far ptr subp ;调用外部子程序subpmov ax, 4c00hint 21h
main endp
code ends
end start
②编写p2.asm文件:
public subp ;声明外部程序要使用的子程序subp
assume cs:code
code segment
subp procs: add ax,axretf
subp endp
code ends
end
③在DOSBox中输入“p1.asm”并按下回车,编译p1.asm文件生成p1.obj文件,然后输入“p2.asm”并按下回车,编译p2.asm文件生成p2.obj文件,最后输入“link p1.obj+p2.obj”,将二者链接,生成可执行文件。
三、汇编指令汇总(并非全部详细介绍)
1、数据传送指令
(1)通用数据传送指令:MOV、PUSH、POP、XCHG。
(2)累加器专用传送指令:IN、OUT、XLAT。
(3)地址传送指令:LEA、LDS、LES。
(4)标志寄存器传送指令:LAHF、SAHF、PUSHF、POPF。
(5)类型转换指令:CBW、CWD。
2、算术指令
(1)加法指令:ADD、ADC、INC。
(2)减法指令:SUB、SBB、DEC、NEG、CMP。
(3)乘法指令:MUL、IMUL。
(4)除法指令:DIV、IDIV。
(5)十进制调整指令:DAA、DAS、AAA、AAS、AAM、AAD。
压缩的BCD码(用4位二进制数表示1位十进制数)调整指令:
● DAA——加法的十进制调整指令
● DAS——减法的十进制调整指令
非压缩的BCD码(用8位二进制数表示1位十进制数)调整指令:
● AAA——加法的ASCII码调整指令
● AAS——减法的ASCII码调整指令
● AAM——乘法的ASCII码调整指令
● AAD——除法的ASCII码调整指令
3、逻辑指令
(1)逻辑运算指令:AND、OR、NOT、XOR、TEST。
(2)移位指令:SHL、SHR、SAL、SAR、ROL、ROR、RCL、RCR。
4、串处理指令
(1)设置方向标志指令:CLD、STD。
(2)串处理指令:MOVSB / MOVSW、STOSB / STOSW、LODSB / LODSW、CMPSB / CMPSW、SCASB / SCASW。
(3)重复执行指令前缀:REP、REPE / REPZ、REPNE / REPNZ。
5、控制转移指令
(1)无条件转移指令:JMP。
(2)条件转移指令:JZ / JNZ、JE / JNE、JS / JNS、JO / JNO、JP / JNP、JB / JNB、JL / JNL、JBE / JNBE、JLE / JNLE、JCXZ。
(3)循环指令:LOOP、LOOPZ / LOOPE、LOOPNZ / LOOPNE。
(4)子程序调用和返回指令:CALL、RET。
(5)中断与中断返回指令:INT、INTO、IRET。
6、处理机控制与杂项操作指令
(1)标志处理指令:CLC、 STC、 CMC、CLD、STD、CLI、STI。
(2)其它处理机控制与杂项操作指令:
①NOP——无操作(机器码占一个字节)。
②HLT——暂停机(等待一次外中断,之后继续执行程序)。
③WAIT——等待(等待外中断,之后仍继续等待)
④ESC——换码。
⑤LOCK——封锁(维持总线的锁存信号,直到其后的指令执行完)。
四、汇编伪操作汇总(并非全部详细介绍)
1、汇编指令与伪操作
(1)汇编指令对应机器指令,在程序运行期间由计算机执行。
(2)伪操作指的是在汇编程序对源程序汇编期间,由汇编程序处理的操作,可以完成如数据定义、分配存储区、指示程序结束等功能。
2、处理器选择伪操作
(1).8086——选择8086指令系统。
(2).286——选择80286指令系统。
(3).286P——选择保护模式下的80286指令系统。
(4).386——选择80386指令系统。
(5).386P——选择保护模式下的80386指令系统。
(6).486——选择80486 指令系统。
(7).486P——选择保护模式下的80486指令系统。
(8).586——选择Pentium 指令系统。
(9).586P——选择保护模式下的Pentium指令系统。
3、段定义伪操作
(1)定义段的基本格式:
assume cs:<代码段名>, ds:<数据段名>, es:<附加段名>
<数据段名> segment ; 定义数据段
…
<数据段名> ends
;----------------------------------------
<附加段名> segment ; 定义附加段
…
<附加段名> ends
;----------------------------------------
<代码段名> segment ; 定义代码段
start:
…
<代码段名> ends
end start
(2)在定义段时还可以添加段声明,如下所示:
ASSUME <段寄存器>:<段名>
<段名> SEGMENT [<定位类型>] [<组合类型>] [<使用类型>] ['<类别>']
……
…… ; 语句序列
<段名> ENDS
①定位类型align_type用于指定段在分配空间时的策略,可选的选项有PARA、BYTE、WORD、DWORD、PAGE。
②组合类型combine_type用于当多个文件编译链接时指定段的共享范围,可选的选项有PRIVATE、PUBLIC、COMMON、STACK、AT exp。
③使用类型use_type用于指定使用内存的方式,可选的选项有USE16(16位)、USE32(32位)。
(3)简化版的段定义伪操作有:“.code [name]”、“.data”、“.data?”、“.fardata [name]”、“.fardata? [name]”、“.const”、“.stack [size]”。
(4)定义段的存储模式(指定在内存中如何安放各段):
①使用伪操作“.MODEL <存储模式> [,其它选项]”。
②存储模式可选的选项有tiny、small、medium、compact、large、huge、flat。
(5)举例——简写程序:
①非简化版:
assume cs:codesg, ss:stacksg, ds:datasg
datasg segmentstr db 'hello world!$'
datasg ends
stacksg segmentdb 32 dup (0)
stacksg ends
codesg segmentstart: mov ax, datasgmov ds, axmov ax, stacksgmov ss, axmov sp, 20hlea bx, stroutput: mov dl, [bx]cmp dl, '$' je stopmov ah, 02Hint 21hinc bxjmp outputstop: mov ax,4c00hint 21h
codesg ends
end start
②简化版:
.8086
.MODEL small
.datastr db 'hello world!$'
.stack 20H
.codestart: mov ax, @datamov ds, axlea bx, stroutput: mov dl, [bx]cmp dl, '$'je stopmov ah, 02Hint 21hinc bxjmp outputstop: mov ax, 4c00hint 21h
end start
4、程序开始和结束伪操作
(1)“TITLE text”操作可为子程序起一个标题。
(2)“NAME module_name”操作可为子程序起一个模块名。
(3)“END [ label ]”操作指定子程序在此行结束,且子程序在标号label处起始。
(4)“. STARTUP”操作用于初始化DS寄存器,相当于“mov ax, @data”和“mov ds, ax”。
(5)“. EXIT [ return_value ]”操作用于结束程序,相当于“mov ax, 4c00h”、“int 21h”。
5、数据定义及存储器分配伪操作
(1)伪操作“<变量名/标号(可选)> <助记符> <操作数> ,<操作数(可选)>, …”可以用于定义数据,其中可选的助记符有DB、DW、DD、DF、DQ、DT。
DATA_BYTE DB 10, 4, 10H, ? ;其中“?”表示随机数
DATA_WORD DW 100, 100H, -5, ?
PAR1 DW 100, 200
PAR2 DW 300, 400
ADDR_TABLE DW PAR1, PAR2
VAR DB 100 DUP (?)
DB 2 DUP (0,2 DUP(1,2),3)
(2)伪操作“<变量名> LABEL <type>”可以使同一变量(同一内存空间)以不同的数据类型解析,如下例所示。
BYTE_ARRAY LABEL BYTE ;指定后面的数据可使用字节类型解析
WORD_ARRAY DW 50 DUP (?) ;以字类型定义数据
;使用BYTE_ARRAY标号访问变量时,以字节类型解析数据
;使用WORD_ARRAY 标号访问变量时,以字类型解析数据
6、表达式赋值伪操作
(1)伪操作“<表达式1> EQU <表达式2>”可以实现将<表达式2>的结果赋值给<表达式1>的功能,类似于C语言中的赋值语句,不同的是,伪操作在编译阶段就有确切的结果了,它将被编译器优化,并非汇编语句。
(2)一个表达式允许多次重复赋值。
7、地址计数器与对准伪操作
(1)伪操作“ORG <表达式>”:设置当前地址计数器的值(地址计数器$——保存当前正在汇编的指令的地址)。
(2)伪操作“ALIGN <表达式(2的整数次幂)>”:保证数组边界地址能被操作数整除。
(3)伪操作“EVEN”:使下一个变量或指令开始于偶数字节地址。
SEG1 SEGMENT
ORG 10 ;设置当前地址计数器的值为10
VAR1 DW 1234H
ORG 20 ;设置当前地址计数器的值为20
VAR2 DW 5678H
ORG $+8 ;设置当前地址计数器的值为原值+8
VAR3 DW 1357H
ALIGN 4 ;保证数组边界从4的整数倍地址开始
ARRAY db 100 DUP(?)
A DB ‘morning’
EVEN ;使下一个变量开始于偶数字节地址
B DW 2 DUP (?)
SEG1 ENDS
8、基数控制伪操作
(1)一般来说,无特别声明时(操作数没有H之类的后缀),操作数默认为十进制数。
(2)使用伪指令“. RADIX <表达式>”,可以将默认进制修改为<表达式>。
五、汇编操作符汇总(并非全部详细介绍)
1、操作符的作用
(1)操作符用于操作数中,通过操作符,将常数、寄存器、标号、变量等,组合成表达式,实现求值的目的。
(2)操作符仅在汇编期间起作用,编译结束后操作符将会被优化,也可认为它属于伪指令,但需要注意的是,使用了操作符的表达式,需保证其意义是明确的。
2、算术操作符
(1)算术操作符有:+、-、*、/、Mod。
(2)举例:
BLOCK DB 25*80*2 DUP(?)
ARRAY DW 1, 2, 3, 4, 5, 6, 7
ARYEND DW ?
MOV CX, (ARYEND - ARRAY) / 2
ADD AX, BLOCK+2 ;符号地址加减一个常数,有明确意义,但地址不允许乘除
MOV AX, BX+1 ;使用错误,这种情况必须使用加减法指令
MOV AX, [BX+1] ;寄存器间接寻址,有明确意义
3、逻辑和移位操作符
(1)逻辑和移位操作符有AND、OR、XOR、NOT、SHL、SHR,它们和一些汇编指令雷同,实现的功能也是一样的,不同的是,它只负责在汇编期间计算表达式结果,汇编结束后表达式将直接被优化为一个确切的操作数。
(2)举例:
OPR1 EQU 25 ;00011001B
OPR2 EQU 7 ;00000111B
AND AX, OPR1 AND OPR2 ;优化为AND AX, 1
MOV AX, 0FFFFH SHL 2 ;优化为MOV AX, 0FFFCH
PORT_VAL = 61H ;01100001B
IN AL, PORT_VAL ;优化为IN AL, 61H
OUT PORT_VAL AND 0FEH, AL ;优化为OUT 60H, AL
4、关系操作符
(1)关系操作符有EQ、NE、LT、LE、GT、GE,它和C语言中的关系操作符一样,用于关系表达式中,关系表达式的结果只有真、假两种结果,当为真时,表示结果的二进制数全部位为1,当为假时,表示结果的二进制数全部位为0。
(2)举例:
MOV FID, (OFFSET Y - OFFSET X) LE 128 ;语句T
X: ……
……
Y: ……
;若≤128 (真) 语句T汇编结果: MOV FID, -1 ;MOV FID, 0FFFFH
;若>128 (假) 语句T汇编结果: MOV FID, 0 ;MOV FID, 0H
5、数值回送操作符
(1)数值回送操作符有OFFSET、SEG、TYPE、LENGTH、SIZE。
(2)OFFSET的操作数为一个变量名或标号,其作用是回送变量或标号的偏移地址。
(3)SEG的操作数为一个变量名或标号,其作用是回送变量或标号的段地址。
(4)TYPE的操作数为一个变量名或标号或常数,其作用如下:
①若操作数为变量名,则结果取决于定义它的助记符,规则如下图所示。
②若操作数为标号,则结果取决于它是近转移还是远转移,规则如下所示。
③若操作数为常数,则返回0。
(5)LENGTH的操作数为一个变量名,其作用是回送由DUP定义的变量的单元数,若变量不是由DUP定义,则回送1。
(6)SIZE的操作数为一个变量名,其作用是回送由DUP定义的变量的占用总字节数,若变量不是由DUP定义,则回送通过变量名直接访问到的内存所占用字节数。
(7)举例:
ARRAY DW 100 DUP (?)
TABLE DB 'ABCD'
ADD SI, TYPE ARRAY ;优化为ADD SI, 2
ADD SI, TYPE TABLE ;优化为ADD SI, 1
MOV CX, LENGTH ARRAY ;优化为MOV CX, 100
MOV CX, LENGTH TABLE ;优化为MOV CX, 1
MOV CX, SIZE ARRAY ;优化为MOV CX, 200
MOV CX, SIZE TABLE ;优化为MOV CX, 1
6、属性操作符
(1)属性操作符有PTR、段操作符、SHORT、THIS、HIGH、LOW、HIGHWORD、LOWWORD。
(2)举例:
MOV WORD PTR [BX], 5 ;指定以字的形式访问[BX]
MOV ES: [BX], AL ;指操作数1的定段地址为ES
JMP SHORT NEXT ;短转移
TA EQU THIS BYTE ;解释下面的内容时可以以BYTE类型数据解释
TD DW 1234H
NEXT EQU THIS FAR ;可使用远转移根据标号NEXT跳转到此处
MOV AX, 2
CONS EQU 1234H
MOV AH, HIGH CONS ;将CONS的高8位存入AH
MOV AL, LOW CONS ;将CONS的低8位存入AL
六、汇编过程
1、可执行文件的生成过程
(1)可执行文件的生成过程回顾:
(2)在生成可执行文件的过程中,会有几个过程文件产生,如下所示:
①列表文件LST:将源程序(汇编语言)、目标程序(机器语言)、错误信息列表示出,以供检查程序用。
②交叉引用文件CRF:包含标识符(段名、过程名、变量名、标号)在源程序中定义的位置和被引用的位置,对源程序所用的各种符号进行前后对照的文件。
2、两遍汇编过程
(1)源文件要生成可执行文件,需要经过两次汇编:
①第一次汇编:确定地址,翻译成各条机器码,字符标号原样写出。
②第二次汇编:将字符标号用计算出的地址值或偏移量代换。
(2)举例:
七、宏汇编
1、汇编中的宏
(1)宏是指源程序中一段有独立功能的程序代码。
(2)宏指令是用户自定义的指令,在编程时,可以将多次使用的功能用一条宏指令来代替。
①宏定义:
<macro_name> MACRO [哑元表] ;哑元表中列出虚参,或者说形参
……
…… ;宏定义体
ENDM
②宏调用(必须先定义后调用):
<macro_name> [实元表] ;实元表中列出实参
③宏展开:将宏定义体复制到宏指令位置,并用实参代替宏定义中的虚参。
(3)举例:
(4)对比子程序,宏定义的参数传送简单,执行效率高,但因为它没有封装的概念,汇编过程中每一个宏调用都直接拷贝一整段宏定义的代码,容易导致代码占用空间大,所以对于功能复杂的代码,建议还是使用子程序进行封装。
2、宏中的局部标号
(1)当宏定义中涉及标号时,容易引起错误,因为一个宏可能在多个地方调用,但整个程序不能有同名的多个标号存在,为了解决这种矛盾,需要引入局部标号。
(2)在宏定义中用“LOCAL”声明宏定义体中的标号,即可将其声明为局部标号,这样,不同地方调用宏时,宏展开后编译器会自动区分这些宏中的标号,不会让他们产生冲突。
(3)举例:
3、变元
(1)变元是操作码中一部分,使用符号“&”进行声明,这样,当宏展开时,实参将会以文本的形式替换宏定义体中的变元虚参。
(2)举例:
4、宏库
(1)可以将用到的宏定义分类放到不同的.mac宏库文件中,同时在程序中用“include”包含宏库文件并调用宏,这样可以使程序文件的阅读性更强。
(2)举例:
①宏库文件asmio.mac:
input macromov ah, 01hint 21h
endm
output macro addrmov dx, offset addrmov ah, 09hint 21h
endm
mult macro mult1, mult2, mult3local lop, exit1mov dx, mult1mov cx, mult2xor ax, axjcxz exit1lop:add ax, dxloop lopexit1:mov mult3, ax
endm
②源程序文件ptest.asm:
include asmio.mac
assume cs:codes,ds:datas
datas segmentstring db 'hello world' ,13, 10, '$'
datas ends
codes segmentstart:mov ax, datasmov ds, axoutput stringmov ah, 4chint 21h
codes ends
end start
八、条件汇编与重复汇编
1、条件汇编
(1)在汇编过程中,可以根据某些条件把一段源程序包括在汇编语言程序内或者排除在外,大致格式如下:
IF××
…… ;满足条件则汇编此块
ELSE ;可选分支
…… ;不满足条件则汇编此块
ENDIF
(2)条件汇编“IF××”大全:
(3)举例:
①IF条件汇编示例:求1~3个数中的最大值并放入AX中。
MAX MACRO K,A,B,C ;K为参与比较的参数个数
LOCAL NEXT,OUT
MOV AX, A
IF K-1
IF K-2
CMP C, AX
JLE NEXT
MOV AX, C
ENDIF
NEXT:
CMP B, AX
JLE OUT
MOV AX, B
ENDIF
OUT:
ENDM
②IFDEF条件汇编示例:参数一共有3个,如果实参传递不符合参数个数要求,将不汇编相关语句,防止出错。
divide macro dividend,divisor,quotient
local comp, out
cnt=0
ifndef dividend
cnt=1
endif
ifndef divisor
cnt=1
endif
ifndef quotient
cnt=1
endif
if cnt
exitm
endif
mov ax, dividend
mov bx, divisor
sub cx, cx
comp:
cmp ax, bx
jb out
sub ax, bx
inc cx
jmp comp
out:
mov quotient, cx
endm
2、重复汇编
(1)重复汇编用于连续产生完全相同或基本相同的一组代码,其伪操作格式如下:
①定重复伪操作REPT:
REPT <表达式> ;表达式结果为重复次数
…… ;重复块
ENDM
②不定重复伪操作IRP:
IRP <哑元>,<自变量表> ;每次重复从自变量表中取一个值替代哑元,直到取完为止
…… ;重复块
ENDM
③不定重复伪操作IRPC:
IRPC <哑元>,<字符串> ;每次重复从字符串中取一个字符替代哑元,直到取完为止
…… ;重复块
ENDM
(2)举例:
①把字符'A'到'Z'的ASCII码填入数组TABLE。
CHAR = 'A'
TABLE LABEL BYTE
REPT 26
DB CHAR
CHAR = CHAR+1
ENDM
②生成一组入栈指令,依次将AX、BX、CX、DX中的内容入栈。
IRP REG, <AX, BX, CX, DX>
PUSH REG
ENDM
③生成一组定义若干“No.x”字符串的汇编语句。
array label byte
IRPC K, 12345
db 'NO.&K'
ENDM
九、反汇编(逆向工程)
1、反汇编概述
(1)把目标代码(二进制机器语言)转为汇编代码的过程就是反汇编,即把机器语言代码转换为汇编语言代码,由低级转高级。
(2)反汇编常用于软件破解(例如找到它是如何注册的,从而解出它的注册码或者编写注册机)、外挂技术、病毒分析、逆向工程、软件汉化等领域。
(3)学习和理解反汇编,对软件调试、漏洞分析、OS的内核原理及理解高级语言代码都有相当大的帮助,在此过程中我们可以领悟到软件作者的编程思想。
2、使用Debug对可执行程序进行反汇编
用debug运行可执行程序即可进行反汇编。
十、混合编程
1、混合编程概述
(1)混合编程是指使用两种或两种以上的程序设计语言来开发应用程序的过程。
(2)程序设计语言有多种,它们有各自的优势和不足,混合编程可以充分利用各种程序设计语言的优势。
2、混合编程举例
3、混合编程的其它方案
(1)不管是用高级语言还是汇编语言编写程序,它们经过编译后都能生成.obj文件,甚至可以打包为.lib文件,而其它程序,不管使用高级语言还是汇编语言,都能对.obj文件和.lib文件进行调用,实现混合编程。
(2)混合编程并不随意,在各种语言进行交互时,需参考编程接口规范。