汇编学习5(未完留坑

前言

通过在主引导扇区写一个程序加载器,来加载另一个程序到内存中,在这个过程中学习更多的语法以及知识点。

思路

操作系统其实也是位于硬盘上面的,计算机开机启动的时候,如果设置的是从硬盘启动,那么ROM-BIOS会先把主引导扇区的内容加载到内存里面,然后跳到那里进行执行。所以我们需要在主引导扇区写一个程序,在执行这个程序的时候,这个程序可以把位于硬盘的操作系统的一系列代码加载到内存的某个地方上,然后跳到那里执行。

被加载的用户程序

用户程序的整体代码:

;代码清单8-2
2 ;文件名:c08.asm
3 ;文件说明:用户程序
4 ;创建日期:2011-5-5 18:17
5
6 ;--------------------------------------------------------------------------------------------------------------------------------
7 SECTION header vstart=0 ;定义用户程序头部段
8 program_length dd program_end ;程序总长度[0x00]
9
10 ;用户程序入口点
11 code_entry dw start ;偏移地址[0x04]
12 dd section.code_1.start ;段地址[0x06]
13
14 realloc_tbl_len dw (header_end-code_1_segment)/4
15 ;段重定位表项个数[0x0a]
16
17 ;段重定位表
18 code_1_segment dd section.code_1.start ;[0x0c]
19 code_2_segment dd section.code_2.start ;[0x10]
20 data_1_segment dd section.data_1.start ;[0x14]
21 data_2_segment dd section.data_2.start ;[0x18]
22 stack_segment dd section.stack.start ;[0x1c]
23
24 header_end:
25
26 ;-------------------------------------------------------------------------------------------------------------------------------
27 SECTION code_1 align=16 vstart=0 ;定义代码段1(16字节对齐)
28 put_string: ;显示串(0结尾)。
29 ;输入:DS:BX=串地址
30 mov cl,[bx]
31 or cl,cl ;cl=0 ?
32 jz .exit ;是的,返回主程序
33 call put_char
34 inc bx ;下一个字符
35 jmp put_string
36
37 .exit:
38 ret
39
40 ;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
41 put_char: ;显示一个字符
42 ;输入:cl=字符ascii
43 push ax
44 push bx
45 push cx
46 push dx
47 push ds
48 push es
49
50 ;以下取当前光标位置
51 mov dx,0x3d4
52 mov al,0x0e
53 out dx,al
54 mov dx,0x3d5
55 in al,dx ;高8位
56 mov ah,al
57
58 mov dx,0x3d4
59 mov al,0x0f
60 out dx,al
61 mov dx,0x3d5
62 in al,dx ;低8位
63 mov bx,ax ;BX=代表光标位置的16位数
64
65 cmp cl,0x0d ;回车符?
66 jnz .put_0a ;不是。看看是不是换行等字符
67 mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
68 mov bl,80
69 div bl
70 mul bl
71 mov bx,ax
72 jmp .set_cursor
73
74 .put_0a:
75 cmp cl,0x0a ;换行符?
76 jnz .put_other ;不是,那就正常显示字符
77 add bx,80
78 jmp .roll_screen
79
80 .put_other: ;正常显示字符
81 mov ax,0xb800
82 mov es,ax
83 shl bx,1
84 mov [es:bx],cl
85
86 ;以下将光标位置推进一个字符
87 shr bx,1
88 add bx,1
89
90 .roll_screen:
91 cmp bx,2000 ;光标超出屏幕?滚屏
92 jl .set_cursor
93
94 mov ax,0xb800
95 mov ds,ax
96 mov es,ax
97 cld
98 mov si,0xa0
99 mov di,0x00
100 mov cx,1920
101 rep movsw
102 mov bx,3840 ;清除屏幕最底一行
103 mov cx,80
104 .cls:
105 mov word[es:bx],0x0720
106 add bx,2
107 loop .cls
108
109 mov bx,1920
110
111 .set_cursor:
112 mov dx,0x3d4
113 mov al,0x0e
114 out dx,al
115 mov dx,0x3d5
116 mov al,bh
117 out dx,al
118 mov dx,0x3d4
119 mov al,0x0f
120 out dx,al
121 mov dx,0x3d5
122 mov al,bl
123 out dx,al
124
125 pop es
126 pop ds
127 pop dx
128 pop cx
129 pop bx
130 pop ax
131
132 ret
133
134 ;-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
135 start:
136 ;初始执行时,DS和ES指向用户程序头部段
137 mov ax,[stack_segment] ;设置到用户程序自己的堆栈
138 mov ss,ax
139 mov sp,stack_end
140
141 mov ax,[data_1_segment] ;设置到用户程序自己的数据段
142 mov ds,ax
143
144 mov bx,msg0
145 call put_string ;显示第一段信息
146
147 push word [es:code_2_segment]
148 mov ax,begin
149 push ax ;可以直接push begin,80386+
150
151 retf ;转移到代码段2执行
152
153 continue:
154 mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
155 mov ds,ax
156
157 mov bx,msg1
158 call put_string ;显示第二段信息
159
160 jmp $
161
162 ;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
163 SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐)
164
165 begin:
166 push word [es:code_1_segment]
167 mov ax,continue
168 push ax ;可以直接push continue,80386+
169
170 retf ;转移到代码段1接着执行
171
172 ;-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
173 SECTION data_1 align=16 vstart=0
174
175 msg0 db ’ This is NASM - the famous Netwide Assembler. ’
176 db ‘Back at SourceForge and in intensive development! ’
177 db ‘Get the current versions from http://www.nasm.us/.’
178 db 0x0d,0x0a,0x0d,0x0a
179 db ’ Example code for calculate 1+2+…+1000:’,0x0d,0x0a,0x0d,0x0a
180 db ’ xor dx,dx’,0x0d,0x0a
181 db ’ xor ax,ax’,0x0d,0x0a
182 db ’ xor cx,cx’,0x0d,0x0a
183 db ’ @@:’,0x0d,0x0a
184 db ’ inc cx’,0x0d,0x0a
185 db ’ add ax,cx’,0x0d,0x0a
186 db ’ adc dx,0’,0x0d,0x0a
187 db ’ inc cx’,0x0d,0x0a
188 db ’ cmp cx,1000’,0x0d,0x0a
189 db ’ jle @@’,0x0d,0x0a
190 db ’ … …(Some other codes)’,0x0d,0x0a,0x0d,0x0a
191 db 0
192
193 ;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
194 SECTION data_2 align=16 vstart=0
195
196 msg1 db ’ The above contents is written by LeeChung. ’
197 db ‘2011-05-06’
198 db 0
199
200 ;-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
201 SECTION stack align=16 vstart=0
202
203 resb 256
204
205 stack_end:
206
207 ;-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
208 SECTION trail align=16
209 program_end:

细节

该用户程序 使用SECTION 段名称 来对程序进行了分段。
Inter处理器要求段在内存中的起始物理地址起码是16字节对齐的,也就是说能被16整除。
vstart的作用:
如果没有给出vstart,那么这个段的出现的标号的汇编地址是相对于整个程序而言,而如果写vstart=0,那么该段标号的汇编地址是相对于该段计算的。并且从0开始计算。
用户程序的头部:
用户程序必须要在开头提供一些信息给加载程序,才能让加载器准确的把用户程序加载到内存的某一片区域。
用户程序起码要包含以下信息:
1.用户程序的大小(以字节为单位)加载器根据这个来确定读多少逻辑扇区
2.应用程序的入口点,包括段地址和偏移地址(给出第一条指令在用户程序中的位置)
3.段重定位表,用户程序可能不止分了一个段,程序加载到内存中之后,每个段的地址必须重新确定一下,段的重定位工作是加载器的工作。

加载器(写在主引导扇区上的程序)的工作流程

初始化和决定加载位置

其一,加载器需要知道内存哪片区域是空闲的,才能把用户程序加载到这个上面。其二,加载器需要知道用户程序位于硬盘的哪里,找到它。一般,物理地址0x0FFFF以下,是加载器以及栈的势力范围,物理地址A0000以上,是BIOS和外围设备的势力范围。可用空间一般是0x10000-9FFFF

