文章目录
一、环境
1、notepad++编写
2、硬盘写入工具 vhd writer 链接
3、nasm编译
4、bochs调试
5、virtual box测试
二、要求
编写一个用户程序加载器
具体要求:
用户程序在磁盘的第100个扇区
将用户程序从磁盘加载到内存的0x10000位置
进入用户程序执行,最后返回程序加载器
编写一个用户程序
具体要求:
在屏幕上打印字符串
ascii字符0x0d为回车字符,光标回车
ascii字符0x0a为换行字符,光标换行
ascii字符0x0为字符串结尾,结束输出
其他字符在光标处进行打印输出,然后推进光标
显存写满时(光标位置>=2000),则向上滚屏,整体向上移动一行
资源
源码以及vhd虚拟硬盘
链接:https://pan.baidu.com/s/1bOcA0w17Nh0RKXF2uZ1WkQ
提取码:undc
三、思路
用户程序解析
加载器
第一步:设置栈段
设置栈段后要紧跟着设置栈顶位置,设置栈段后系统会在下一条指令执行前禁止中断,防止栈段已设置,但栈顶未设置的期间中断例程造成不合法的访问,所以栈顶设置尽量紧跟着栈段设置
第二步:设置数据段
第三步:计算用户程序加载地址
第四步:加载用户程序前512字节的内容(1个扇区大小)
第五步:计算用户程序大小,根据结果确定是否继续读取磁盘
第六步:用户程序段重定位表处理
第七步:填入调用用户程序的地址
第八步:调用用户程序
第九步:用户程序执行完毕,进入死循环
用户程序字符串打印过程
四、效果
开始
加载器程序加载完成,准备跳转
跳转至用户程序
即将执行字符串打印例程
第一段字符串打印效果
第二段字符串打印效果
用户程序执行完毕,准备跳转回加载器
成功跳转回加载器
virtual box演示效果
五、代码
加载器代码
app_lba_start equ 100 ;常数申明
section loader align=16 vstart=0x7c00
;设置堆栈指针,起始地址0x07e0,长度256字节
mov ax,0x07e0
mov ss,ax
mov sp,0x100
;用户程序加载段地址计算
mov ax,[cs:phy_base]
mov dx,[cs:phy_base+0x02]
mov bx,0x10
div bx ;商在ax中,余在dx中
;ds、es指向userapp加载段地址
mov ds,ax
mov es,ax
;读取userapp头部512字节
xor di,di ;传递userapp逻辑扇区号
mov si,app_lba_start ;传递userapp逻辑扇区号
mov bx,0x0 ;传递userapp加载偏移地址
call read_hard_disk_0
;填入
;计算userapp大小
mov ax,[ds:0x04]
mov dx,[ds:0x06]
mov bx,512
div bx
cmp dx,0x0 ;如果未除尽,说明读取扇区等于ax+1,减去已读取的还有ax个,ax也就不用减了
jnz @1
dec ax
@1:
cmp ax,0x0 ;表示userapp长度<=512字节,已经读取过一个扇区,就不用读取了
jz @3
;读取剩余扇区
xor bx,bx
mov cx,ax
@2:
inc si ;逻辑扇区号递增
add bx,0x200 ;userapp加载偏移地址递增
call read_hard_disk_0
loop @2
@3:
;段重定位表处理
mov bx,0x10 ;段重定位表起始地址
mov cx,[ds:0x0e] ;段重定位表项数
@4:
mov ax,[ds:bx]
mov dx,[ds:bx+0x02]
call calc_section_base
mov [ds:bx],ax
add bx,0x04
loop @4
;计算userapp的main代码段地址
mov ax,[ds:0x0a]
mov dx,[ds:0x0a+2]
call calc_section_base
mov [ds:0x0a],ax
;填入调用地址
mov ax,@5
mov [ds:0x0],ax
mov ax,cs
mov [ds:0x2],ax
jmp far [0x08]
@5:
jmp $
read_hard_disk_0:
push ax
push bx
push cx
push dx
;di:si为扇区号,ds:bx为写入地址
mov dx,0x1f2 ;0x1f2:设置读取扇区数
mov al,0x01
out dx,al ;格式固定,dx指定端口,al传输或接收数据
inc dx ;0x1f3:设置 lba 0~7位
mov ax,si
out dx,al
inc dx ;0x1f4:设置 lba 8~15位
mov al,ah
out dx,al
inc dx ;0x1f5:设置 lba 16~23位
mov ax,di
out dx,al
inc dx ;0x1f6:设置 lba 24~27位,28~31位为选择设置
and ah,0x0F ;清除ah高4位,低4位存储 LBA 信息
mov al,0xe0 ;al高4位设置选择信息,0xe0表示 主硬盘 LBA模式
or al,ah ;将ah低4位 lba信息 传送到al
out dx,al ;此时al 0~3 存储 lba信息,4~7 存储选择信息
inc dx ;0x1f7:控制命令端口
mov al,0x20 ;0x20表示读取硬盘命令
out dx,al
;等待硬盘准备完毕
waits:
in al,dx ;0x1f7端口每时每刻返回自身状态信息
and al,0x88 ;清除7、3位之外的信息
;第7位为0表示硬盘不忙,为1表示硬盘忙
;第3位为0表示硬盘没有准备好,为1表示硬盘准备好进行传输
cmp al,0x08
jnz waits ;若未准备好,则持续等待
;准备读取,ds:bx为写入地址
mov cx,256 ;读取字数
mov dx,0x1f0 ;dx:数据传输端口
read:
in ax,dx
mov [bx],ax
add bx,0x02
loop read
pop dx
pop cx
pop bx
pop dx
ret
calc_section_base: ;接收dx:ax userapp分段地址
;返回段地址,填入ax
push dx ;不将ax压栈是因为ax用于返回计算结果
add ax,[cs:phy_base] ;将低16位加到ax中
adc dx,[cs:phy_base+0x02] ;adc加法会中进位标志位中取来自上一次加法的进位加到结果中,这样就完成了两个32位数的相加
;高16位在dx中,低16位在ax中
;x86一共20位地址,高4位在dx低4位中,低16位在ax中
;因为我们要计算的是段地址,ax段中低4位应该为0
;于是仅需要将dx低4位,ax高12位组合传输到ax
;shr位右移指令,ror为循环右移指令
ror dx,0x04 ;左移12位,低12位被置0
shr ax,0x04 ;右移4位,高4位被置0
and dx,0xf000
or ax,dx
pop dx
ret
phy_base: ;userapp加载的内存地址
dd 0x10000
times 510-($-$$) db 0
db 0x55,0xaa
用户程序代码
section header align=16 vstart=0
;跳转至该程序前cs:ip的内容,由loader填写
comeback_addr:
dd 0
program_length:
dd program_end
code_main_entry:
dw start_main
dd section.code_main.start
realloc_tbl_length:
dw (header_end-code_main_section) / 4
code_main_section: dd section.code_main.start
code_0_section: dd section.code_0.start
data_0_section: dd section.data_0.start
data_1_section: dd section.data_1.start
stack_0_section: dd section.stack_0.start
header_end:
section code_main align=16 vstart=0
start_main:
mov ax,[es:stack_0_section]
mov ss,ax
mov sp,stack_0_end
mov ax,[es:data_0_section]
mov ds,ax
mov bx,string_0
call put_string ;打印第一段字符串
mov ax,[es:data_1_section]
mov ds,ax
mov bx,string_1
call put_string ;打印第二段字符串
;返回调用位置
push dword [es:comeback_addr]
retf
;----------------------------------------------------------------------
put_string: ;接受字符串地址ds:bx并在光标处显示
push ds ;ascii码=0x0d 回车,ascii码=0x0a 换行
push bx ;ascii码= 0x0 返回,否则正常显示
push cx
put_cnt:
mov cl,[ds:bx] ;字符读取
or cl,cl
jz exit ;cl为0,结束输出
call acting ;传入字符cl,根据情况打印
inc bx
jmp put_cnt
exit:
pop cx
pop bx
pop ds
ret
;----------------------------------------------------------------------
acting: ;接收字符cl,根据情况打印
push ax
push bx
push cx
push dx
cmp cl,0x0d ;回车符
jnz @2 ;不是回车符则跳到@2
call ac_enter ;是回车符,光标回车
jmp @acting_end
@2:
cmp cl,0x0a ;换行符
jnz @3 ;不是换行符则跳到@3
call ac_n ;是换行符,光标换行
jmp @acting_end
@3:
call get_cursor ;获取光标位置,存于ax
mov bx,ax
mov dh,0x07
mov dl,cl
call put_char ;传入字符dl,属性dh,显示位置bx
add ax,0x01 ;光标推进1格
call over_ ;接收光标位置ax,返回合法位置ax
mov bx,ax
call set_cursor
@acting_end:
pop dx
pop cx
pop bx
pop ax
ret
;----------------------------------------------------------------------
put_char: ;接收ascii码dl,属性dh,显示位置bx
push es
push ax
push bx
push dx
mov ax,0xb800
mov es,ax
push dx
mov ax,bx
mov bx,0x02
mul bx ;位置*2得到传送地址,乘法结果<4000
mov bx,ax
pop dx
mov [es:bx],dx
pop dx
pop bx
pop ax
pop es
ret
;----------------------------------------------------------------------
get_cursor: ;返回光标位置,存于ax中
push dx
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位读取
pop dx
ret
;----------------------------------------------------------------------
set_cursor: ;接收位置信息bx,设置光标
push ax
push bx
push dx
mov dx,0x3d4 ;索引端口设置
mov al,0x0e
out dx,al ;索引设置
mov dx,0x3d5 ;数据端口设置
mov al,bh
out dx,al ;光标高8位写入
mov dx,0x3d4 ;索引端口设置
mov al,0x0f
out dx,al ;索引设置
mov dx,0x3d5 ;数据端口设置
mov al,bl
out dx,al ;光标低8位写入
pop dx
pop bx
pop ax
ret
;----------------------------------------------------------------------
cal_enter: ;计算光标若回车,返回其位置存于ax
push bx
call get_cursor ;获取光标位置,存于ax
push ax
mov bl,0x50
div bl ;ax/80,商在al,余在ah
xor bx,bx
mov bl,ah
pop ax
sub ax,bx ;计算光标行首位置
pop bx
ret
;----------------------------------------------------------------------
cal_n: ;计算光标若换行,返回其位置存于ax
call get_cursor ;获取光标位置,存于ax
add ax,0x50 ;计算光标换行位置
ret
;----------------------------------------------------------------------
ac_enter: ;字符为回车符时,光标回到行首
push ax
push bx
call cal_enter ;获取回车后光标显示位置ax
mov bx,ax
call set_cursor ;设置光标位置bx
pop bx
pop ax
ret
;----------------------------------------------------------------------
ac_n: ;字符为换行符,换行
push ax
push bx
call cal_n ;获取换行后光标显示位置ax
call over_ ;接收位置信息ax,返回正常显示位置ax
mov bx,ax
call set_cursor ;设置光标位置bx
pop bx
pop ax
ret
;----------------------------------------------------------------------
over_: ;接收光标将要显示的位置ax,并返回合法位置ax
cmp ax,0x7d0 ;是否小于2000
jl @1 ;小于则跳转至@1
call scrolling ;若不小于则进行滚屏
sub ax,0x50 ;滚屏后,处理越界值ax
@1:
ret
;----------------------------------------------------------------------
scrolling: ;滚屏操作
push ds
push es
push si
push di
push ax
push cx
mov ax,0xb800
mov ds,ax ;源地址
mov si,160 ;从第二行开始传送
mov es,ax ;目的地址
mov di,0 ;从第一行开始接收
mov cx,0x0780 ;传送字数 1920
cld ;由低地址向高地址遍历
rep movsw ;批量传送
mov bx,3840 ;清除屏幕最底一行
mov cx,80
clear:
mov word[es:bx],0x0720
add bx,2
loop clear
pop cx
pop ax
pop di
pop si
pop es
pop ds
ret
section code_0 align=16 vstart=0
push ax
pop ax
section data_0 align=16 vstart=0
string_0:
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 'Back at SourceForge and in intensive development! '
db 'Get the current versions from http://www.nasm.us/.'
db 0x0d,0x0a,0x0d,0x0a
db 'Back at SourceForge and in intensive development! '
db 'Get the current versions from http://www.nasm.us/.'
db 0x0d,0x0a,0x0d,0x0a
db 'Back at SourceForge and in intensive development! '
db 'Get the current versions from http://www.nasm.us/.'
db 0x0d,0x0a,0x0d,0x0a0
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
string_0_end:
section data_1 align=16 vstart=0
string_1:
db 'this is my own OS!!!'
db 0
string_1_end:
section stack_0 align=16 vstart=0
times 256 db 0
stack_0_end:
section tail align=16
program_end: