引言
记录一下学习汇编过程中写的一些小程序,代码都是在DosBox上完成的,记录的目的也是为了在后面想要复习相关知识点的时候有一个很好的资料和练习素材,同时也希望能够为后来学习的朋友起一点指导作用。
1. Hello World!
DATA SEGMENT
str db 'Hello World$' ;要输出的字符串必须要以$结尾
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA ;将CS和CODE,DS和DATA段建立联系
START:
MOV BX,DATA ; 段寄存器不能被立即数直接赋值,需要先赋值给其他寄存器,然后再赋值
MOV DS,BX
LEA DX,str
MOV AH,9 ; 执行9号系统调用,从DX寄存器中取出值并打印
INT 21H ; 产生一个系统中断
MOV AH,4CH ;将控制权返回给终端
INT 21H
CODE ENDS
END START
ps:
- LEA和MOV其实作用差不多,LEA是load effective address 的缩写,其实就是把后一个参数的地址传递给第一个参数,而MOV则是传递后面的值,值是地址就是地址,是值就是值。lea其实和mov+offset功能一样,唯一不同lea是硬指令,在指令执行的时候才会计算,而offset在汇编阶段就已经完成计算了。传送门
- INT 21H其实就是执行系统调用,那执行哪一个呢?玄机就在前面的mov中,有些同学可能对系统调用表不清楚,可以随时查阅这里–>传送门。
2. 计算平均值
DATA SEGMENT
sum DW 1,2,3,4,5,6,7,8,9
DATA ENDS
STACK SEGMENT
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:
MOV BX, DATA
MOV DS, BX
MOV CX, 9 ; loop使用CX作为判断条件 即设置循环次数
XOR AX, AX ;清零
XOR BX, BX
MOV SI, OFFSET sum ; 得到sum的起始地址
again:
ADD AX, [SI]
ADD SI, 2 ; 索引指向下一项
LOOP again ; 循环结构语句,其中隐含使用CX寄存器,每次加1
;CDQ
MOV CL, 9
DIV CL ; 商在AX, 余数在DX
MOV DL, AL
ADD DL, 30H
MOV AH, 02H ;2号系统调用,从DL取出值输出
INT 21H
MOV AH, 4CH ;控制权返回给终端,不写的话显示完就卡那了
INT 21H
CODE ENDS
END START
3. 键盘输入1-7,显示相应为星期几
DATA SEGMENT
msg1 db 'Monday$'
msg2 db 'Tuesday$'
msg3 db 'Wednesday$'
msg4 db 'Thyrsday$'
msg5 db 'Friday$'
msg6 db 'Saturday$'
msg7 db 'Sunday$'
msg db 'input number(1-7):$'
table dw disp1,disp2,disp3,disp4
dw disp5,disp6,disp7
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV BX, DATA
MOV DS, BX
again:
LEA DX, msg
MOV AH, 9 ; 9号系统调用,从DX取数据并输出
INT 21H
XOR AX, AX ; 清空AX,不然后面乘4低位会出现错误
MOV AH, 01H ; 1号系统调用,将字符输入到AL中
INT 21H
;MOVZX AX, AL
SUB AX, 100H ; 系统调用会在AX上加100h
SUB AX, 30H ; ascll码,减30h变裸数字
; 不在范围内重新输入
CMP AL, 1 ; 减法操作,但不保存结果,只改变状态寄存器
JB again ; 无符号小于则跳转 CF=1
CMP AL, 7
JA again ; 无符号大于则跳转 CF=0,ZF=0
DEC AL
MOV AX, AL
MOV AH, 0 ; 意义不大,主要是前面类型不合适,这里补救一下
SHL AX, 1 ; 乘2,匹配到对应项地址
MOV BX, AX
;jmp dword ptr table[AX] ; 16位dos环境中,AX不能用作偏移地址
JMP WORD PTR TABLE[BX] ; jmp实际需要的是一个地址
disp1:
MOV AX, OFFSET msg1
jmp disp
disp2:
MOV AX, OFFSET msg2
jmp disp
disp3:
MOV AX, OFFSET msg3
jmp disp
disp4:
MOV AX, OFFSET msg4
jmp disp
disp5:
MOV AX, OFFSET msg5
jmp disp
disp6:
MOV AX, OFFSET msg6
jmp disp
disp7:
MOV AX, OFFSET msg7
jmp disp
disp:
MOV DX, AX
MOV AH, 9
INT 21H
MOV AH, 4CH
INT 21H ;控制权返回给终端
CODE ENDS
END START
ps:
- 切记,16位dos环境中,AX不能用作偏移地址,所以我们使用BX。
- 从table中取label的时候因为没有类型,需要我们指定类型,从而确定长度。
4. 模拟函数调用过程
sum函数的逻辑如下:
int sum(int lhs, int rhs){
int T1 = 1;
int T2 = 2;
return lhs + rhs + T1 + T2;
}
ASSUME CS:CODE,DS:DATA,SS:STACK
STACK SEGMENT
db 20 dup(0)
STACK ENDS
DATA SEGMENT
db 20 dup(0)
str db "hello world!$"
DATA ENDS
CODE SEGMENT
START:
MOV AX, DATA
MOV DS, AX
MOV AX, STACK
MOV SS, AX
;开始业务逻辑部分
MOV AX, 3h ;传递参数 立即数不能直接传递 para1
PUSH AX
MOV AX, 4H ; para2
PUSH AX
CALL sum ; 调用sum
MOV DL, AX
MOV AH, 02h; 从DX取值并输出
INT 21H
MOV AH, 4CH ; 将控制权转移给终端
INT 21H ; 产生系统中断
sum:
PUSH bp
MOV BP, SP ; 初始化函数堆栈
SUB SP, 20 ; 20字节留作局部变量
; 保护寄存器
PUSH BX
PUSH CX
PUSH DX
; 定义两个局部变量
MOV word ptr SS:[BP - 2], 1h
MOV word ptr SS:[BP - 4], 2h
; 修改寄存器
MOV BX, 2h
MOV CX, 3h
MOV DX, 4h
; 计算部分 两个参数加上常数1+2
MOV AX,SS:[BP + 6]; param1
ADD AX,SS:[BP + 4]; param2
ADD AX,SS:[BP - 2]; 1
ADD AX,SS:[BP - 4]; 2
ADD AX, 30H; 输出为ascll码,加上48
//|--------|
//|param2 3|
//|param1 4|
//|ret_addr| 返回地址就是CALL指令下一条指令的地址
//|ebp | EBP其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部
//|ebp-2 1h|
//|ebp-4 2h|
//|--------|
; 恢复寄存器
POP DX
POP CX
POP BX
MOV SP,BP
POP BP
RET
CODE ENDS
END START
ps:
- 函数的返回值一般存放在EAX中。
- 对于使用栈的函数调用过程一定要清楚,在参数和栈帧起点ebp之间还有一个ret_addr,存放着调用这个函数的原函数的EIP寄存器的位置,即程序计数器的位置,这样与ebp结合可以精确的在子函数执行完以后回到原函数。
5. 汇编生成1-N共N位不重复的随机数
比如1-5,就会生成12345, 34152,54312等等。
;生成八位的随机字符串
DATA SEGMENT
result db " " ; 9位
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX, DATA
MOV DS, AX
MOV SI, 0 ; 目前到第几个随机数
doRand: ; 这里设置一个label方便其他程序调用
CreateRandNumdo1: ; 每执行一次可以确定一个随机数
CALL getRand ; 这个随机数在AL中
XOR DI, DI
XOR CX, CX
LEA DI, result ; di中存着result的起始地址
; 这里在前几次会有很多无意义的执行,但是因为初始值为0,所以正确性是可以保证的
MOV CX, 8 ; CX为loop默认使用的寄存器,SI为现在已有的随机字符数,也就是循环这么多次
CreateRandNumdo2:
MOV BH, [DI]; 每次通过[DI]取一个值
CMP BH, AL ; 比较此次得到的随机数和已有的随机数
JZ CreateRandNumdo1 ; 出现重复的时候再次生成随机数
INC DI
LOOP CreateRandNumdo2
; CreateRandNumdo1结束以后,AL中存放着此次要插入的值,插入到result[SI]中
;ADD AL, 30H ; 输出为ASCLL,所以加上30H
MOV result[SI], AL
INC SI ; 现在对第几个随机数赋值
CMP SI, 8 ; 已经生成了完成了我们需要的随机字符串
JB doRand; 开始生成下一个随机字符 JB 无符号大于则跳转
MOV CX, 8
MOV SI, 0
ToASCLL: ; 目前全部的值都是数字,需要转换成ASCLL码
ADD result[SI], 30H
INC SI
LOOP ToASCLL
MOV result[SI], 24H ; $的ASCLL码为36, 给字符串末尾加上$
LEA DX, result
MOV AH, 9 ; 从DX中读取字符串并输出
INT 21H
;注释掉的部分为测试getRand
;--------------------------------------
;call getRand
;MOV DL, AL
;ADD DL, 30H ; 显示ascll码,加30H
;MOV AH, 02H ; 从DL取出数字显示
;INT 21H
;--------------------------------------
MOV AH, 4CH ; 控制权交给终端
INT 21H
getRand: ;获取一个随机数,放到AL中
XOR AL, AL
MOV AX, 0H
; 汇编中使用in/out来访问系统的io空间
OUT 43H, AL ;将0送到43h端口
IN AL, 40H ;将40h端口的数据送到AL寄存器
MOV BL, 8 ;除以8,得到范围0-7的余数
DIV BL ;商在AL, 余数在AH
MOV AL, AH ;
MOV AH, 0
INC AL ; 加1,得到范围1-8
RET
CODE ENDS
END START
ps:
- 汇编是直接面向硬件的,它可以访问系统的mem空间,也可以直接访问系统的io空间。汇编中使用in/out来访问系统的io空间。传送门
- 事实上这个代码还有优化空间,就是检测的时候没必要每次都检查8次,正确性可以保证,但是浪费一些指令。所以可以用一个寄存器保存现在已经完成的随机字符的数量,在CreateRandNumdo1给CX赋当前已经生成的字符数。
- 代码太难重用了,可以用一个寄存器保存需要生成的长度,类似于全局变量,替换这个代码满天飞的8。。
总结
后面还会持续更新这篇博客。先不做总结。