现在我在吃饭,假设我是程序而我前面的桌子是数据。我知道桌子在哪,我也知道我的嘴在哪。我伸出筷子去夹菜,在保护模式下,程序段基址和数据段基址不一样,导致我前方的时空扭曲了,筷子伸到了口大丁桌子上而我并不知道,所以上帝要把数据拷贝到口大丁的桌子上。那么既然我面前的桌子没用能不能不要了呢?也不行,因为桌子没了,我就移到了桌子的位置,而我收回的手就只能把菜送到自己的后脑勺了。
我是个吃货,不光胖自己身后桌子上的东西也不放过
(
坑爹的
gcc
会把字符常量放在代码段的后面
)
。我把筷子伸向背后,空间又扭曲了,筷子出现的位置离口大丁桌子的距离和我一样宽。上帝没法区分我和我背后的桌子,他干脆把我和桌子一起拷贝过去
(
因为程序长度不一定,稍微改动源码长度就会变
)
,这样一来数据段中和我体型一样宽的地区都被浪费了。
数据段基址一般是 0 ,否则操作一些硬件就会比较麻烦,比如显存从 0xa0000 开始,如果数据段基址不为 0 ,代码在访问显存时就需要在原来的地址上减去这个数。但从 0 开始的问题就在于,上帝 ( 处于实模式,在我运行之前运行并结束的负责复制内存和设置全局变量表的程序 ) 一般离 0 不会太远,如果我实在太胖,那么上帝在复制我和桌子的过程中很可能把他自己也盖住了,上帝创世未捷身先死那我也不存在了。
可以在我和桌子前面加上一段为上帝留的空间 0x0000-0x9000 ,这下上帝不怕被覆盖了。同时注意,在可执行文件中我的代码不能离上帝太近,否则我的复制可能盖住我自己,假如上帝代码从 0x8000 开始,应该在我和上帝之间留出 0x1000 的间隔。
数据段基址一般是 0 ,否则操作一些硬件就会比较麻烦,比如显存从 0xa0000 开始,如果数据段基址不为 0 ,代码在访问显存时就需要在原来的地址上减去这个数。但从 0 开始的问题就在于,上帝 ( 处于实模式,在我运行之前运行并结束的负责复制内存和设置全局变量表的程序 ) 一般离 0 不会太远,如果我实在太胖,那么上帝在复制我和桌子的过程中很可能把他自己也盖住了,上帝创世未捷身先死那我也不存在了。
可以在我和桌子前面加上一段为上帝留的空间 0x0000-0x9000 ,这下上帝不怕被覆盖了。同时注意,在可执行文件中我的代码不能离上帝太近,否则我的复制可能盖住我自己,假如上帝代码从 0x8000 开始,应该在我和上帝之间留出 0x1000 的间隔。
这顿饭不容易啊,还是不要当吃货了。
程序更改如下:
ipl.nas中改成jmp 0x8200,你应该知道改什么地方。
程序: asmhead.nas
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; haribote-os boot asm
; TAB=4
BOTPAK EQU 0x00280000 ; 主程序地址
DSKCAC EQU 0x00100000 ;
DSKCAC0 EQU 0x00008000 ;
DATPAK EQU 0x00000000 ; 程序数据地址
; 有关BOOT_INFO
CYLS EQU 0x0ff0 ; 设定启动区
LEDS EQU 0x0ff1 ; 键盘状态
VMODE EQU 0x0ff2 ; 颜色位数
SCRNX EQU 0x0ff4 ; 分辨率X (screen x)
SCRNY EQU 0x0ff6 ; 分辨率Y (screen y)
VRAM EQU 0x0ff8 ; 图像缓冲区开始地址,显卡内存
ORG 0x8200
; 初始化栈地址
mov esp,0x8000
; 设定Graphic Mode
MOV AL,0x13 ; VGA显卡,320x200x8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 返回键盘状态
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
; PIC 可编程中断控制器 有两个PIC 每个PIC有8个输入0-7 处理为1,忽略为0
; cli关闭所有中断,sti打开所有中断
MOV AL,0xff
OUT 0x21,AL ; PCI1 data
NOP ; 太快可能会有问题
OUT 0xa1,AL ; PCI2 data
CLI ; 关闭全部中断
; 蛋疼的键盘A20 address enable
CALL waitkbdout
MOV AL,0xd1
OUT 0x64,AL
CALL waitkbdout
MOV AL,0xdf ; enable A20
OUT 0x60,AL
CALL waitkbdout
; 切换到保护模式
LGDT [GDTR0]
MOV EAX,CR0
AND EAX,0x7fffffff ; 禁止分页
OR EAX,0x00000001
MOV CR0,EAX
JMP pipelineflush
pipelineflush:
MOV AX,1*8 ; 取数据段偏移
MOV DS,AX ; 数据段
MOV ES,AX ; 数据段(字符操作目标)
MOV FS,AX ; 数据段
MOV GS,AX ; 数据段
MOV SS,AX ; 栈段
; 主程序加载到0x280000
MOV ESI,bootpack+0x1000
MOV EDI,BOTPAK+0x9000
MOV ECX,0x77000/4
CALL memcpy
; 数据加载到0x000000
MOV esi,bootpack+0x1000
mov eax,DATPAK+0x9000
MOV edi,eax
MOV ecx,0x77000/4 ; 小心不要覆盖引导区
CALL memcpy
; 跳转主程序
MOV ESP,0x9ffff ; 设置栈地址
JMP DWORD 2*8:0x0000f000 ; 跳转到0x280000
waitkbdout:
IN AL,0x64
AND AL,0x02 ; cpu可向键盘写命令时为1
JNZ waitkbdout ;
RET
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy
RET
; 全局变量表
ALIGNB 16 ; 16字节对齐 bss段
GDT0:
RESB 8 ; 第一项为0,这是规定
DW 0xffff,0x0000,0x9200,0x00cf ; 整个内存
DW 0xffff,0x0000,0x9a28,0x0047 ; 程序段
DW 0
GDTR0:
DW 8*3-1 ; 表的大小(字节)减1
DD GDT0 ; 表的地址
ALIGNB 16
bootpack:
resb 0x1000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
程序: Makefile
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
default:
make run
run: ipl.nas asmhead.nas func.nas bootpack.c stdio.c
nasm -f bin ipl.nas -o ipl.img -l ipl.lst
nasm -f bin asmhead.nas -o asmhead.o -l asmhead.lst
i386-elf-gcc -Wall -c bootpack.c -o bootpack.o
nasm -f elf func.nas -o func.o -l func.lst
i386-elf-ld -Tdata 0x9000 -Ttext 0xf000 bootpack.o func.o -o bootpack
i386-elf-objcopy -S -O binary bootpack bootpack.bin
cat bootpack.bin >> asmhead.o
dd if=asmhead.o of=ipl.img bs=512 seek=1 conv=notrunc
qemu-system-i386 -fda ipl.img -boot a
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;