一、准备GDT并加载
1、了解GDT(32位系统)
之所以拆的这么乱,据传是因为要向前兼容8088等硬件的原因。所以设置起来有些麻烦。
找一个GDT来看看,便于我们理解。windows内核调试可以很方便的找一个来看。先安装配置一个双机调试:
1、主机安装wdk,快捷方式->属性->命令后输入这些参数: -b -k com:pipe,port=\\.\pipe\com_1,resets=0,reconnect -y,双击运行即可
也可以运行windbg,用菜单选择方式启动:
File->kernel debug->COM->115200,\\.pipe\com_1,勾选Pipe、Reconnect、Resets[0]
2、虚拟机端:
(1)安装串口:
a.选择使用管道命名:输入“\\.\pipe\com_1”(vmware17.5 不带双引号. com_1要和上面的对应。如果上面起的名字是abc,那么这里也应该是abc)
b.选择该端是服务器--因为是服务器,所以要早于客户机启动
c.选择另一端是应用程序
d.I/O模式:勾选轮询时主动放弃CPU (允许客户机操作系统在轮询模式下【而不是中断模式】使用此串口)
(2)客户机操作系统:
修改boot.ini,添加一条启动debug条目:
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /debug /debugport=COM1: /baudrate=115200
这个参数不用死记,打开win.ini,复制启动条目;
xp下运行msconfig,选到复制过的条目,选高级,勾选debug,选com1,选115200即可。
如果客户机是 windows10也可以采用这种方式。不过win10是64位系统,其描述符和32位系统不太一样。所以......
(3)连接好以后,在windbg,kb>提示符下输入r gdtr,看一下寄存器里面存的地址,再用dd命令看一下,找到gdt的条目,对照上图分析一下加强感性认识。
可以看到第一个8byte是全0。分析第二个看看:00cf9b00 0000ffff(由于高字节在后面,所以反过来才对。所以还是用dq一下看8字节比较好)
段基址24~31位:00
GDLA+段长度16~19位: c f = 1 1 0 0 | 1 1 1 1 -- G=1,粒度是4K;D=1,操作数是32位;L=0,非64位;AVL=0,没有用,这个可以随便。段长度16~19位=F。
P+DPL+S+TYPE: 9 b = 1 0 0 1 | 1 0 1 1 -- P=1,在内存中;DPL=0,特权级是0;S=1,普通段;TYPE.T=1,代码段;C=0,不可执行;R=1,可读;A=1,已被访问。
段基址0~23位:00 00 00
段长度0~15位:ff ff
总结:段基址=00 00 00 00,段长度= ff ff f(0~0xfffff,共1024,粒度是4096,所以长度是4G)
这个段还在内存中,是基址为0,长度4G,特权级为0,已被访问,可读不可执行的32位代码段。
63 | 56 | 55 | 48 | 47 | 40 | 39 | 16 | 15 | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
段基址24~31位 | G | D | L | A | 段长度:16~19位 | P | DPL | S | TYPE | 段基址:0~23位 | 段长度:0~15位 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | C | R | A | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
0 | E | W | A |
00cff300`0000ffff:段基址:00 00 00;段长度:ff ff ff; GDLA全是1100;P=1,DPL=3,S=1;T=0,C=0,R=1,A=1。权限为3的数据段
构造一个试试:段基址0x8000,长度4M,32位,用户态的可扩展可读写未访问的数据段:
段基址:4个字节--0x00 00 80 00, 分成两段,高8位00放在56~63位,剩下的24位放在16~39位。
段长度:4M,超过1M,粒度应该用4K,所以第55位G=1,32位段D=1并且L=0,AVL取0. GDLA=0xc
4M=1K x 4K = 2^10 所以段长度第10位设置为1: 0000 0000 0100 0000 0000 = 0x0 40 00
55~48位:GDLA+段长度高4位合并:0xc0
15~0位:0x 40 00
P+DPL+S+TYPE:1 11 1 0110 =f6,47~40位0xf6
合到一起:63~56 0x00
55~48 0xc0
47~40 0xf6
39~16 0x00 80 00
15~0 0x 40 00
这个GDT条目:0x00 c0 f6 00 80 00 40 00
二、打开A20
这个简单且固定。原因是为了向前兼容8086系列硬件。
三、设置r0寄存器
这个也简单且固定。
; boot.asm
[org 0x7c00]
[section .data]
LSA equ 0x500
M32 equ 0x800
[section .text]
[bits 16]
global _start
_start:
mov ax, cs
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov sp, 0x1000
mov ax, 3
int 0x10
mov si, M_ldSetup
call print
mov bl, 1 ;setup routine load to 2nd sector
mov si, LSA;memory address
call readHD
mov bl, 2 ;mode32 routine load to 3rd sector
mov si, M32;memory address
call readHD
jmp LSA
readHD:
mov dx, 0x1f2 ;count
mov al, 2
out dx, al
inc dx ;LBA address
mov al, bl
out dx, al
inc dx
xor al, al
out dx, al
inc dx
out dx, al
mov dx, 0x1f6
mov al, 0xe0
out dx, al
mov dx, 0x1f7
.waitRDY:
in al, dx
test al, 0x40
jz .waitRDY
mov al, 0x20
out dx, al
.waitDRQ:
in al, dx
test al, 0x08
jz .waitDRQ
mov cx, 0x100 ;256
mov dx, 0x1f0
mov di, si
.readDAT:
in ax, dx
push dx
push ax
mov dx, 0x1f7
in al, dx
test al, 0x01 ;ERR
jnz .done
pop ax
pop dx
mov word[di], ax
add di, 2
loop .readDAT
.done:
ret
print:
mov ah, 0xe
mov bh, 0
mov bl, 1
.loop:
mov al, [si]
cmp al, 0
jz .done
int 0x10
inc si
jmp .loop
.done:
ret
M_ldSetup:
db "Load setup routine ...", 13, 10, 0
times 510 - ($-$$) db 0
db 0x55, 0xaa
;setup.asm
[org 0x500]
[section .text]
[bits 16]
xchg bx, bx
mov si, M_runSetup
call print
lgdt [gdt_entry]
mov dx, 0x92
in al, dx
or al, 0x2
out dx, al
mov eax, cr0
or eax, 0x1
mov cr0, eax
xchg bx, bx
jmp 08:0
print:
mov ax, 0xb800
mov gs, ax
mov di, 0xa0
mov ah, 0x26
.loop:
mov al, [si]
cmp al, 0
jz .done
mov word[gs:di], ax
inc si
inc di
inc di
jmp .loop
.done:
ret
GDT:
dq 0
dq 0x_00_40_9e_00_08_00_76_ff ; 32 code segment
dq 0x_00_00_92_0b_80_00_7f_ff ; display memory area
END:
gdt_len equ END-GDT-1
gdt_entry:
dw gdt_len
dd GDT
M_runSetup:
db "Setup routing running ...", 0
;mode32.asm
[org 0x800]
[section .text]
[bits 32]
xchg bx, bx
mode32:
mov ax, cs
mov gs, ax
mov si, M_32
call print32
hlt
print32:
mov ax, 0x10
mov ds, ax
mov di, 0x140
mov ah, 0x62
.loop:
;mov al, [gs:si-0x800]
mov al, [es:si]
cmp al, 0
jz .done
mov word[ds:di], ax
inc si
inc di
inc di
jmp .loop
.done:
ret
M_32:
db "32 bit routine ...", 0
感受:
基础知识要尽量搞透彻。
遇到问题耐心一步一步把程序走一遍,注意看寄存器和内存的变化。汇编要把内存搞明白,GDT熟练掌握,另外要注意基址和偏移问题。容易踩坑。同时,原来看书说GDT 0条目没有用,通过这次学习发现,是有用的。比如上面程序寻找si的时候,默认是ds段,gs是代码段,都可以用,但要计算。在从0为基址开始时,es用GDT条目0即可实验成功(同gs - 0x800效果一样)。
另外Makefile文件把,mode32写入了第三个扇区。第三个扇区的内容被boot读到内存0x800处。
build:
nasm boot.asm
nasm setup.asm
nasm mode32.asm
dd if=/dev/zero of=a.img bs=512 count=2880
dd if=boot of=a.img bs=512 count=1 seek=0 conv=notrunc
dd if=setup of=a.img bs=512 count=1 seek=1 conv=notrunc
dd if=mode32 of=a.img bs=512 count=1 seek=2 conv=notrunc
bochs:
bochs -q -f bochsrc
clean:
rm boot setup a.img
unlock:
rm a.img.lock
考虑把16bit程序和32bit程序放在一个文件里应该也可以,试了一下还行
[org 0x500]
[section .text]
[bits 16]
mov si, M_setupCome
call print
xchg bx,bx
lgdt [GDT_ENTRA]
mov dx, 0x92
in al, dx
or al, 0x02
out dx, al
mov eax, cr0
or ax, 1
mov cr0, eax
jmp 8:entra32
print:
mov ax, 0xb800
mov gs, ax
mov di, 0xa0
mov ah, 0x26
.loop:
mov al, [si]
cmp al, 0
jz .done
mov word[gs:di], ax
inc si
add di,2
jmp .loop
.done:
ret
M_setupCome:
db "Welcome setup routine comming ...", 0
; ------------GDT-----------------------------
GDT:
dq 0
dq 0x_00_cf_9e_00_00_00_ff_ff
dq 0x_00_cf_96_00_00_00_ff_ff
dq 0x_00_00_92_0b_80_00_7f_ff
END:
gdt_len equ END - GDT -1
GDT_ENTRA:
dw gdt_len
dq GDT
; -----------------------------------------------
; ------------32bit segment----------------------
[section .text]
[bits 32]
entra32:
mov ax, cs
mov ds, ax
mov si, M_32
call print32
hlt
print32:
mov eax, 0x18 ;显示存储区的描述符
mov gs, ax
mov di, 0x140 ;第三行开始显示
mov ah, 0x62
.loop:
mov al, [si]
cmp al, 0
jz .done
xchg bx, bx
mov [gs:di], ax
inc si
add di,2
jmp .loop
.done:
ret
M_32:
db "32 bits routine comming ...", 0
hlt