第十六章 直接定址表
16.1 描述了单位长度的标号
地址标号,表征了位置的偏移地址
label:
数据标号,表征了一段内存空间的物理地址和长度,增强型地址标号
段地址,数据标号所在段的关联段寄存器,assume
注意要提前准备好相关的段寄存器
偏移地址,地址标号
长度,伪指令,[db,dw,dd]
使用
定义
assume ds:data
data segment
label dw idata1, idata2
data ends
直接使用
mov ax, label
等价于
mov word ptr ax, ds:[offset label]
偏移使用
mov ax, label[2]
mov ax, label[si]
等价于
mov word ptr ax, ds:[offset label+2]
mov word ptr ax, ds:[offset label+si]
16.2 在其他段中使用数据标号
数据标号,和地址标号一样支持伪指令
offset,获取标号的偏移地址
seg,获取标号的段地址
16.3 直接定址表
任务,打印字节的十六进制表示,如256表示为FF
一个字节可以拆成,高四位和低四位,各表示成一个字节
如何将一个字节(00~0F)表示为一个字符('0'~'F')
逻辑比较
bx=0~9 ax=bx+30h
bx=a~f ax=bx-a+'a'
查表法
table db '0123456789ABCDEF'
mov ax, table[bx]
利用表,建立下标到数据集合的一种映射关系,便于根据下标获取映射数据
目的
为了算法的清晰和简洁
为了加快运算速度,以空间换时间
为了使用程序易于扩充,代码变化度分离,开闭原则
查表法
用查表的依据数据,直接计算出目标元素的位置
用于查表法的表,称为直接定址表
16.4 程序入口地址的字节定址表
任务
一个子程序,有多个次级子程序构成
根据参数,自动调用次级子程序
方案一,查表法
fun1:
jmp short fun1_start
table dw sub1, sub2, sub3
fun1_start:
push bx
cmp ah, 2
ja fun1_ret
mov bl, ah
mov bh, 0
add bx, bx
call word ptr table[bx]
fun1_ret:
pop bx
ret
方案二,逻辑比较法
fun1:
cmp ah, 0
jne fun1_1
call sub1
jmp fun1_ret
fun1_1:
cmp ah, 1
jne fun1_2
call sub2
jmp fun1_ret
fun1_2:
cmp ah, 2
jne fun1_ret
call sub2
fun1_ret:
ret
16.5 实验16 编写包含多个功能子程序的中断例程
第十七章 使用BIOS进行键盘输入和磁盘读写
17.1 int9中断例程对键盘输入的处理
数据结构
键盘缓冲区,16个字,15个输入,一个输入包含一个扫描码及其对应的ASCII码
键盘缓冲区以循环队列实现
头指针和尾指针,可能存放在剩下的字中
状态字节,40:17
行为
按下字符键
读出通码,从60h端口
存入尾指针,尾指针自增
产生ASCII码,根据状态字节
存入尾指针,尾指针自增
松开字符键
忽略
按下控制键
更新状态字节
松开控制键
更新状态字节
概述
int9中断例程,将当前输入循环写入字符缓冲区
17.2 使用int16h中断例程读取键盘缓冲区
例子
mov ah, 0
int 16h
解释
int16h中断例程中的0号子程序,用于从键盘缓冲区中读取一个键盘输入
读取的流程
检测键盘缓冲区,直到有数据
读取头指针指向的数据,ASCII码进入AL,扫描码进入AH
头指针循环自增
在int16h中断例程中,一定设有IF=1的指令
在等待键盘缓冲区中有数据时,需要产生键盘输入中断,使得int9h中断例程向键盘缓冲区送入键盘输入
17.3 字符串的输入
字符串输入程序
调用int16h读取键盘输入
若是字符,则压入字符栈,显示字符栈中所有数据
若是退格键,则字符栈弹出数据,显示字符栈中所有数据,注意擦除上一次的显示
若是回车键,则将0压入字符栈,返回
例子
; sub-program, char stack push, pop, show
; parameters
; ah, function index
; 0, push
; 1, pop
; 2, show
; ds:si, char stack address
; al
; fun0, char will push
; fun1, char will pop
; dh, dl, row and column for show
char_stack:
jmp short char_stack_start
table dw char_stack_push, char_stack_pop, char_stack_show
top dw 0
char_stack_start:
push bx
cmp ah, 2
ja char_stack_ret
mov bl, ah
mov bh, 0
add bx, bx
jmp word ptr table[bx]
char_stack_push:
mov bx, top
mov [si][bx], al
inc top
jmp char_stack_ret
char_stack_pop:
cmp top, 0
je char_stack_ret
dec top
mov bx, top
mov al, [si][bx]
jmp char_stack_ret
char_stack_show:
push di
push es
push ax
push dx
push cx
mov bx, 0b800h
mov es, bx
mov ah, 0
mov al, 160
mul dh
mov dh, 0
add ax, dx
add ax, dx
mov di, ax
mov cx, top
jcxz char_stack_show_e
mov bx, 0
char_stack_show_s:
mov al, [si][bx]
mov es:[di], al
add di, 2
inc bx
loop char_stack_show_s
char_stack_show_e:
mov byte ptr es:[di], ' '
pop cx
pop dx
pop ax
pop es
pop di
char_stack_ret:
pop bx
ret
; read string
; sub-program, char stack push, pop, show
; parameters
; ds:si, char stack address
get_str:
push ax
get_str_s:
mov ah, 0
int 16h
cmp al, 20h
jb get_str_not_char
mov ah, 0
call char_stack
mov ah, 2
call char_stack
jmp short get_str_s
get_str_not_char:
cmp ah, 0eh
je get_str_backspace
cmp ah, 1ch
je get_str_enter
jmp short get_str_s
get_str_backspace:
mov ah, 1
call char_stack
mov ah, 2
call char_stack
jmp short get_str_s
get_str_enter:
mov al, 0
mov ah, 0
call char_stack
mov ah, 2
call char_stack
pop ax
ret
注意
mov es:[di], ' '
这句话不会报错,但是会按字进行传送,导致逻辑错误
直接运行和debug运行结果不一样,因为debug中操作的显存一致在变化
17.4 使用int13h中断例程对磁盘进行读写
3.5英寸软盘
一共上下两面,一面一个磁头
每面80个磁道
每个磁道18个扇区
每个扇区512字节
软盘共计1440KB,大约1.44MB
一盘共两面,一面八十道,一道十八区,一区五一二
磁盘的实际访问,由磁盘驱动程序完成,我们通过指令控制磁盘控制器来访问磁盘
直接操作磁盘控制器,涉及许多硬件细节,可以使用BIOS中的13h中断例程
磁盘访问
以扇区为单位
读写扇区,需要给出面号、磁道号、扇区号
面号和磁道号,从0开始
扇区号,从1开始
int13h
参数
ah 功能号,2为读扇区,3为写扇区
al 读写的扇区数
dl 驱动器号
软驱从0开始,0为软驱A,1为软驱B
硬盘从80h开始,80h为硬盘C,81h为硬盘D
dh 磁头号
ch 磁道号
cl 扇区号
es:bx 读写扇区的内存区
返回值
成功 ah=0 al=读写的扇区数
失败 ah=出错代码
例子1
将0面0磁道1扇区,读到0:200h
assume cs:code
stack segment stack
db 128 dup (0)
stack ends
data segment
start:
mov ax, 0
mov es, ax
mov bx, 200h
mov ah, 2
mov al, 1
mov dl, 0
mov dh, 0
mov ch, 0
moc cl, 1
int 13h
data ends
end start
例子2
将当前屏幕数据写入软盘
assume cs:code
stack segment stack
db 128 dup (0)
stack ends
data segment
start:
mov ax, 0b800h
mov es, ax
mov bx, 0
mov ah, 3
mov al, 8
mov dl, 0
mov dh, 0
mov ch, 0
moc cl, 1
int 13h
mov ax, 4c00h
int 21h
data ends
end start
结果
读取成功
写入似乎失败了,0~FFF全是FF
17.5 实验17 编写包含多个功能子程序的中断例程
17.6 课程设计2
材料
开机后,CPU的CS:IP被初始化为FFFF:0,在该处有一条跳转指令
跳转去执行BIOS中的硬件系统检测和初始化程序
建立BIOS支持的中断向量表
调用int19h进行操作系统的引导
若从0号软驱启动
读取其0面0道1扇区的数据(操作系统引导程序),到0:7c00
跳转到0:7c00
执行程序从而激活操作系统
若0号软驱没有软盘,或发生软盘IO错误
读取硬盘C的0面0道1扇区的数据,到0:7c00
跳转到0:7c00,引导操作系统
第十八章 综合研究
目的
启示如何进行独立研究和深度思考
认识到汇编语言对于深入理解其他领域知识的重要性
融会贯通汇编语言知识
体验用研究的方法进行学习
打破常规,怀疑常见,提升认识
研究试验1 搭建一个精简的C语言开发环境
只使用必要的文件
复制tc.exe
清空目录配置
编译连接测试,复制依赖的文件
例子程序
main()
{
prnitf("Hello world!\n");
}
研究试验2 使用寄存器
例1
main()
{
_AX = 1;
_BX = 1;
_CX = 1;
_AX = _BX + _CX;
_AH = _BL + _CL;
_AL = _BH + _CH;
}
debug查看其代码
例2
main()
{
_AX = 1;
_BX = 1;
_CX = 1;
_AX = _BX + _CX;
_AH = _BL + _CL;
_AL = _BH + _CH;
printf("%x\n", main);
}
打印main函数在代码段中的偏移地址,查看C代码和汇编代码的对应关系
main的入口地址为1fa
例3
void f(void);
main()
{
_AX = 1;
_BX = 1;
_CX = 2;
f();
}
void f(void)
{
_AX = _BX + _CX;
}
main的入口地址为1fa
函数main和f都被编译为子程序
部分汇编代码
208A:01FA 55 PUSH BP
208A:01FB 8BEC MOV BP, SP
208A:01FD B80100 MOV AX, 0001
208A:0200 BB0100 MOV BX, 0002
208A:0203 B90200 MOV CX, 0002
208A:0206 E80200 CALL 020B
208A:0209 5D POP BP
208A:020A C3 RET
208A:020B 55 PUSH BP
208A:020C 8BEC MOV BP, SP
208A:020E 8BC3 MOV AX, BX
208A:0210 03C1 ADD AX, CX
208A:0212 5D POP BP
208A:0213 C3 RET
研究试验3 使用内存空间
存储空间的信息
位置
长度
寄存器
位置,寄存器名称
长度,寄存器名称
内存空间
位置,首地址信息
长度,空间存储数据的类型信息
例子
*(char *)0x2000='a'
将(char *)0x2000作为内存空间信息,段地址为ds,偏移地址为0x2000,类型为char
语句含义,向内存空间((char *)0x2000)写入'a'
*(char far *)0x20000000='a'
将(char far *)0x20000000作为内存空间信息,段地址为0x2000,偏移地址为0,类型为char
语句含义,向内存空间((char *)0x20000000)写入'a'
用地址直接访问内存空间是不安全的,可能会覆盖重要数据
例1
mian()
{
*(char *)0x2000 = 'a';
// mov ax, 0
// mov es, ax
// mov byte ptr es:[2000], 'a'
*(int *)0x2000 = 0xf;
// mov word ptr es:[2000], 0fh
*(char far *)0x20001000 = 'a';
// mov ax, 02000h
// mov es, ax
// mov byte ptr es:[1000], 'a'
_AX = 0x2000;
// mov ax, 02000h
*(char *)_AX = 'b';
// mov bx, 0
// mov es, bx
// mov bx, ax
// mov byte ptr es:[bx], 'b'
_BX = 0x1000;
// mov bx, 01000h
*(char*)(_BX+_BX) = 'a';
// add bx, bx
// mov byte ptr es:[bx], 'a'
*(char far *)(0x20001000+_BX) = *(char *)_AX;
// mov cx, 0
// mov es, cx
// mov bp, ax
// mov dl, es:[bp]
// mov cx, 02000h
// add bx, 01000h
// jnc no_carry
// add cx, 0100h
// no_carry:
// mov es, cx
// mov es:[bx], dl
}
汇编代码
*(char *)0x2000 = 'a';
// mov byte ptr [2000], 61h
*(int *)0x2000 = 0xf;
// mov word ptr [2000], 0fh
*(char far *)0x20001000 = 'a';
// mov bx, 02000h
// mov es, bx
// mov bx, 01000h
// mov byte ptr es:[bx], 61h
_AX = 0x2000;
// mov ax, 2000h
*(char *)_AX = 'b';
// mov bx, ax
// mov byte ptr [bx], 62h
_BX = 0x1000;
// mov bx, 01000h
*(char*)(_BX+_BX) = 'a';
// add bx, bx
// mov byte ptr [bx], 61h
*(char far *)(0x20001000+_BX) = *(char *)_AX;
// mov bx, ax
// mov al, [bx]
// xor cx, cx
// add bx, 1000h
// adc cx, 2000h
// mov es, cx
// mov es:[bx], al
例2
在屏幕中间打印绿色a
*(int far *)0xb80007d0 = 0x0261;
例3
int a1, a2, a3;
void f(void);
main()
{
int b1, b2, b3;
a1 = 0xa1; a2 = 0xa2; a3 = 0xa3;
b1 = 0xb1; b2 = 0xb2; b3 = oxb3;
}
void f(void)
{
int c1, c2, c3;
a1 = 0x0fa1; a2 = 0x0fa2; a3 = 0x0fa3;
c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
}
汇编代码
push bp
mov bp, sp
sub sp, 6
mov word ptr ds:[01a6h], 0a1h
mov word ptr ds:[01a8h], 0a2h
mov word ptr ds:[01aah], 0a3h
mov word ptr ss:[bp-6], 0b1h
mov word ptr ss:[bp-4], 0b2h
mov word ptr ss:[bp-2], 0b3h
mov sp, bp
pop bp
ret
push bp
mov bp, sp
sub sp, 6
mov word ptr ds:[01a6h], 0fa1h
mov word ptr ds:[01a8h], 0fa2h
mov word ptr ds:[01aah], 0fa3h
mov word ptr ss:[bp-6], 0c1h
mov word ptr ss:[bp-4], 0c2h
mov word ptr ss:[bp-2], 0c3h
C语言将全局变量放在数据段,将局部变量放在栈中
例4
int f(void);
int a, b, ab;
main()
{
int c;
c = f();
}
int f(void)
{
ab = a + b;
return ab;
}
汇编代码
push bp
mov bp, sp
sub bp, 2
call 020a
mov ss:[bp-2]. ax
mov sp, bp
pop bp
ret
020a push bp
mov bp, sp
mov ax, ds:[01a6]
add ax, ds:[01a8]
mov ds:[01aa], ax
mov ax, ds:[01aa]
jmp 021c
021c pop bp
ret
C语言将函数返回值放在寄存器AX中
例5
#define Buffer ((char *)*(int far *)0x200)
main()
{
Buffer = (char *)malloc(20);
Buffer[10] = 0;
while (Buffer[10] != 8)
{
Buffer[Buffer[10]] = 'a' + Buffer[10];
Buffer[10]++;
}
free(Buffer);
}
解释
*(int far *)0x200
地址为200:0的int型内存空间
((char *)*(int far *)0x200)
将int型内存空间,转化为char*型内存空间,用于存放char数组首地址
汇编代码
push bp
mov bp, sp
mov ax, 14h
push ax
call 04ddh
pop cx
xor bx, bx
mov es, bx
mov bx, 200h
mov es:[bx], ax
xor bx, bx
mov es, bx
mov bx, 200h
mov bx, es:[bx]
mov byte ptr [bx+10], 0
jmp 025bh
xor bx, bx ;021fh
mov es, bx
mov bx, 200h
mov bx, es:[bx]
mov al, [bx+10]
add al, 'a'
xor bx, bx
mov es, bx
mov bx, 200h
mov bx, es:[bx]
push ax
push bx
xor bx, bx
mov es, bx
mov bx, 200h
mov bx, es:[bx]
mov al, [bx+10]
CBW
pop bx
add bx, ax
pop ax
mov [bx], al
xor bx, bx
mov es, bx
mov bx, 200h
mov bx, es:[bx]
inc byte ptr [bx+10]
xor bx, bx ;025bh
mov es, bx
mov bx, 200h
mov bx, es:[bx]
cmp byte ptr [bx+10], 8
jnz 021f
xor bx, bx
mov es, bx
mov bx, 200h
push es:[bx]
call 06e8h
call cx
pop bp
ret
研究试验4 不用main函数编程
试验1
// t41.c
f()
{
*(char far *) (0xb8000000+12*160+40*2) = 'a'
*(char far *) (0xb8000000+12*160+40*2 + 1) = 2
}
使用TC编译成功,连接失败
Link Error: Undefined symbol '_main' in module C0S
C0S.obj中调用了_main函数,但是找不到定义
试验2
使用link.exe连接t41.obj成功
汇编代码
push bp
mov bp, sp
mov bx, 0b800h
mov es, bx
mov bx, 07d0h
mov byte ptr es:[bx], 'a'
mov bx, 0b800h
mov es, bx
mov bx, 07d1h
mov byte ptr es:[bx], 2
pop bp
ret
一共1d字节(CX)
C语言函数被编译为子程序,子程序结尾使用ret,而不是int21h,不能正确返回
f函数被编译在代码段首地址,偏移地址为0
试验3
将f函数重命名为main,编译连接成功
程序长度为0ebe字节
试验4
调用main函数的指令的位置,208c:011a
可以打印出main的偏移地址1fa
执行程序到main函数开头,g 1fa
查看栈顶保存的返回地址011d
反汇编调用指令,u cs:011a
程序的返回指令位置,208c:0151
在main函数调用位置,继续往下反汇编
找到"INT 21"
试验5
对main函数的调用指令和程序返回指令来自于C0S.obj
对C0S.obj单独进行连接,会出现连接错误,但仍然生成了C0S.exe,使用debug查看
调用main函数的指令的位置相同
程序返回指令的位置相同
tc.exe会将C0S.obj和用户的obj进行连接,程序运行过程如下
C0S.obj中的程序作为程序入口,被首先运行,会进行相关的初始化
申请资源、设置DS和SS等
C0S.obj调用main函数,开始执行用户程序
用户程序返回,C0S.obj接着执行
资源释放,环境恢复
C0S.obj调用int21h的4ch子程序,程序返回
在C语言中,main函数算是一个回调函数,是C语言开发系统和用户程序进行衔接的调用约定
C语言开发系统,会在调用main前,进行系统的初始化,在调用main后,进行系统的资源释放和环境恢复
C语言开发系统把这组程序,放在了C0S.obj中
自定义C0S.obj,并连接用户程序t41.obj
编译C0S.asm
assume cs:code
data segment stack
db 128 dup (0)
data ends
code segment
start: call s
mov ax, 4c00h
int 21h
s:
code ends
end start
TC连接并执行成功
汇编代码
call 8
mov ax, 4c00h
int 21h
push bp
mov bp, sp
mov bx, 0b800h
mov es, bx
mov bx, 07d0h
mov byte ptr es:[bx], 'a'
mov bx, 0b800h
mov es, bx
mov bx, 07d1h
mov byte ptr es:[bx], 2
pop bp
ret
可以发现,连接后,t41.obj代码段中的指令直接跟在C0S.obj的代码段尾
使用C0S.obj,并连接新用户程序
#define Buffer ((char *)*(int far *)0x200)
main()
{
Buffer = 0;
Buffer[10] = 0;
while (Buffer[10] != 8)
{
Buffer[Buffer[10]] = 'a' + Buffer[10];
Buffer[10]++;
}
}
研究试验5 函数如何接收不定数量的参数
试验1
t51.c
void show_char(char a, int b);
main()
{
show_char('a', 2);
}
void show_char(char a, int b)
{
*(char far *) (0xb8000000+12*160+40*2) = a;
*(char far *) (0xb8000000+12*160+40*2 + 1) = b;
}
汇编代码
push bp
mov bp, sp
mov ax, 2
push ax
mov ax, 'a'
push ax
call show_char
pop cx
pop cx
pop bp
ret
show_char:
push bp
mov sp, sp
mov al, [bp+4]
mov bx, 0b800h
mov es, bx
mov bx, 07d0h
mov es:[bx], al
mov al, [bp+6]
mov bx, 0b800h
mov es, bx
mov bx, 07d1h
mov es:[bx], al
pop bp
ret
函数调用过程
将函数参数,从右到左,依次入栈
call 函数标号
call的下一指令地址入栈
函数调用
bp入栈
bp = sp
sp调整,为局部变量分配空间
程序逻辑
sp = bp
bp出栈
ret
call的下一指令地址出栈
函数参数出栈
试验2
// t52.c
void show_char(int, int, ...);
main()
{
show_char(8,2,'a','b','c','d','e','f','g','h');
}
void show_char(int n, int color, ...)
{
int a;
for (a = 0; a < n; ++a)
{
*(char far *) (0xb8000000+12*160+40*2+a+a) = *(int *)(_BP+8+a+a);
*(char far *) (0xb8000000+12*160+40*2+a+a+1) = color;
}
}
汇编代码
push bp
mov bp, sp
mov ax, 68h
push ax
mov ax, 67h
push ax
mov ax, 66h
push ax
mov ax, 65h
push ax
mov ax, 64h
push ax
mov ax, 63h
push ax
mov ax, 62h
push ax
mov ax, 61h
push ax
mov ax, 2
push ax
mov ax, 8
push ax
call show_char
add sp, 14h
pop bp
ret
show_char:
push bp
mov bp, sp
push si
xor si, si
jmp show_char_while_cmp
show_char_while_start:
mov bx, bp
add bx, 8
inc bx
inc bx
mov al, [bx]
push ax
mov ax, si
cwd
push dx
push ax
mov ax, si
cwd
pop bx
pop cx
add bx, ax
adc cx, dx
add bx, 7d0h
adc cx, 0b800
mov es, cx
pop ax
mov es:[bx], al
mov al, [bp+6]
push ax
mov ax, si
cwd
push dx
push ax
mov ax, si
cwd
pop bx
pop cx
add bx, ax
adc cx, dx
add bx, 7d1h
adc cx, 0b800
mov es, cx
pop ax
mov es:[bx], al
inc si
show_char_while_cmp:
cmp si, [bp+4]
jl show_char_while_start
pop si
pop bp
ret
试验3
void my_printf(char * f, ...)
{
int a, b;
for (a = 0, b = 0; ; a+=2)
{
if (*f == 0) break;
if (*f == '%' && *(f+1) == 'c')
{
*(char far *) (0xb8000000+12*160+40*2+a) = *(int *)(_BP+6+b);
f += 2;
b += 2;
}
else
{
*(char far *) (0xb8000000+12*160+40*2+a) = *f;
++f;
}
}
}
main()
{
my_printf("Hi %c%c, welcome", 'W', 'R');
}