外围设备及其接口

外围设备统称为输入输出设备,外围设备与处理器打交道是通过I/O接口的,更细致来说是通过I/O接口上的端口来打交道,而端口的实质就是寄存器。
在一些计算机系统中,端口是直接映射到内存中的,而另一些计算机系统中,端口是独立编址的,不直接和内存相联系。
所有的端口都是编了号的,比如 接口A就有3个端口,分别编号为0x0021-0023
端口的访问是使用 in 和 out指令的。
in指令是从端口读数据
in指令的目的操作数必须是AL或者AX,当访问8位寄存器的时候用AL,访问16位寄存器的时候用AX,in的源操作数应当是寄存器DX
out指令是向端口发送数据
out指令与in相反,目的操作数可以是8位立即数或者寄存器DX,源操作数必须是寄存器AL或者AX。

通过硬盘控制器端口读取扇区的数据

因为被加载的用户程序实际是位于硬盘上面的,以扇区为单位。
个人计算机上的主硬盘控制器被分配了8位端口,端口号从0x1f0到0x1f7。
整个过程:
第一步,设置要读取的扇区数量。这个数值要写入0x1f2端口
例如:
mov dx,0x1f2
mov al,0x01 //读一个扇区
out dx,al
第二步,设置起始的扇区号(读写是连续的)
这里的扇区号是28位的,因为都是8位寄存器,所以得分成4段放入。0x1f3端口放0-7位(这里指的是从右往左);0x1f4放8-15位;0x1f5放16-23位,最后4位在0x1f6端口,假设要读取的扇区起始号为0x02
mov dx,0x1f3
mov al,0x02
out dx,al
inc dx
mov al,0x00
out dx,al
inc dx
out dx,al
inc dx
mov al,0xe0 //注意这里用的0xe0
out dx,al
0x1f6端口对应的模式
在这里插入图片描述
第三步,向端口0x1f7写入0x20,请求硬盘读
mov dx,0x1f7
mov al,0x20
out dx,al
**第四步,等待读写操作完成,**端口0x1f7是命令端口,也是状态端口。通过端口发送这个命令之后,硬盘就开始忙了,它将0x1f7的第7位置1,表示很忙,一旦硬盘准备就绪,它将此位清0,并且将第三位置1,表示准备好了,请求主机发送或者接受数据。
完成这一步的典型代码:
mov dx,0x1f7
.waits:
in al,dx
and al,0x88
cmp al,0x88
jnz.waits
第五步,连续取出数据,0x1f0是硬盘接口的数据端口,(还是16位的),一旦硬盘空闲并且准备就绪,就可以连续从这个端口写入或者读出数据,读出来的数据存放到由DS寄存器指定的数据段,偏移地址为BX的地方。

段的重定位

用户程序加载到内存,以分段形式存在是依靠加载器的代码来实现的。

整个加载器的代码

1 ;代码清单8-1
2 ;文件名:c08_mbr.asm
3 ;文件说明:硬盘主引导扇区代码(加载程序)
4 ;创建日期:2011-5-5 18:17
56 app_lba_start equ 100 ;声明常数(用户程序起始逻辑扇区号)
7 ;常数的声明不会占用汇编地址
89 SECTION mbr align=16 vstart=0x7c00 //这里使用了vstart=0x7c00,所有汇编地址相对这个地址计算
10
11 ;设置堆栈段和栈指针
12 mov ax,0
13 mov ss,ax
14 mov sp,ax
15
16 mov ax,[cs:phy_base] ;计算用于加载用户程序的逻辑段地址 //这里的CS寄存器的内容是0x0000
17 mov dx,[cs:phy_base+0x02]
18 mov bx,16
19 div bx ;这里要除以16,使得DS段寄存器放的是0x1000,而不是0x10000
20 mov ds,ax ;令DS和ES指向该段以进行操作
21 mov es,ax //这个时候,ES和DS都指向了0x1000
22
23 ;以下读取程序的起始部分
24 xor DI,DI
25 mov SI,app_lba_start ;程序在硬盘上的起始逻辑扇区号
26 xor BX,BX ;加载到DS:0x0000处
27 call read_hard_disk_0 //调用
28
29 ;以下判断整个程序有多大
30 mov dx,[2] ;这里的[2]等效于[DS:0x02]
31 mov ax,[0]
32 mov bx,512 ;512字节每扇区
33 div bx
34 cmp dx,0
35 jnz @1 ;未除尽,因此结果比实际扇区数少1
36 dec ax ;已经读了一个扇区,扇区总数减1
37 @1:
38 cmp ax,0 ;考虑实际长度小于等于512个字节的情况
39 jz direct
40
41 ;读取剩余的扇区
42 push ds ;以下要用到并改变DS寄存器
43
44 mov cx,ax ;循环次数(剩余扇区数)
45 @2:
46 mov ax,ds
47 add ax,0x20 ;得到下一个以512字节为边界的段地址
48 mov ds,ax
49
50 xor bx,bx ;每次读时,偏移地址始终为0x0000
51 inc si ;下一个逻辑扇区
52 call read_hard_disk_0
53 loop @2 ;循环读,直到读完整个功能程序
54
55 pop ds ;恢复数据段基址到用户程序头部段
56
57 ;计算入口点代码段基址
58 direct:
59 mov dx,[0x08]
60 mov ax,[0x06]
61 call calc_segment_base
62 mov [0x06],ax ;回填修正后的入口点代码段基址
63
64 ;开始处理段重定位表
65 mov cx,[0x0a] ;需要重定位的项目数量
66 mov bx,0x0c ;重定位表首地址
67
68 realloc:
69 mov dx,[bx+0x02] ;32位地址的高16位
70 mov ax,[bx]
71 call calc_segment_base
72 mov [bx],ax ;回填段的基址
73 add bx,4 ;下一个重定位项(每项占4个字节)
74 loop realloc
75
76 jmp far [0x04] ;转移到用户程序
77
78 ;-------------------------------------------------------------------------------
79 read_hard_disk_0: ;从硬盘读取一个逻辑扇区
80 ;输入:DI:SI=起始逻辑扇区号
81 ; DS:BX=目标缓冲区地址
82 push ax
83 push bx
84 push cx
85 push dx
86
87 mov dx,0x1f2
88 mov al,1
89 out dx,al ;读取的扇区数
90
91 inc dx ;0x1f3
92 mov ax,si
93 out dx,al ;LBA地址7~0
94
95 inc dx ;0x1f4
96 mov al,ah
97 out dx,al ;LBA地址15~8
98
99 inc dx ;0x1f5
100 mov ax,di
101 out dx,al ;LBA地址23~16
102
103 inc dx ;0x1f6
104 mov al,0xe0 ;LBA28模式,主盘
105 or al,ah ;LBA地址27~24
106 out dx,al
107
108 inc dx ;0x1f7
109 mov al,0x20 ;读命令
110 out dx,al
111
112 .waits:
113 in al,dx
114 and al,0x88
115 cmp al,0x08 //如果al等于0x08则退出循环
116 jnz .waits ;不忙,且硬盘已准备好数据传输
117
118 mov cx,256 ;总共要读取的字数(注意这里是字数,字节数得乘以2)
119 mov dx,0x1f0
120 .readw:
121 in ax,dx
122 mov [bx],ax
123 add bx,2
124 loop .readw //cx=256,故会循环256次,一次读取2个字节,共读取512个字节
125
126 pop dx
127 pop cx
128 pop bx
129 pop ax
130
131 ret
132
133 ;-------------------------------------------------------------------------------
134 calc_segment_base: ;计算16位段地址
135 ;输入:DX:AX=32位物理地址
136 ;返回:AX=16位段基地址
137 push dx
138
139 add ax,[cs:phy_base]
140 adc dx,[cs:phy_base+0x02]
141 shr ax,4
142 ror dx,4
143 and dx,0xf000
144 or ax,dx
145
146 pop dx
147
148 ret
149
150 ;-------------------------------------------------------------------------------
151 phy_base dd 0x10000 ;用户程序被加载的物理起始地址
152
153 times 510-( − - $) db 0
154 db 0x55,0xaa

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值