汇编语言:知识点拾遗
前言
读王爽的书已经有一段时间了,马上就要学习有关“中断”的知识了。在继续学习之前,我想把一些我感觉比较有意思的小细节,小技巧总结一下。文章不长,如果以后有新的收获,我会把这一系列继续写下去。
大小写字转换的技巧
一般来说,要想实现大小写字母的转换,有两种基本思路。
- 根据大小写字母在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 IPretf——相对于先pop IP,然后pop CScall s——相当于先push IP,然后jmp near ptr scall far ptr s——相当于先push CS,再push IP,最后jmp far ptr scall 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