加载器,是用来加载用户程序的,所以加载器程序存放在主引导扇区,为了能顺利执行用户程序,所以加载器和用户程序必须要有一些约定,主要目的就是为了段的重定位(回填逻辑段地址),所以配合本文的用户程序,做出如下约定
1.用户程序总大小,占用整个程序的第00-03四个字节,存放的内容是用户程序的字节数,比方说这个程序大小是512字节,那么用户程序前四个字节的内容在内存中应该是:10 0000 0000
(0x200,512D)
2.入口代码偏移量,占用整个程序的第04-05两个字节,存放的是程序启动函数的偏移量,例如java和c的main方法
3.入口代码所在段的汇编地址,占用整个用户程序第06-09两个字节,存放的是程序启动函数所在段的汇编地址,加载器会根据该值计算出一个逻辑段地址并且回填这个地址
4.需要重定位的段个数,占用整个用户程序第0A-0B两个字节,存放的是程序一共有多少个段,本例中3个
5.用户程序第0C字节开始,每4个字节存放的是一个段的汇编地址,假设当前程序有3个段,那么从0C开始接下来的12个字节,存放的都是段的汇编地址,加载器算出逻辑段地址之后,回填此处
下面的代码和下面的图是一个具有3个段的用户程序,这个程序作为用户程序,主要执行以下2个步骤
1.将数据段中的值拿出来,放到栈中
2.将栈中的数据弹出,并放到显存,最终屏幕上显示大写的SWT
;此程序作为用户程序,主要执行以下2个步骤
;1.将数据段中的值拿出来,放到栈中
;2.将栈中的数据弹出,并放到显存,最终屏幕显示大写字母SWT
section header vstart=0
program_length dd program_end ;00-03
main_offset dw main ;04-05程序入口偏移量
main_segment dd section.code.start ;06-09程序入口的段地址
sec_tble_len dw 0x3 ;0A-0B,段的个数
;下面三个汇编地址,会被加载器替换成段地址
table_start: ;0C开始
code_section dd section.code.start ;代码段的汇编地址
stack_section dd section.stack.start;栈段的汇编地址
data_section dd section.data.start ;数据段的汇编地址
table_end:
section code vstart=0 align=16 ;align16表示段
;地址最后4bit都是0
main:
mov ss,[stack_section] ;设置栈段逻辑地址ss=1005H
mov sp,0
mov ds,[data_section] ;设置数据段逻辑地址ds=1008H
mov cx,3 ;循环三次,注意每次循环CX是变化的
mov di,0
push_data_section: ;将数据段中的数据压栈
mov ax,[di] ;从栈挪2个字节到ax
push ax
inc di
inc di
loop push_data_section
xor di,di
mov cx,3 ;循环三次,之前的CX=0了,此处重新赋值
mov ax,0xb800
mov es,ax ;设置显存段地址
pop_data_to_video_memory: ;将栈中数据弹出并放到缓存
pop ax
mov byte [es:di],ah
inc di
mov byte [es:di],al
inc di
loop pop_data_to_video_memory
dot_stop:
hlt
jmp dot_stop
section stack vstart=0 align=16
times 10 dd 0xcccccccc
section data vstart=0 align=16
db 0x0b
db 84d ;大写字母T,0x54
db 0x0b
db 87d ;大写字母W,0x57
db 0x0b
db 83d ;大写字母S,0x53
times 14 dw 0xffffffff
section tail
program_end: