汇编语言:知识点拾遗
前言
读王爽的书已经有一段时间了,马上就要学习有关“中断”的知识了。在继续学习之前,我想把一些我感觉比较有意思的小细节,小技巧总结一下。文章不长,如果以后有新的收获,我会把这一系列继续写下去。
大小写字转换的技巧
一般来说,要想实现大小写字母的转换,有两种基本思路。
- 根据大小写字母在ascii表中的相对位置进行转换
- 根据字母在字母表中相对于首字母a的位置进行转换
第一种思路:
如果字母为大写字母,小写字母 = 大写字母 + 20H
如果字母为小写字母,大写字母 = 小写字母 - 20H
第二种思路:
如果字母为大写字母,小写字母 = ‘a’ + 大写字母 - ‘A’
如果字母为小写字母,大写字母 = ‘A’ + 小写字母 - ‘a’
王爽的书中有提供了令一种方法:
假设当前字母在寄存器AL中
如果AL为大写字母,则and al,11011111B
如果AL为小写字母,则 or al,00100000B
此时AL中即为所要转换的结果。
为什么可以这样做呢?我下面给出一张表格,供大家验证。
大写 | 十六进制 | 二进制 | 小写 | 十六进制 | 二进制 |
---|---|---|---|---|---|
A | 41H | 01000001 | a | 61H | 01100001 |
B | 42H | 01000010 | b | 62H | 01100010 |
C | 43H | 01000011 | c | 63H | 01100011 |
D | 44H | 01000100 | d | 64H | 01100100 |
E | 45H | 01000101 | e | 65H | 01100101 |
F | 46H | 01000110 | f | 66H | 01100110 |
JMP、CALL、RET指令的机制
以下用s表示标号
jmp short s
——依据位移修改IP进行段内短转移(8位位移)jmp near ptr s
——依据位移修改IP进行段内近转移(16位位移)jmp far ptr s
——段间远转移,使(CS) = s的段地址,(IP) = s的偏移地址jmp 16位reg
——使(IP) = (reg)jmp word ptr 内存单元地址
——使(IP) = (内存单元地址)jmp dword ptr 内存单元地址
——使(CS) = (内存单元地址+2),(IP) = (内存单元地址)
详细内容见原书。
我们可以用汇编语言(尽管不符合语法)去理解ret和call指令
ret
——相当于pop IP
retf
——相对于先pop IP
,然后pop CS
call s
——相当于先push IP
,然后jmp near ptr s
call far ptr s
——相当于先push CS
,再push IP
,最后jmp far ptr s
call 16位reg
——相当于push IP
,然后jmp reg(16位)
call word ptr 内存单元地址
——相当于push IP
,然后jmp word ptr 内存单元地址
call dword ptr 内存单元地址
——相当于先push CS
,再push IP
,最后jmp dword ptr 内存单元地址
由于call与ret常常配合使用,所以之前我以为这两条指令必须成对出现(在设计子程序的时候)。汇编语言是相当自由的语言,当然没有这种约束,这些指令你想怎么用,你就怎么用,只不过它们的配合使用是一种“套路”罢了。
如果不熟悉call,ret的执行原理,在设计子程序的堆栈传参时,就会很难理解。
我在课堂上学习这两条指令的时候,老师完全没说堆栈的事,之后我发现有一个实验(输出一个集合的所有子集)是要用递归的技巧的,我当时就要骂人了,老师当时堆栈传参讲的不清不楚(说难听点,讲了和没讲是一样的),果断放弃这个选题,选了一个更简单的实验,但这样怎么体现出我认真的学习态度呢?(不要脸)
后来学习了王爽的书,弄懂了这些原理,堆栈传参就很好理解了。
最后补充一点:
ret n
——相当于pop IP
,然后add sp,n
C语言中局部变量也在堆栈中存放。
标志位DF与串处理指令
这部分老师在课堂上是不讲的(不知道其他学校是怎么样的)。其实这部分很简单。
方向标志位DF
- DF = 1,每次操作后
inc si, inc di
- DF = 2,每次操作后
dec si, dec di
cld
指令使DF=0,std
指令DF =1
下面用汇编指令(不符合语法)来理解下面两条指令
movsb
功能:
mov es:[di], byte ptr ds:[si]
;如果DF = 0
inc si
inc di
;如果DF = 1
dec si
dec di
movsw
功能:
mov es:[di], word ptr ds:[si]
;如果DF = 0
add si,2
add di,2
;如果DF = 1
sub si,2
sub di,2
rep指令
rep指令常常和以上两条指令配合使用。举个例子:
rep movsb
相当于
s: movsb
loop s ;根据CX决定循环次数
小结
串传送指令的注意点
- 传送的初始位置:ds : si
- 传送的目的位置:es : di
- 传送的长度: CX
- 传递的方向: 标志为DF
其他注意事项
由于篇幅原因(其实就是懒得再写下去了),还有一些注意点就不详细讨论了。
比如:
- 除法指令的溢出问题
- 80X25彩色字符模式的显示缓冲区
总结
汇编语言是一门相当自由的语言,只要你有耐心,你可以用它完成好多事情。
这种感觉是在学习王爽的教材中体会得到。在此之前,我只是通过学校课堂大概了解了一下汇编语言,我甚至不知道数据段,代码段,堆栈段是可以通过自己修改段寄存器来“定义”的(就是蠢,没别的解释)。但这种自由也带来一种坏处,就是代码的可读性较差,核心代码往往被其他操作掩盖(比如中间值传递,传参,保护寄存器),因此看似很长的代码,实际实现的功能很简单(这让我怎么显摆),甚至昨天刚写的代码,第二天居然读不懂了(好吧,我只是因为懒,没写注释,但不管怎么说,汇编就是难读!)。
但不管怎么说汇编还是很有用的。下面就用王爽书中的实验11——编写子程序,结束这篇博文。(我怎么还没写注释!反正没有老师看,偷个懒啦)
assume cs:codesg
datasg segment
db 'Beginner`s All-purpose Symbolic Instruction Code.',0
datasg ends
codesg segment
main: mov ax,datasg
mov ds,ax
mov si,0
call letterc
mov ax,4c00h
int 21h
;-----------------------------------------------------------
;proc_name: letterc
;function: translate uppercase into lowercase in a string which ends with digit 0
;interface: ds:si points to the first address of the string
letterc: push ax
push si
letterc_s: mov al,[si]
cmp al,'a'
jb letterc_next
cmp al,'z'
ja letterc_next
and al,11011111b
mov [si],al
letterc_next: inc si
cmp al,0
je letterc_out
jmp short letterc_s
letterc_out: pop si
pop ax
ret
;----------------------------------------------------------
codesg ends
end main