仅仅是打印了三个字符了,但确实是用心去练习了。
rem auto.cmd
@echo off
nasm -fbin -o boot.bin boot.asm
dd if=boot.bin of=c.img seek=0 count=1
echo.
nasm -fbin -o loader.bin loader.asm
dd if=loader.bin of=c.img seek=1 count=2
echo.
nasm -fbin -o kernel.bin kernel.asm
dd if=kernel.bin of=c.img seek=3 count=253
echo.
rem del *.bin
del c.img.lock
;boot. asm
bits 16
start:
mov ax, 0x7c0
mov ds, ax
cli
mov dx, 0x1f2
mov al, 2 ; 2 sector
out dx, al
mov dx, 0x1f3
mov al, 1 ; the second sector, from zero to ...
out dx, al
mov dx, 0x1f4
mov al, 0
out dx, al
mov dx, 0x1f5
mov al, 0
out dx, al
mov dx, 0x1f6
mov al, 0xe0 ; LBA mode
out dx, al
mov dx, 0x1f7
mov al, 0x20
out dx, al
mov dx, 0x1f7
.1:
in al, dx
and al, 0x88
cmp al, 0x08
jne .1
mov dx, 0x1f0
mov bx, 0x200
mov cx, 256
.2:
in ax, dx
mov [bx], ax
inc bx
inc bx
loop .2
lgdt [gdtr]
in al, 0x92
or al, 0000_0010b ; 0x2
out 0x92, al
mov eax, cr0
or eax, 0000_0000_0000_0000_0000_0000_0000_0001b ; 0x1
mov cr0, eax
jmp dword 1 * 8 : 0
gdt_start:
dq 0x0000_0000_0000_0000 ; 0 * 8
dq 0x00c0_9a00_7e00_ffff ; 1 * 8
dq 0x00c0_9200_7e00_ffff ; 2 * 8
dq 0x00c0_920b_8000_ffff ; 3 * 8
gdt_end:
gdtr:
dw gdt_end - gdt_start - 1
dd gdt_start + 0x7c00
times 510 - ($ - $$) db 0
dw 0xaa55
; loader.asm
bits 32
start:
mov ax, 3 * 8
mov gs, ax
mov ax, 2 * 8
mov ds, ax
; xor ebx, ebx
; mov byte [gs: ebx], 'P'
; inc ebx
; mov byte [gs: ebx], 0xc
; inc ebx
mov dx, 0x1f2
mov al, 253 ; 253 sectors
out dx, al
mov dx, 0x1f3
mov al, 3 ; the third sector
out dx, al
mov dx, 0x1f4
mov al, 0
out dx, al
mov dx, 0x1f5
mov al, 0
out dx, al
mov dx, 0x1f6
mov al, 0xe0 ; LBA mode
out dx, al
mov dx, 0x1f7
mov al, 0x20 ; read sector instruction
out dx, al
mov dx, 0x1f7
.1:
in al, dx
and al, 0x88
cmp al, 0x08
jnz .1
mov dx, 0x1f0
mov ebx, 0x100000 - 0x7e00
mov ecx, 253 * 256
.2:
in ax, dx
mov [ebx], ax
inc ebx
inc ebx
loop .2
lgdt [gdtr]
jmp dword 4 * 8 : 0
gdt_start:
dq 0x0000_0000_0000_0000 ; 0 * 8
dq 0x00c0_9a00_7e00_ffff ; 1 * 8
dq 0x00c0_9200_7e00_ffff ; 2 * 8
dq 0x00c0_920b_8000_ffff ; 3 * 8
dq 0x00cf_9a10_0000_ffff ; 4 * 8
dq 0x00cf_9210_0000_ffff ; 5 * 8
dq 0x00cf_9a00_0000_ffff ; 6 * 8
dq 0x00cf_9200_0000_ffff ; 7 * 8
gdt_end:
gdtr:
dw gdt_end - gdt_start - 1
dd gdt_start + 0x7e00
bits 32
%macro enter 0
push ebp
mov ebp, esp
%endmacro
%macro push_some 0
push ecx
push edx
push ebx
push esi
push edi
%endmacro
%macro pop_some 0
pop edi
pop esi
pop ebx
pop edx
pop ecx
%endmacro
;人为的增加了远返回的难度,因为似乎没有类似的指令, 因此在这里用宏模拟了一个
%macro jmpf 2
; jmpf instruction(%1), format_address(%2)
mov eax, [%2]
mov [%%.0], eax
mov ax, [%2 + 4]
mov [%%.0 + 4], ax
db %1
%%.0:
dd 0
dw 0
%endmacro
start:
mov ax, 5 * 8
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov esp, stack_top
mov ax, 3 * 8
mov gs, ax
sub esp, 20 * 4
call set_page
call 4 * 8 : cls ;使用远转移,返回时要使用指令retf,远转移的指令格式是 段选择符的值:段内偏移地址
mov dword [esp + 0 * 4], 'P'
mov dword [esp + 1 * 4], 0xc
call 4 * 8 : _print_c
call 6 * 8 : DEMO
call set_page2
call 6 * 8 : DEMO
add esp, 20 * 4
jmp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cls: ;void cls(void)
enter
push_some
mov eax, [ebp + 4] ;保存远返回的地址
mov [ret_addr], eax
mov ax, [ebp + 2 * 4]
mov [ret_addr + 4], ax
xor ebx, ebx
mov al, ' '
mov ah, 0xf
mov ecx, 2000
.1:
mov [gs:ebx], ax
inc ebx
inc ebx
loop .1
pop_some
leave
;retf ;因为我们的调用是远调用,所以这里也采用远返回
;pop eax ;因为没有段地址,因此这样返回会有问题
;ret
cli
add esp, 8
jmpf 0xea, ret_addr ; 这里是故意复杂化了,其实只要用数据定义指令就好了
; db 0xea
ret_addr: dd 0
dw 0
set_page: ;void set_page(void) ;全对等的映射,也就是和物理地址和虚拟地址一一对应
pusha
mov ecx, 1024 ;共1024个页目录项目
mov edi, page_dir_start ;页目录表的偏移地址
mov eax, PAGE_TABLE_BASE + 3 ;要填充的是页表的物理地址,+3的意思是页属性为 0000000011b
.1:
stosd ;串传送指令,每次将eax中的值,传送到[es:edi]中的连续4个字节中
add eax, 4 * 1024 ;连续填充,也就是下一个页目录表项指向另外一个页表,这些页表也是连续存放的
loop .1
mov ecx, 5 * 1024 ; 可以使用20M的内存
mov edi, page_table_start ;取页表的偏移地址
mov eax, 0 + 3 ;从物理页的第0页开始填充
.2:
stosd
add eax, 4 * 1024 ;连续填充,也就是指向下一个页面,每个页面的大小为固定 4K
loop .2
mov eax, PAGE_DIR_BASE ;装载页目录表基地址
mov cr3, eax
mov eax, cr0
or eax, 0x8000_0000
mov cr0, eax ;开启分页模式 ;1000_0000_0000_0000_0000_0000_0000_0000b,最高位置1
jmp short .page
.page: ;据说这样可以刷新高速缓冲
popa
ret
set_page2:
pusha
mov ecx, 1024
mov edi, page_dir_start2
mov eax, PAGE_TABLE_BASE + 3
.1:
stosd
add eax, 4 * 1024
loop .1
mov ecx, 5 * 1024
mov edi, page_table_start
mov eax, 0 + 3
.2:
stosd
add eax, 4 * 1024
loop .2
mov eax, FUNC2 ; 这里是func2的线性地址,也就是分段机制转化得到的地址形式
mov ebx, eax ;保留一份副本
shr eax, 22 ;取得最高10位,得到在页目录表中的项目数
shl eax, 2 ; 乘以4,得到在页目录表中的偏移地址
add eax, page_dir_start2 ;加上页目录表的基地址,注意这里不是物理地址,
;而是相对于此段的偏移,我们仅使用了一个段
and ebx, 0000_0000_0011_1111_1111_0000_0000_0000b ;屏蔽无用位
shr ebx, 12 ;取得中间10位,得到在某个页表中的项目数
shl ebx, 2 ;乘以4,得到在此页表中的偏移
mov ecx, [eax] ;取得页表的基地址,注意这里是页表的物理地址
and ecx, 0xfffff000 ;去掉页表属性
add ecx, ebx ;得到在页表项在页表中的物理地址,如果在此处访问,需要减去段长度
sub ecx, 0x100000 ;这里减去段长度,得以访问页表项
mov dword [ecx], FUNC1 + 3 ;将函数的所在页物理地址和页属性存入页表项中,从而使改变了页映射关系
;使func2的线性地址映射到func1
mov eax, PAGE_DIR_BASE2
mov cr3, eax
jmp short .page
.page:
popa
ret
print_c: ; void print_c(char c, int colour) ;字符打印的函数
enter
push_some
sub esp, 20 * 4
mov ebx, [cursor_pos]
mov al, [ebp + 2 * 4]
mov ah, [ebp + 3 * 4]
mov [gs:ebx], ax
inc ebx
inc ebx
mov [cursor_pos], ebx
add esp, 20 * 4
leave
ret
_print_c: ; void _print_c(char c, int colour) ;字符打印的函数
enter
push_some
sub esp, 20 * 4
mov ebx, [cursor_pos] ;远调用压入堆栈8个字节分别是段选择符和偏移地址,所以这里累计加到3 * 4
mov al, [ebp + 3 * 4]
mov ah, [ebp + 4 * 4]
mov [gs:ebx], ax
inc ebx
inc ebx
mov [cursor_pos], ebx
add esp, 20 * 4
leave
retf
cursor_pos: dd 0 ;光标位置,这里未设置
;栈空间
stack_bottom:
times 1024 db 0
stack_top:
;页目录表1
align 4 * 1024 ; 4k
page_dir_start:
times 4 * 1024 db 0
page_dir_end:
PAGE_DIR_BASE equ 0x100000 + page_dir_start ;线性地址,等同于物理地址
;页表(公用)
page_table_start:
times 5 * 4 * 1024 db 0 ; 20M
page_table_end:
PAGE_TABLE_BASE equ 0x100000 + page_table_start
;页目录表2
page_dir_start2:
times 4 * 1024 db 0
page_dir_end2:
PAGE_DIR_BASE2 equ 0x100000 + page_dir_start2
align 4096 ;页对齐
;似乎必须用这样的格式,???
demo:
mov eax, FUNC2
call eax
retf
DEMO equ 0x100000 + demo
align 4096
func2:
enter
push_some
sub esp, 20 * 4
mov dword [esp + 0 * 4], '2'
mov dword [esp + 1 * 4], 0x9
call 4 * 8 : _print_c
add esp, 20 * 4
leave
ret
FUNC2 equ 0x100000 + func2
align 4096
func1:
enter
push_some
sub esp, 20 * 4
mov dword [esp + 0 * 4], '1' ;在页映射变换后,不能调用print(char c, int colour)这个函数,不知道什么原因
mov dword [esp + 1 * 4], 0xa
call 4 * 8 : _print_c
; mov ebx, 160
; mov al, '1'
; mov ah, 0xf
; mov [gs:ebx], ax
add esp, 20 * 4
leave
ret
FUNC1 equ 0x100000 + func1
###############################################################
# bochsrc.txt file for DLX Linux disk image.
###############################################################
# how much memory the emulated machine will have
megs: 64
# filename of ROM images
romimage: file=../BIOS-bochs-latest
vgaromimage: file=../VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=a.img, status=inserted
##floppyb: 1_44=b.img, status=inserted
# hard disk
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="c.img", cylinders=306, heads=4, spt=17
# choose the boot disk.
boot: c
# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx
#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga
# where do we send log messages?
#log: bochsout.txt
# disable the mouse, since DLX is text only
mouse: enabled=0
# set up IPS value and clock sync
cpu: ips=15000000
clock: sync=both
# enable key mapping, using US layout as default.
#
# NOTE: In Bochs 1.4, keyboard mapping is only 100% implemented on X windows.
# However, the key mapping tables are used in the paste function, so
# in the DLX Linux example I'm enabling keyboard_mapping so that paste
# will work. Cut&Paste is currently implemented on win32 and X windows only.
keyboard: keymap=../keymaps/x11-pc-us.map
#keyboard: keymap=../keymaps/x11-pc-fr.map
#keyboard: keymap=../keymaps/x11-pc-de.map
#keyboard: keymap=../keymaps/x11-pc-es.map