大概的流程是加载器先把磁盘中的应用程序加载到内存并把执行权移交给应用程序。分为以下几个步骤:
从磁盘读取应用程序并装入内存(加载器的作用1)。
应用程序被装入内存后需要加载器对内存中的应用程序部分地址进行重定位(加载器的作用2)。
加载器将执行权移交应用程序(加载器的作用3)。
前面说过,部分外围设备将自己的地址空间直接映射到内存地址空间中,这样CPU可以直接访问该内存空间来操作外围设备,譬如显卡。但这里磁盘空间是独立编址的,我们必须通过IO来访问(即端口访问),为什么CPU能直接进行端口访问磁盘,因为CPU和外围设备(这里是磁盘)之间直接进行电路焊接的。主要通过指令OUT来设置/写磁盘相关端口参数,通过指令IN来读磁盘数据。
既然加载器通过IN/OUT方式读写磁盘数据到内存中,必须要知道磁盘数据大小即应用程序大小、放在磁盘什么位置(哪个扇区)。因此加载器和应用程序之间有个协议约定,即程序头,该程序头结构中主要信息包括程序总长度、用户程序入口点、段重定位表项个数、段重定位表。该程序头结构在编译器编译程序时自动生成。
程序头结构如下(举例):
SECTION header vstart=0 ;定义用户程序头部段 ,从0地址开始
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06]
realloc_tbl_len dw (header_end-code_1_segment)/4;段重定位表项个数[0x0a]
;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c]
header_end:
加载器在加载程序时,先获取程序头中内容,并从应用程序所在磁盘逻辑扇区号中开始加载到指定内存地址中,因为磁盘每个扇区只有512字节,所以加载器都以512字节为一个加载块加载到指定的内存地址中,没存地址中的每个加载块之间有个地址间隔(8086为0x20)。
加载器加载完应用程序后,需要进行地址重定位。因为定义的各种段地址起始地址都是从0开始的,加载到内存中是存放到指定内存地址中去的。
如下所示:
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06]
realloc_tbl_len dw (header_end-code_1_segment)/4
;段重定位表项个数[0x0a]
;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c]
header_end:
由于该段vstart = 0,所以编译后起始地址为0.其他段定义雷同。这就需要进行段地址重定位。
段地址重定位完成后,使用jump far/near(或省略near即为near近调整) 重定位表中的应用程序的入口地址(即将CPU控制权交给应用程序)。
应用程序从入口地址执行时,即需要根据不同段地址配置不同段寄存器,即使用堆栈段地址配置SS寄存器,使用堆栈段end地址赋值SP寄存器。使用数据段地址赋值DS寄存器,如果用户定义了多个数据段怎么办?在访问数据段1过程中需要访问另一个数据段2时,先将进行中的数据段(假设为数据段1 )push入栈,然后重定位表中的数据段2所在的地址重新赋值给DS,然后再访问DS,访问结束后,再将原有入栈的数据段出栈恢复到DS中(或另一种方法,再次访问重定位表数据段1的地址,重新设置DS寄存器即可)。CS寄存器的设置是在加载器中进行的,试想一下,加入加载器不进行CS寄存器设置,它如何执行应用程序中的入口地址呢。
;代码清单8-1//加载器源代码
;文件名:c08_mbr.asm
;文件说明:硬盘主引导扇区代码(加载程序)
;创建日期:2011-5-5 18:17
app_lba_start equ 100 ;声明常数(用户程序起始逻辑扇区号)
;常数的声明不会占用汇编地址
SECTION mbr align=16 vstart=0x7c00
;设置堆栈段和栈指针
mov ax,0
mov ss,ax
mov sp,ax
mov ax,[cs:phy_base] ;计算用于加载用户程序的逻辑段地址
mov dx,[cs:phy_base+0x02]
mov bx,16
div bx
mov ds,ax ;令DS和ES指向该段以进行操作
mov es,ax
;以下读取程序的起始部分
xor di,di
mov si,app_lba_start ;程序在硬盘上的起始逻辑扇区号
xor bx,bx ;加载到DS:0x0000处
call read_hard_disk_0
;以下判断整个程序有多大
mov dx,[2] ;曾经把dx写成了ds,花了二十分钟排错
mov ax,[0]
mov bx,512 ;512字节每扇区
div bx
cmp dx,0
jnz @1 ;未除尽,因此结果比实际扇区数少1
dec ax ;已经读了一个扇区,扇区总数减1
@1:
cmp ax,0 ;考虑实际长度小于等于512个字节的情况
jz direct
;读取剩余的扇区
push ds ;以下要用到并改变DS寄存器
mov cx,ax ;循环次数(剩余扇区数)
@2:
mov ax,ds
add ax,0x20 ;得到下一个以512字节为边界的段地址
mov ds,ax
xor bx,bx ;每次读时,偏移地址始终为0x0000
inc si ;下一个逻辑扇区
call read_hard_disk_0
loop @2 ;循环读,直到读完整个功能程序
pop ds ;恢复数据段基址到用户程序头部段
;计算入口点代码段基址
direct:
mov dx,[0x08]
mov ax,[0x06]
call calc_segment_base
mov [0x06],ax ;回填修正后的入口点代码段基址
;开始处理段重定位表
mov cx,[0x0a] ;需要重定位的项目数量
mov bx,0x0c ;重定位表首地址
realloc:
mov dx,[bx+0x02] ;32位地址的高16位
mov ax,[bx]
call calc_segment_base
mov [bx],ax ;回填段的基址
add bx,4 ;下一个重定位项(每项占4个字节)
loop realloc
jmp [0x04] ;转移到用户程序
;-------------------------------------------------------------------------------
read_hard_disk_0: ;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
; DS:BX=目标缓冲区地址
push ax
push bx
push cx
push dx
mov dx,0x1f2
mov al,1
out dx,al ;读取的扇区数
inc dx ;0x1f3
mov ax,si
out dx,al ;LBA地址7~0
inc dx ;0x1f4
mov al,ah
out dx,al ;LBA地址15~8
inc dx ;0x1f5
mov ax,di
out dx,al ;LBA地址23~16
inc dx ;0x1f6
mov al,0xe0 ;LBA28模式,主盘
or al,ah ;LBA地址27~24
out dx,al
inc dx ;0x1f7
mov al,0x20 ;读命令
out dx,al
.waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输
mov cx,256 ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [bx],ax
add bx,2
loop .readw
pop dx
pop cx
pop bx
pop ax
ret
;-------------------------------------------------------------------------------
calc_segment_base: ;计算16位段地址
;输入:DX:AX=32位物理地址
;返回:AX=16位段基地址
push dx
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02]
shr ax,4
ror dx,4
and dx,0xf000
or ax,dx
pop dx
ret
;-------------------------------------------------------------------------------
phy_base dd 0x10000 ;用户程序被加载的物理起始地址
times 510-($-$$) db 0
db 0x55,0xaa
;代码清单8-2/用户程序源代码
;文件名:c08.asm
;文件说明:用户程序
;创建日期:2011-5-5 18:17
;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code_1.start ;段地址[0x06]
realloc_tbl_len dw (header_end-code_1_segment)/4
;段重定位表项个数[0x0a]
;段重定位表
code_1_segment dd section.code_1.start ;[0x0c]
code_2_segment dd section.code_2.start ;[0x10]
data_1_segment dd section.data_1.start ;[0x14]
data_2_segment dd section.data_2.start ;[0x18]
stack_segment dd section.stack.start ;[0x1c]
header_end:
;===============================================================================
SECTION code_1 align=16 vstart=0 ;定义代码段1(16字节对齐)
put_string: ;显示串(0结尾)。
;输入:DS:BX=串地址
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string
.exit:
ret
;-------------------------------------------------------------------------------
put_char: ;显示一个字符
;输入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es
;以下取当前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
in al,dx ;高8位
mov ah,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
in al,dx ;低8位
mov bx,ax ;BX=代表光标位置的16位数
cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen
.put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl
;以下将光标位置推进一个字符
shr bx,1
add bx,1
.roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor
mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720
add bx,2
loop .cls
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
;-------------------------------------------------------------------------------
start:
;初始执行时,DS和ES指向用户程序头部段
mov ax,[stack_segment] ;设置到用户程序自己的堆栈
mov ss,ax
mov sp,stack_end
mov ax,[data_1_segment] ;设置到用户程序自己的数据段
mov ds,ax
mov bx,msg0
call put_string ;显示第一段信息
push word [es:code_2_segment]
mov ax,begin
push ax ;可以直接push begin,80386+
retf ;转移到代码段2执行
continue:
mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
mov ds,ax
mov bx,msg1
call put_string ;显示第二段信息
jmp $
;===============================================================================
SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐)
begin:
push word [es:code_1_segment]
mov ax,continue
push ax ;可以直接push continue,80386+
retf ;转移到代码段1接着执行
;===============================================================================
SECTION data_1 align=16 vstart=0
msg0 db ' This is NASM - the famous Netwide Assembler. '
db 'Back at SourceForge and in intensive development! '
db 'Get the current versions from http://www.nasm.us/.'
db 0x0d,0x0a,0x0d,0x0a
db ' Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
db ' xor dx,dx',0x0d,0x0a
db ' xor ax,ax',0x0d,0x0a
db ' xor cx,cx',0x0d,0x0a
db ' @@:',0x0d,0x0a
db ' inc cx',0x0d,0x0a
db ' add ax,cx',0x0d,0x0a
db ' adc dx,0',0x0d,0x0a
db ' inc cx',0x0d,0x0a
db ' cmp cx,1000',0x0d,0x0a
db ' jle @@',0x0d,0x0a
db ' ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
db 0
;===============================================================================
SECTION data_2 align=16 vstart=0
msg1 db ' The above contents is written by LeeChung. '
db '2011-05-06'
db 0
;===============================================================================
SECTION stack align=16 vstart=0
resb 256
stack_end:
;===============================================================================
SECTION trail align=16
program_end:
1 ;代码清单8-2//用户程序编译后执行文件
2 ;文件名:c08.asm
3 ;文件说明:用户程序
4 ;创建日期:2011-5-5 18:17
5
6 ;===============================================================================
7 SECTION header vstart=0 ;定义用户程序头部段
8 00000000 [00000000] program_length dd program_end ;程序总长度[0x00]
9
10 ;用户程序入口点
11 00000004 [A600] code_entry dw start ;偏移地址[0x04]
12 00000006 [00000000] dd section.code_1.start ;段地址[0x06]
13
14 0000000A 0500 realloc_tbl_len dw (header_end-code_1_segment)/4
15 ;段重定位表项个数[0x0a]
16
17 ;段重定位表
18 0000000C [00000000] code_1_segment dd section.code_1.start ;[0x0c]
19 00000010 [00000000] code_2_segment dd section.code_2.start ;[0x10]
20 00000014 [00000000] data_1_segment dd section.data_1.start ;[0x14]
21 00000018 [00000000] data_2_segment dd section.data_2.start ;[0x18]
22 0000001C [00000000] 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 00000000 8A0F mov cl,[bx]
31 00000002 08C9 or cl,cl ;cl=0 ?
32 00000004 7407 jz .exit ;是的,返回主程序
33 00000006 E80500 call put_char
34 00000009 43 inc bx ;下一个字符
35 0000000A E9F3FF jmp put_string
36
37 .exit:
38 0000000D C3 ret
39
40 ;-------------------------------------------------------------------------------
41 put_char: ;显示一个字符
42 ;输入:cl=字符ascii
43 0000000E 50 push ax
44 0000000F 53 push bx
45 00000010 51 push cx
46 00000011 52 push dx
47 00000012 1E push ds
48 00000013 06 push es
49
50 ;以下取当前光标位置
51 00000014 BAD403 mov dx,0x3d4
52 00000017 B00E mov al,0x0e
53 00000019 EE out dx,al
54
55 0000001A BAD503 mov dx,0x3d5
56 0000001D EC in al,dx ;高8位
57 0000001E 88C4 mov ah,al
58
59 00000020 BAD403 mov dx,0x3d4
60 00000023 B00F mov al,0x0f
61 00000025 EE out dx,al
62 00000026 BAD503 mov dx,0x3d5
63 00000029 EC in al,dx ;低8位
64 0000002A 89C3 mov bx,ax ;BX=代表光标位置的16位数
65
66 0000002C 80F90D cmp cl,0x0d ;回车符?
67 0000002F 750D jnz .put_0a ;不是。看看是不是换行等字符
68 00000031 89D8 mov ax,bx ;此句略显多余,但去掉后还得改书,麻烦
69 00000033 B350 mov bl,80
70 00000035 F6F3 div bl
71 00000037 F6E3 mul bl
72 00000039 89C3 mov bx,ax
73 0000003B E94900 jmp .set_cursor
74
75 .put_0a:
76 0000003E 80F90A cmp cl,0x0a ;换行符?
77 00000041 7507 jnz .put_other ;不是,那就正常显示字符
78 00000043 81C35000 add bx,80
79 00000047 E91000 jmp .roll_screen
80
81 .put_other: ;正常显示字符
82 0000004A B800B8 mov ax,0xb800
83 0000004D 8EC0 mov es,ax
84 0000004F D1E3 shl bx,1
85 00000051 26880F mov [es:bx],cl
86
87 ;以下将光标位置推进一个字符
88 00000054 D1EB shr bx,1
89 00000056 81C30100 add bx,1
90
91 .roll_screen:
92 0000005A 81FBD007 cmp bx,2000 ;光标超出屏幕?滚屏
93 0000005E 7C27 jl .set_cursor
94
95 00000060 B800B8 mov ax,0xb800
96 00000063 8ED8 mov ds,ax
97 00000065 8EC0 mov es,ax
98 00000067 FC cld
99 00000068 BEA000 mov si,0xa0
100 0000006B BF0000 mov di,0x00
101 0000006E B98007 mov cx,1920
102 00000071 F3A5 rep movsw
103 00000073 BB000F mov bx,3840 ;清除屏幕最底一行
104 00000076 B95000 mov cx,80
105 .cls:
106 00000079 26C7072007 mov word[es:bx],0x0720
107 0000007E 81C30200 add bx,2
108 00000082 E2F5 loop .cls
109
110 00000084 BB8007 mov bx,1920
111
112 .set_cursor:
113 00000087 BAD403 mov dx,0x3d4
114 0000008A B00E mov al,0x0e
115 0000008C EE out dx,al
116 0000008D BAD503 mov dx,0x3d5
117 00000090 88F8 mov al,bh
118 00000092 EE out dx,al
119 00000093 BAD403 mov dx,0x3d4
120 00000096 B00F mov al,0x0f
121 00000098 EE out dx,al
122 00000099 BAD503 mov dx,0x3d5
123 0000009C 88D8 mov al,bl
124 0000009E EE out dx,al
125
126 0000009F 07 pop es
127 000000A0 1F pop ds
128 000000A1 5A pop dx
129 000000A2 59 pop cx
130 000000A3 5B pop bx
131 000000A4 58 pop ax
132
133 000000A5 C3 ret
134
135 ;-------------------------------------------------------------------------------
136 start:
137 ;初始执行时,DS和ES指向用户程序头部段
138 000000A6 A1[1C00] mov ax,[stack_segment] ;设置到用户程序自己的堆栈
139 000000A9 8ED0 mov ss,ax
140 000000AB BC[0001] mov sp,stack_end
141
142 000000AE A1[1400] mov ax,[data_1_segment] ;设置到用户程序自己的数据段
143 000000B1 8ED8 mov ds,ax
144
145 000000B3 BB[0000] mov bx,msg0
146 000000B6 E847FF call put_string ;显示第一段信息
147
148 000000B9 26FF36[1000] push word [es:code_2_segment]
149 000000BE B8[0000] mov ax,begin
150 000000C1 50 push ax ;可以直接push begin,80386+
151
152 000000C2 CB retf ;转移到代码段2执行
153
154 continue:
155 000000C3 26A1[1800] mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
156 000000C7 8ED8 mov ds,ax
157
158 000000C9 BB[0000] mov bx,msg1
159 000000CC E831FF call put_string ;显示第二段信息
160
161 000000CF E9FDFF jmp $
162
163 ;===============================================================================
164 SECTION code_2 align=16 vstart=0 ;定义代码段2(16字节对齐)
165
166 begin:
167 00000000 26FF36[0C00] push word [es:code_1_segment]
168 00000005 B8[C300] mov ax,continue
169 00000008 50 push ax ;可以直接push continue,80386+
170
171 00000009 CB retf ;转移到代码段1接着执行
172
173 ;===============================================================================
174 SECTION data_1 align=16 vstart=0
175
176 00000000 202054686973206973- msg0 db ' This is NASM - the famous Netwide Assembler. '
177 00000009 204E41534D202D2074-
178 00000012 68652066616D6F7573-
179 0000001B 204E65747769646520-
180 00000024 417373656D626C6572-
181 0000002D 2E20
182 0000002F 4261636B2061742053- db 'Back at SourceForge and in intensive development! '
183 00000038 6F75726365466F7267-
184 00000041 6520616E6420696E20-
185 0000004A 696E74656E73697665-
186 00000053 20646576656C6F706D-
187 0000005C 656E742120
188 00000061 476574207468652063- db 'Get the current versions from http://www.nasm.us/.'
189 0000006A 757272656E74207665-
190 00000073 7273696F6E73206672-
191 0000007C 6F6D20687474703A2F-
192 00000085 2F7777772E6E61736D-
193 0000008E 2E75732F2E
194 00000093 0D0A0D0A db 0x0d,0x0a,0x0d,0x0a
195 00000097 20204578616D706C65- db ' Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
196 000000A0 20636F646520666F72-
197 000000A9 2063616C63756C6174-
198 000000B2 6520312B322B2E2E2E-
199 000000BB 2B313030303A0D0A0D-
200 000000C4 0A
201 000000C5 2020202020786F7220- db ' xor dx,dx',0x0d,0x0a
202 000000CE 64782C64780D0A
203 000000D5 2020202020786F7220- db ' xor ax,ax',0x0d,0x0a
204 000000DE 61782C61780D0A
205 000000E5 2020202020786F7220- db ' xor cx,cx',0x0d,0x0a
206 000000EE 63782C63780D0A
207 000000F5 202040403A0D0A db ' @@:',0x0d,0x0a
208 000000FC 2020202020696E6320- db ' inc cx',0x0d,0x0a
209 00000105 63780D0A
210 00000109 202020202061646420- db ' add ax,cx',0x0d,0x0a
211 00000112 61782C63780D0A
212 00000119 202020202061646320- db ' adc dx,0',0x0d,0x0a
213 00000122 64782C300D0A
214 00000128 2020202020696E6320- db ' inc cx',0x0d,0x0a
215 00000131 63780D0A
216 00000135 2020202020636D7020- db ' cmp cx,1000',0x0d,0x0a
217 0000013E 63782C313030300D0A
218 00000147 20202020206A6C6520- db ' jle @@',0x0d,0x0a
219 00000150 40400D0A
220 00000154 20202020202E2E2E20- db ' ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
221 0000015D 2E2E2E28536F6D6520-
222 00000166 6F7468657220636F64-
223 0000016F 6573290D0A0D0A
224 00000176 00 db 0
225
226 ;===============================================================================
227 SECTION data_2 align=16 vstart=0
228
229 00000000 20205468652061626F- msg1 db ' The above contents is written by LeeChung. '
230 00000009 766520636F6E74656E-
231 00000012 747320697320777269-
232 0000001B 7474656E206279204C-
233 00000024 65654368756E672E20
234 0000002D 323031312D30352D30- db '2011-05-06'
235 00000036 36
236 00000037 00 db 0
237
238 ;===============================================================================
239 SECTION stack align=16 vstart=0
240
241 00000000 resb 256
242 ****************** warning: uninitialized space declared in stack section: zeroing
243
244 stack_end:
245
246 ;===============================================================================
247 SECTION trail align=16
248 program_end: