首页
社区
课程
招聘
[分享]学写操作系统(一)……(十五)
发表于: 2010-12-30 01:27 19067

[分享]学写操作系统(一)……(十五)

2010-12-30 01:27
19067
收藏
免费 7
支持
分享
最新回复 (77)
雪    币: 17
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cpe
26
看到好贴就要顶
2010-12-31 12:36
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
27
转载:
在Protected Mode下,一个重要的必不可少的数据结构就是GDT(Global Descriptor Table)。
为什么要有GDT?我们首先考虑一下在Real Mode下的编程模型:
在Real Mode下,我们对一个内存地址的访问是通过Segment:Offset的方式来进行的,其中Segment是一个段的Base Address,一个Segment的最大长度是64 KB,这是16-bit系统所能表示的最大长度。而Offset则是相对于此Segment Base Address的偏移量。Base Address+Offset就是一个内存绝对地址。由此,我们可以看出,一个段具备两个因素:Base Address和Limit(段的最大长度),而对一个内存地址的访问,则是需要指出:使用哪个段?以及相对于这个段Base Address的Offset,这个Offset应该小于此段的Limit。当然对于16-bit系统,Limit不要指定,默认为最大长度64KB,而 16-bit的Offset也永远不可能大于此Limit。我们在实际编程的时候,使用16-bit段寄存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)来指定Segment,CPU将段积存器中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的Base Address。
到了Protected Mode,内存的管理模式分为两种,段模式和页模式,其中页模式也是基于段模式的。也就是说,Protected Mode的内存管理模式事实上是:纯段模式和段页式。进一步说,段模式是必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则这是纯段模式。
既然是这样,我们就先不去考虑页模式。对于段模式来讲,访问一个内存地址仍然使用Segment:Offset的方式,这是很自然的。由于 Protected Mode运行在32-bit系统上,那么Segment的两个因素:Base Address和Limit也都是32位的。IA-32允许将一个段的Base Address设为32-bit所能表示的任何值(Limit则可以被设为32-bit所能表示的,以2^12为倍数的任何指),而不象Real Mode下,一个段的Base Address只能是16的倍数(因为其低4-bit是通过左移运算得来的,只能为0,从而达到使用16-bit段寄存器表示20-bit Base Address的目的),而一个段的Limit只能为固定值64 KB。另外,Protected Mode,顾名思义,又为段模式提供了保护机制,也就说一个段的描述符需要规定对自身的访问权限(Access)。所以,在Protected Mode下,对一个段的描述则包括3方面因素:[Base Address, Limit, Access],它们加在一起被放在一个64-bit长的数据结构中,被称为段描述符。这种情况下,如果我们直接通过一个64-bit段描述符来引用一个段的时候,就必须使用一个64-bit长的段积存器装入这个段描述符。但Intel为了保持向后兼容,将段积存器仍然规定为16-bit(尽管每个段积存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段积存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段积存器来直接引用64-bit的段描述符。
怎么办?解决的方法就是把这些长度为64-bit的段描述符放入一个数组中,而将段寄存器中的值作为下标索引来间接引用(事实上,是将段寄存器中的高13 -bit的内容作为索引)。这个全局的数组就是GDT。事实上,在GDT中存放的不仅仅是段描述符,还有其它描述符,它们都是64-bit长,我们随后再讨论。
GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,所以 Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此积存器中的内容作为GDT的入口来访问GDT了。
GDT是Protected Mode所必须的数据结构,也是唯一的——不应该,也不可能有多个。另外,正象它的名字(Global Descriptor Table)所揭示的,它是全局可见的,对任何一个任务而言都是这样。
除了GDT之外,IA-32还允许程序员构建与GDT类似的数据结构,它们被称作LDT(Local Descriptor Table),但与GDT不同的是,LDT在系统中可以存在多个,并且从LDT的名字可以得知,LDT不是全局可见的,它们只对引用它们的任务可见,每个任务最多可以拥有一个LDT。另外,每一个LDT自身作为一个段存在,它们的段描述符被放在GDT中。
IA-32为LDT的入口地址也提供了一个寄存器LDTR,因为在任何时刻只能有一个任务在运行,所以LDT寄存器全局也只需要有一个。如果一个任务拥有自身的LDT,那么当它需要引用自身的LDT时,它需要通过LLDT将其LDT的段描述符装入此寄存器。LLDT指令与LGDT指令不同的时,LGDT指令的操作数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bit GDT的入口地址,以及16-bit的GDT Limit。而LLDT指令的操作数是一个16-bit的选择子,这个选择子主要内容是:被装入的LDT的段描述符在GDT中的索引值——这一点和刚才所讨论的通过段积存器引用段的模式是一样的。
更详细的请参照附件。
未完待续
上传的附件:
2010-12-31 21:30
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
28
为了跳转到保护模式,必须做以下几件事:
1.定义GDT列表(跟16位的段很像,只是16位段直接存储,32位段间接存储在内存中。并加上访问限制等,相当于定义段的列表,16位段都是已知的,32位段要以连续的数组形式定义在段数组GDT中,通过16位的索引-称为段选择子来定位)
2.装载GDT,这个再简单不过了,将GDT数组的地址和大小传给GDTR寄存器。
3.为了不搞混16位和32位中断,屏蔽16位中断
4.保证所有的协处理都被正确的Reset(相当于统统清零)
5.进入保护模式
6.启动内核
其实说了这么大一堆,其实跟16位没什么多大差别:
段地址存在GDT中,而GDT首地址和大小在GDTR寄存器中,大小是64位,数组的第一个元素全0,通过索引就可以定位段地址,加上偏移量就得到了地址。其他就是防止16位与32位混淆错乱,以及那个从16位到32位模式转换的关键一跳。键盘、鼠标、显示器都可以有段地址,通过地址都能访问了,于是乎,按照我的猜测,unix所有设备即文件就从这而来,这不全都连起来了,看起来很复杂,其实很简单是不?如果加上由bios控制的限制,ring0-ring3不就应运而生,对内存加以各种限制而已,bios其实就是一个操作系统的操作系统。想法很好,很符合设计模式的思路,封装的也不错,虽然我没有怎么学过操作系统!
这节虽然短点,但绝对是精华!
未完待续
2011-1-1 00:28
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
29
跳到保护模式的代码如下:
;代码头
  org        7c00h
  mov ax,0
  mov es,ax
  mov bx,7e00h
  mov ah,02
  mov al,1        ;加载扇区数
  mov ch,0
  mov cl,2
  mov dh,0
  mov dl,0
  int 13h        ;将磁盘第二扇区开始al个扇区装入内存
  jmp start
  
  ;定义GDT
        GDT_ADDRESS:
        dw         0,0,0,0                                        ;默认
        CS_ADDRESS:
        dw 0xFFFF,0,0x9A00,0x00CF                ;代码段,大小4G
        dw 0xFFFF,0,0x9200,0x00CF                 ;数据段, 大小4g
       
        GDT_LEN        equ $-GDT_ADDRESS
        ;GDTR LOAD       
        GDT_LOAD:
        dw        GDT_LEN       
        dw        0,0       
       
        CS_SELECTOR        equ CS_ADDRESS-GDT_ADDRESS       
       
        ;IDTR LOAD        中断向量
        IDT_LOAD:
        dw        0
        dw        0,0       

times         510-($-$$)        db        0        ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw         0xaa55                                ; 结束标志

start:
        ;设置GDT基址
        xor eax,eax                                ;清零
        ;compute ds:GDT_ADDRESS
        mov ax,ds                                       
        shl eax,4                                        ;段地址乘以16
        add eax,GDT_ADDRESS
        mov dword [GDT_LOAD+2],eax        ;装入基地址
        lgdt [GDT_LOAD]                        ;加载到GDTR寄存器
       
       
        ;初始化新的中断向量表
        lidt [IDT_LOAD]
       
        ;屏蔽所有可屏蔽中断-关掉所有8259中断
        mov al,0xff        ;mask all interrupts for now
        out 0xA1,al
        out 0x80,al        ;Delay is needed after doing I/O
        ;mov al,0xFB ; mask all irq‘s but irq2 which
        ;out 0x21,al
        ;out 0x80,al        ;Delay is needed after doing I/O

        ; 保证所有的协处理都被正确的Reset

        xor ax, ax
        out 0xf0,al
        out 0x80,al        ;Delay is needed after doing I/O

        out 0xf1,al
        out 0x80,al        ;Delay is needed after doing I/O
       
        ;进入32位保护模式        ;//CR0寄存器的PE位
        mov ax,1
        LMSW  ax
       
        ;启动内核       
        jmp dword CS_SELECTOR:kernel_start
        ;db 0x66, 0xea ; prefix + jmpi-opcode
        ;dw 0x7e00,0
        ;dw CS_SELECTOR
        int 3h
        int 3h
        xor ecx,edx
kernel_start:
        xor ebx,ecx
        jmp $
;代码尾

这次没有图形显示,只是验证跳入保护模式的过程,最关键的一句出现在第一句,就是前几节出现的org        7c00h,如果不加这句,其他全是对的就是不能在保护模式下根据段地址定位。
代码很简单,其中如屏蔽所有可屏蔽中断和协处理器清零,知道原理就可以了。代码含义在上两节的下载文件中。
这里要说的是打开20地址线,现在的计算机默认都会打开,所以没必要打开。
代码的含义是定义两个段,都是0初始地址,最大界限,最大权限的段,分别具有代码和数据段的特征。在装入gdtr寄存器后,打开进入保护模式的PE位,然后jmp到内核开始kernel_start,运行。实在是不用多说什么了。

        int 3h
        int 3h
        xor ecx,edx
只是为了测试,
以下具体讲解怎样调试,并且见证在bochs的伟大的一跳,大家会发现伟大的一跳实际上发生在LMSW  ax这条语句上,这时地址长度发生了显著的变化,但当那神奇的jmp时,还是会有些激动。
调试需要准备以下工具:
vc6或vc2005,...,2010
bochs 2.4.5
ultraedit或notepad
nasm
virtual pc 2007
1.随便在哪个不含空格,不含中文的地方新建个目录比如叫os
2.打开ultraedit,将上述代码粘到新建文本,存到os目录,起名os.asm
3.将nasm运行目录添加到环境path里,注销计算机,重新登录,使path起作用
4.命令行编译 nasm os.asm -o os.bin
5.用virtual pc 2007创建软盘映像,拷到os目录更名位newos.img
6.命令行运行copy /Y newos.img os.img
7.打开vc,以二进制打开os.img,os.bin
8.全选os.bin的二进制,并确切记住占有多少字节,ctol+c,或复制按钮
9.选中 os.img刚才一样多个字节,切记这是最重要的,否则长度会变。然后粘贴,或者ctol+v
10.os.img存盘
11.新建bochsrc.bxrc文件,内容

# what disk images will be used
floppya: 1_44=os.img, status=inserted

# choose the boot disk.
boot: floppy

# where do we send log messages?
log: bochsout.txt

# disable the mouse, since DLX is text only
mouse: enabled=0

12.在该目录创建dg.bat新文件,内容
"C:\Program Files\Bochs-2.4.5\bochsdbg.exe" -q -f bochsrc.bxrc

13.运行dg.bat
14.在bochs控制台输入
b 0x7e00
回车
c
回车
然后
u 0x7e00 0x7f00
回车
出现
00007e00: (                    ) xor eax, eax              ; 6631c0
00007e03: (                    ) mov ax, ds                ; 8cd8
00007e05: (                    ) shl eax, 0x04             ; 66c1e004
00007e09: (                    ) add eax, 0x00007c19       ; 6605197c0000
00007e0f: (                    ) mov dword ptr ds:0x7c33, eax ; 66a3337c
00007e13: (                    ) lgdt ds:0x7c31            ; 0f0116317c
00007e18: (                    ) lidt ds:0x7c37            ; 0f011e377c
00007e1d: (                    ) mov al, 0xff              ; b0ff
00007e1f: (                    ) out 0xa1, al              ; e6a1
00007e21: (                    ) out 0x80, al              ; e680
00007e23: (                    ) xor ax, ax                ; 31c0
00007e25: (                    ) out 0xf0, al              ; e6f0
00007e27: (                    ) out 0x80, al              ; e680
00007e29: (                    ) out 0xf1, al              ; e6f1
00007e2b: (                    ) out 0x80, al              ; e680
00007e2d: (                    ) mov ax, 0x0001            ; b80100
00007e30: (                    ) lmsw ax                   ; 0f01f0
00007e33: (                    ) jmp far 0008:00007e42     ; 66ea427e00000800
00007e3b: (                    ) int 0x03                  ; cd03
00007e3d: (                    ) int 0x03                  ; cd03
00007e3f: (                    ) xor ecx, edx              ; 6631d1
00007e42: (                    ) xor ebx, ecx              ; 6631cb
然后
b 0x7e30
回车
然后
s
回车
单步执行
伟大的模式转换出现了
(0) Breakpoint 2, 0x0000000000007e33 in ?? ()
Next at t=28191868
(0) [0x00007e33] 0000:0000000000007e33 (unk. ctxt): jmp far 0008:00007e42     ;
66ea427e00000800
然后
s
回车
跳入了内核
Next at t=28191869
(0) [0x00007e42] 0008:0000000000007e42 (unk. ctxt): xor bx, cx             ;6631cb
当然内核可以加载到任何地方,这里只是演示,
自此在bochs里亲眼见证了整个过程。
比较两张图可以看到地址长度的明显变化,注:演示图版本与上述说明略有出入。
图中可以看到段地址是8(0008:00007e42),高13位是索引值,剩下第三位这里是0,
所以第一项就是1000b,即8
第二项就是10000b,即16,注:2就是10b,后添3个0
...
下图
(0) [0x00007e42] 0008:0000000000007e42 (unk. ctxt): xor bx, cx                ;
明显看到已经跳转到了内核起始处
下一节将详细分析bin文件格式及反汇编
未完待续
上传的附件:
2011-1-2 01:50
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
30
经过细致分析,nasm编译出来的格式跟内存映像完全一样,
实际上org        7c00h是一条伪语句
就是告诉编译器程序要加载在7c00h,相对地址请根据7c00h来计算,
也就是说编译器处理了,
比如
add eax,GDT_ADDRESS
这句,经过两种编译方式得到的语句分别是(IDPRO反编译)
有org        7c00h编译成
66 05 19 7C 00 00                       add     eax, 7C19h
没有的编译成
66 05 19 00 00 00                       add     eax, 19h
所以org        7c00h这条伪语句就是就是告诉编译器(相当于链接器)链接位置,
以便产生正确的label地址!
未完待续
2011-1-2 02:58
0
雪    币: 38
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
31
请问这位仁兄,你是看什么操作系统的书的? 谢了
2011-1-2 10:18
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
32
互联网,ollydbg,idapro非常非常强大
至于书
参考“自己动手写操作系统”
参考网上分析dos,linux启动源码
2011-1-2 12:12
0
雪    币: 30
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
33
支持支持
2011-1-2 16:24
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
34
现在需要跨过1m边界去读写内存,并且自由跳转。
但有一个残酷的问题是bin编译出来的是16位的汇编,伟大的一跳以后都是32位汇编,如果再用nasm编译出16位bin的方式显然就大错特错了,在真正开始32位汇编前,我们需要做一些事情验证我们的正确性,所以,我们必须将32位汇编硬编码进我们初生的内核。
类似于病毒,我们需要做的是在0x500000处写入一句代码,并jmp过去,然后看写的对不对,为了让代码简单,我们写入以下语句mov eax, ecx,在ollydbg中汇编mov eax, ecx,得到db 0x89,0xc8
下面在ollydbg汇编出如下语句
MOV ECX,500000
MOV WORD PTR DS:[ECX],0C889
JMP ECX
0C889就是db 0x89,0xc8,将其写入ECX指定的地址中,注意只写两字节,然后jmp过去,代码是如此的简单,但执行起来却绝非如此。
先列出具体的代码
org        7c00h
  jmp start
  
  ;定义GDT
        GDT_ADDRESS:
        dw         0,0,0,0                                                ;默认
        CS_ADDRESS:
        dw 0xFFFF,0,0x9A00,0x00CF                ;代码段,大小4G
        dw 0xFFFF,0,0x9200,0x00CF                 ;数据段, 大小4g
       
        GDT_LEN        equ $-GDT_ADDRESS
        ;GDTR LOAD       
        GDT_LOAD:
        dw        GDT_LEN       
        dw        0,0        ;compute as follow
       
        CS_SELECTOR        equ CS_ADDRESS-GDT_ADDRESS       
       
        ;IDTR LOAD        中断向量
        IDT_LOAD:
        dw        0
        dw        0,0       
start:
        ;设置GDT基址
        xor eax,eax                                                ;清零
        ;compute ds:GDT_ADDRESS
        mov ax,ds                                                        ;ds扩展到32位eax
        shl eax,4                                                        ;段地址乘以16
        add eax,GDT_ADDRESS
        mov dword [GDT_LOAD+2],eax                        ;装入基地址
        lgdt [GDT_LOAD]                                        ;加载到GDTR寄存器
       
       
        ;初始化新的中断向量表
        lidt [IDT_LOAD]
       
        ;屏蔽所有可屏蔽中断-关掉所有8259中断
        mov al,0xff        ;mask all interrupts for now
        out 0xA1,al
        out 0x80,al        ;Delay is needed after doing I/O
        ;mov al,0xFB ; mask all irq‘s but irq2 which
        ;out 0x21,al
        ;out 0x80,al        ;Delay is needed after doing I/O

        ; 保证所有的协处理都被正确的Reset

        xor ax, ax
        out 0xf0,al
        out 0x80,al        ;Delay is needed after doing I/O

        out 0xf1,al
        out 0x80,al        ;Delay is needed after doing I/O
       
        ;进入32位模式        ;//CR0寄存器的PE位
        mov ax,1
        LMSW  ax
       
        jmp dword CS_SELECTOR:kernel_start
        dw 0
        kernel_start:    ;启动内核
                db 0xB9,0x00,0x00,0x50,0x00,0x66,0xC7,0x01,0x89,0xC8,0xFF,0xE1
       
times         510-($-$$)        db        0        ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw         0xaa55                                ; 结束标志

注意:
db 0xB9,0x00,0x00,0x50,0x00,0x66,0xC7,0x01,0x89,0xC8,0xFF,0xE1
其实就是跳入保护模式的32位汇编,如果用bin编译出来就有点乱套了
对应的代码就是
MOV ECX,500000
MOV WORD PTR DS:[ECX],0C889
JMP ECX
然后自以为就okay了,编译后并入img后,装入bochs进行调试,意外发生了,
当单步执行到MOV WORD PTR DS:[ECX],0C889,灾难发生了,bochs跳转到起始处
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b
也就是说咱们刚初生的系统夭折了,经过反复测试,一条条比对,测试,终于知道了隐藏在深处的原因,因为我们装入的段是代码段有可读和可执行的权限,我们需要写的权限,但没有,我们的第二个代码段有这个权限,这时猜测需要将数据段装入cs和ds段,装入后写完病毒,再将第一个有执行权限的代码段装入,就okay了,结果是猜测是完全正确的。
bochs命令行命令如下:
b 0x7c00
c
u 0x7c00 0x7c90
b 0x7c57
c
s
s
u 0x7c64 0x7c80
set cs=0x10
set ds=0x10
s
s
set cs=8
set ds=8
s
终于我们看见了我们之前写入的久违的病毒代码
(0) [0x00500000] 0008:0000000000500000 (unk. ctxt): mov eax, ecx              ;
注意gdt第二条索引是0x10000,即16
okay,我们制作了病毒,并将其装入特定的大内存处,并跳转成功。
至于在程序中将代码段转换留给大家做练习。
未完待续
上传的附件:
2011-1-2 17:53
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
35
终于,我们要鸟枪换炮了,我们以下将用c编写内核代码,并在bochs里面看看我们的大炮是怎么被换上的。
很可惜,现在流行的很多编译器不能编译无格式的,能编译成exe,coff,elf等格式,我们现在内核还很稚嫩,还没有loader,
所以只能加载经过编译的无格式的二进制机器码。
找啊找啊,终于GCC浮出水面,怪不得linux不能在windows下编译,vc只能编译有格式的代码,著名的如pe,只好到linux下了。
下了个ubuntu,其实什么linux都无所谓,只要能声称有能编译内核的gcc就可以。
在命令行,敲
gcc
回车
ld
回车
看看有反应,没有讨厌的没有安装就可以了。
从最粗浅的c写起
命令行用vi工具编辑下列文本,存为test.c

void main()
{
    int s,i;
   for(i=0,s=0;i<=100;i++)
   {
        s+=i;
   }
}

代码真是粗浅,我们最好现在不调运行时库,系统调用那更别提了!我们现在只编写最原始的c代码,
当然可以嵌入汇编。
在命令行键入
gcc –c test.c
回车后生成test.o
再键入
ld –Ttext 0x0 –e main --oformat binary –o test.bin test.o
回车后生成test.bin
将test.bin拷到windows系统,最好用vmware7以上来做,共享做得很好
然后接着nasm照例编出os.bin
代码如下
  org        7c00h
  mov ax,0
  mov es,ax
  mov bx,7e00h
  mov ah,02
  mov al,1        ;加载扇区数
  mov ch,0
  mov cl,2
  mov dh,0
  mov dl,0
  int 13h        ;将磁盘第二扇区开始al个扇区装入内存
  jmp start
  
  ;定义GDT
        GDT_ADDRESS:
        dw         0,0,0,0                                        ;默认
        CS_ADDRESS:
        dw 0xFFFF,0,0x9A00,0x00CF                ;代码段,大小4G
        dw 0xFFFF,0,0x9200,0x00CF                 ;数据段, 大小4g
       
        GDT_LEN        equ $-GDT_ADDRESS
        ;GDTR LOAD       
        GDT_LOAD:
        dw        GDT_LEN       
        dw        0,0        ;compute as follow
       
        CS_SELECTOR        equ CS_ADDRESS-GDT_ADDRESS
       
        ;IDTR LOAD        中断向量
        IDT_LOAD:
        dw        0
        dw        0,0       

start:
        ;设置GDT基址
        xor eax,eax                                                ;清零
        ;compute ds:GDT_ADDRESS
        mov ax,ds                                                        ;ds扩展到32位eax
        shl eax,4                                                        ;段地址乘以16
        add eax,GDT_ADDRESS
        mov dword [GDT_LOAD+2],eax                        ;装入基地址
        lgdt [GDT_LOAD]                                        ;加载到GDTR寄存器
       
       
        ;初始化新的中断向量表
        lidt [IDT_LOAD]
       
        ;屏蔽所有可屏蔽中断-关掉所有8259中断
        mov al,0xff        ;mask all interrupts for now
        out 0xA1,al
        out 0x80,al        ;Delay is needed after doing I/O
        ;mov al,0xFB ; mask all irq‘s but irq2 which
        ;out 0x21,al
        ;out 0x80,al        ;Delay is needed after doing I/O

        ; 保证所有的协处理都被正确的Reset

        xor ax, ax
        out 0xf0,al
        out 0x80,al        ;Delay is needed after doing I/O

        out 0xf1,al
        out 0x80,al        ;Delay is needed after doing I/O
       
        ;进入32位模式        ;//CR0寄存器的PE位
        mov ax,1
        LMSW  ax
       
        jmp dword CS_SELECTOR:kernel_start
        dw 0       
       
times         510-($-$$)        db        0        ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw         0xaa55                                ; 结束标志
kernel_start:        ;占位符,会放置c语言编译后的代码
        db 0

好了,编译出来,根据以前的方法将os.bin拷贝到os.img前512字节,
将linux下c编译出来的test.bin拷贝到os.img的512字节后(55aa后面)
存盘,然后用bochs调试,
命令如下
b 0x7e00
c
u 0x7e00 0x7f00
跳转到c编译过的汇编代码的开头,得到反汇编代码
00007e00: (                    ) lea ecx, dword ptr ss:[esp+4] ; 8d4c2404
00007e04: (                    ) and esp, 0xfffffff0       ; 83e4f0
00007e07: (                    ) push dword ptr ds:[ecx-4] ; ff71fc
00007e0a: (                    ) push ebp                  ; 55
00007e0b: (                    ) mov ebp, esp              ; 89e5
00007e0d: (                    ) push ecx                  ; 51
00007e0e: (                    ) sub esp, 0x00000010       ; 83ec10
00007e11: (                    ) mov dword ptr ss:[ebp-12], 0x00000000 ; c745f4
00000000
00007e18: (                    ) mov dword ptr ss:[ebp-8], 0x00000000 ; c745f80
0000000
00007e1f: (                    ) jmp .+10                  ; eb0a
00007e21: (                    ) mov eax, dword ptr ss:[ebp-12] ; 8b45f4
00007e24: (                    ) add dword ptr ss:[ebp-8], eax ; 0145f8
00007e27: (                    ) add dword ptr ss:[ebp-12], 0x00000001 ; 8345f4
01
00007e2b: (                    ) cmp dword ptr ss:[ebp-12], 0x00000064 ; 837df4
64
00007e2f: (                    ) jle .-16                  ; 7ef0
00007e31: (                    ) add esp, 0x00000010       ; 83c410
00007e34: (                    ) pop ecx                   ; 59
00007e35: (                    ) pop ebp                   ; 5d
00007e36: (                    ) lea esp, dword ptr ds:[ecx-4] ; 8d61fc
00007e39: (                    ) ret                       ; c3
用idapro打开test.bin,反汇编,并将显示字节数设置为8,得到
seg000:00000000 8D 4C 24 04                                   lea     ecx, [esp+4]
seg000:00000004 83 E4 F0                                      and     esp, 0FFFFFFF0h
seg000:00000007 FF 71 FC                                      push    dword ptr [ecx-4]
seg000:0000000A 55                                            push    ebp
seg000:0000000B 89 E5                                         mov     ebp, esp
seg000:0000000D 51                                            push    ecx
seg000:0000000E 83 EC 10                                      sub     esp, 10h
seg000:00000011 C7 45 F4 00 00 00 00                    mov     dword ptr [ebp-0Ch], 0
seg000:00000018 C7 45 F8 00 00 00 00                    mov     dword ptr [ebp-8], 0
seg000:0000001F EB 0A                                         jmp     short loc_2B
seg000:00000021                               ; ---------------------------------------------------------------------------
seg000:00000021
seg000:00000021                               loc_21:                                 ; CODE XREF: seg000:0000002Fj
seg000:00000021 8B 45 F4                                      mov     eax, [ebp-0Ch]
seg000:00000024 01 45 F8                                      add     [ebp-8], eax
seg000:00000027 83 45 F4 01                                   add     dword ptr [ebp-0Ch], 1
seg000:0000002B
seg000:0000002B                               loc_2B:                                 ; CODE XREF: seg000:0000001Fj
seg000:0000002B 83 7D F4 64                                cmp     dword ptr [ebp-0Ch], 64h
seg000:0000002F 7E F0                                         jle     short loc_21
seg000:00000031 83 C4 10                                      add     esp, 10h
seg000:00000034 59                                            pop     ecx
seg000:00000035 5D                                            pop     ebp
seg000:00000036 8D 61 FC                                      lea     esp, [ecx-4]
seg000:00000039 C3                                            retn
经过逐字节比对,完全一样,没有任何区别,
也就是说,我们加载了c编译后的内核代码,并且开始运行了,我们正在装备武器,之后我们将武装
到牙齿。
未完待续
上传的附件:
2011-1-2 20:58
0
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
36
纠正个错误,GDTR大小是48位,不是64位。

forchinese兄这是你写的吗?继续努力咯。我也在写一个类似的教程,刚刚开始写。

后面的部分准备一进保护模式就直接跳到x64,免得跟别人的教程内容重复了LOL
2011-1-2 21:01
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
37
呵呵,笔误,感谢。本意是
段地址存在GDT中,而GDT首地址和大小在GDTR寄存器中,大小是64位
指段地址大小是64位,但看起来很容易理解成GDTR大小是64位。
看得还很仔细啊!
2011-1-2 21:13
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
38
给个链接,也好学习支持一下。
2011-1-2 21:15
0
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
39
http://www.cnblogs.com/bombless/archive/2010/12/28/writing-x86-os-part1.html

我从不同角度写的,呵呵。
2011-1-2 21:52
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
40
十分感谢,我不是在写教程,我是从无到有,正学呢!
2011-1-2 21:55
0
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
41
段选择子和段寄存器的关系很耐人寻味,我才看完2篇文章,推荐你也看看:

http://blog.csdn.net/favory/archive/2008/12/27/3618387.aspx

http://blog.csdn.net/perisun/archive/2008/11/28/3400772.aspx

第2篇文章重点看它的评论
2011-1-2 21:57
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
42
十分的感谢!
2011-1-2 22:01
0
雪    币: 26
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
43
我也初学,可能我们水平差不多,多多交流哈,呵呵。

我今天才学会刚刚进保护模式,这2天下一步是学会看懂LINUX 0.11的BOOTSECT.S。

看的是这个:http://www.julienlecomte.net/simplix/sh.php?f=src/boot/bootsect.S

还有就是尽可能快些学会从保护模式进长模式。
想要挑战下英语水平,因为国内很多中文资料对长模式的讨论不是很深入。

有挑战才有意思,呵呵。
2011-1-2 22:14
0
雪    币: 138
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
44
我是新手~什么教程都看看
谢谢
2011-1-3 00:17
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
45
没看过这个,帮助很大,实在是感谢!
2011-1-3 00:39
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
46
你那个网站还不错,看了一部分,茅塞顿开,原来如此。操作系统实现终于明白了。但为了明白系统实现,关键还得弄清楚一些基本的东西,因为论坛发帖限制,等过24小时再发。
不识庐山真面目,只缘身在此山中。
2011-1-3 01:43
0
雪    币: 163
活跃值: (103)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
47
楼主好贴!我顶
2011-1-3 07:14
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
48
还是先看看吧
很多都不明白
2011-1-3 08:04
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
49
楼主啊 你发的是一个系列吧
先支持下 慢慢研究
2011-1-3 08:05
0
雪    币: 81
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
50
现在终于可以开始显示字符串了,谁料想艰辛之路刚刚开始。
先给出boot端代码
LoadSectorIndex                equ                02
  LoadSectorTotal         equ         1
  LoadAddress                                equ                7e00h
  EntryPoint                                equ                00f8h
  
  org        7c00h
  mov ax,0
  mov es,ax
  mov bx,LoadAddress
  mov ah,2
  mov al,LoadSectorTotal        ;加载扇区数
  mov ch,0
  mov cl,LoadSectorIndex
  mov dh,0
  mov dl,0
  int 13h        ;将磁盘第LoadSectorIndex扇区开始LoadSectorTotal个扇区装入内存LoadAddress
  jmp start
  
  ;定义GDT
        GDT_ADDRESS:
        dw         0,0,0,0                                                                                ;默认
        CS_ADDRESS:
        dw 0xFFFF,0,0x9A00,0x00CF                        ;代码段,大小4G
        dw 0xFFFF,0,0x9200,0x00CF                 ;数据段, 大小4g
       
        GDT_LEN                equ $-GDT_ADDRESS
        GDT_Total        equ        (GDT_LEN-8)/8
       
        ;GDTR LOAD       
        GDT_LOAD:
        dw        GDT_LEN       
        dw        0,0        ;compute as follow
       
        CS_SELECTOR        equ CS_ADDRESS-GDT_ADDRESS       
       
        ;IDTR LOAD        中断向量
        IDT_LOAD:
        dw        0
        dw        0,0       
       
start:
        ;设置GDT基址
        xor eax,eax                                                        ;清零
        ;compute ds:GDT_ADDRESS
        mov ax,ds                                                                ;ds扩展到32位eax
        shl eax,4                                                                ;段地址乘以16
        add eax,GDT_ADDRESS
        mov dword [GDT_LOAD+2],eax                ;装入基地址
        lgdt [GDT_LOAD]                                                ;加载到GDTR寄存器
       
       
        ;初始化新的中断向量表
        lidt [IDT_LOAD]
       
        ;屏蔽所有可屏蔽中断-关掉所有8259中断
        mov al,0xff        ;mask all interrupts for now
        out 0xA1,al
        out 0x80,al        ;Delay is needed after doing I/O
        ;mov al,0xFB ; mask all irq‘s but irq2 which
        ;out 0x21,al
        ;out 0x80,al        ;Delay is needed after doing I/O

        ; 保证所有的协处理都被正确的Reset
        xor ax, ax
        out 0xf0,al
        out 0x80,al        ;Delay is needed after doing I/O

        out 0xf1,al
        out 0x80,al        ;Delay is needed after doing I/O
       
       
          
        ;进入32位模式        ;//CR0寄存器的PE位
        mov ax,1
        LMSW  ax
       
        ;设置段为第二项-数据段
        mov ax,0x10
        mov ds,ax  
        mov es,ax  
        mov fs,ax
        mov gs,ax  
        mov ss,ax   
       
        jmp dword CS_SELECTOR:kernel_start+EntryPoint
        dw 0       
       
times         510-($-$$)        db        0        ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw         0xaa55                                ; 结束标志
kernel_start:                ;占位符,会放置c语言编译后的代码
        db 0
有几个需要说明的地方,我们需要写显存,默认在0xb8000,所以需要将显示段加载,mov gs,ax  实际上就是说gs指向第二个段即数据段,大家发现kernel多了个EntryPoint入口地址,这个值通常操作系统会在pe头上读取,我们是无格式的,所以费了不是一点半点的周折,下面我们来看内核代码

typedef unsigned char uint8_t;

/* The address of the video memory */  
#define VIDEO ((uint8_t *) 0xb8000)  
/* Video text attributes. */  
#define GFX_ATTR(textcolor, bgcolor, blinking) \  
     ((blinking << 7) | (bgcolor << 4) | textcolor)  
/* Screen geometry. */  
#define SCREEN_ROWS 25  
#define SCREEN_COLS 80  

/* Text attributes. */  
#define GFX_STATIC             0  
#define GFX_BLINKING           1  
  
/* Foreground and background colors. */  
#define GFX_BLACK              0 /* 0000 */  
#define GFX_BLUE               1 /* 0001 */  
#define GFX_GREEN              2 /* 0010 */  
#define GFX_CYAN               3 /* 0011 */  
#define GFX_RED                4 /* 0100 */  
#define GFX_MAGENTA            5 /* 0101 */  
#define GFX_BROWN              6 /* 0110 */  
#define GFX_LIGHT_GRAY         7 /* 0111 */  
  
/* Foreground-only colors. */  
#define GFX_DARK_GRAY          8 /* 1000 */  
#define GFX_LIGHT_BLUE         9 /* 1001 */  
#define GFX_LIGHT_GREEN       10 /* 1010 */  
#define GFX_LIGHT_CYAN        11 /* 1011 */  
#define GFX_LIGHT_RED         12 /* 1100 */  
#define GFX_LIGHT_MAGENTA     13 /* 1101 */  
#define GFX_YELLOW            14 /* 1110 */  
#define GFX_WHITE             15 /* 1111 */  
  
/* Light gray text on a black background. */  
#define DEFAULT_TEXT_ATTR  GFX_ATTR(GFX_LIGHT_GRAY, GFX_BLACK, GFX_STATIC)  

/*
  * Clears the screen.
  */  
void gfx_cls(void)  
{  
     int i;  
   
     for (i = 0; i < SCREEN_ROWS * SCREEN_COLS; i++) {  
         *(VIDEO + 2*i) = 0;  
         *(VIDEO + 2*i + 1) = DEFAULT_TEXT_ATTR;  
     }     
     
}

/*
* Writes the specified character at the specified position.
* Does NOT handle scrolling.
* Does NOT move the cursor.
*/  
void videomem_putchar(char c, int row, int col, uint8_t textattr)  
{  
    int idx = 2 * (row * SCREEN_COLS + col);  
    *(VIDEO + idx) = c;  
    *(VIDEO + idx + 1) = textattr;  
}  

/*
* Writes the specified string starting at the specified position.
* The string must be NULL terminated.
* Does NOT handle scrolling.
* Does NOT move the cursor.
*/  
void videomem_putstring(char *s, int row, int col, uint8_t textattr)  
{  
    char c;  
    int i = row * SCREEN_COLS + col;
  
    while ((c = *s++) != '\0')
        {  
        if (i < 0 || i >= SCREEN_ROWS * SCREEN_COLS)  
            break;  
        *(VIDEO + 2*i) = c;  
        *(VIDEO + 2*i + 1) = textattr;  
        i++;  
    }  
}  
  
void entry_os()
{
        gfx_cls();       
        videomem_putstring("First os!\0",3,3,GFX_ATTR(GFX_RED, GFX_BLUE, GFX_STATIC));
        for(;;){}
}

是有一点长,稍微解释一下,代码意思很简单gfx_cls清屏,videomem_putstring在显存输出字符串,清屏是将屏幕每一个显存用默认格式写一遍,videomem_putstring是在指定行,指定列按指定格式输出字符串,具体格式参数意义在注释中很清楚,还要详细请在网络搜索,其中部分代码节选自上次有位兄弟给的链接,大大受益,马上就用上了,
http://www.julienlecomte.net/simplix/
其中有些代码那是写得相当的经典,
好了,用上节的方法编译、拼接、加载到bochs却只是清了屏,什么也没有显示,于是打出了linux对应的汇编代码,然后跟idapro逐条比对,终于发现了问题所在,ld不知道你要加载到哪里,按照参数加载到0,字符串显然是静态分配的,所以link位置完全错了,通过bochs将近一小时来回一条条各种版本比对,一遍一遍比较各种版本的汇编语句,字节码,找到了上述问题。
网上基本没有任何资料关于这方面,所以只好对着ld详细文档,拿着放大镜找,终于知道了问题所在,原来问题在这里
ld -Ttext 0 -e entry_os --oformat binary -o os.bin os.o
这里,原来-Ttext 0即指代码段首地址,
以前用的顺手,没出错,就以为是圣经了,
修改如下:
ld -Ttext 0x7e00 -e entry_os --oformat binary -o os.bin os.o
好了编译好boot.bin
编译好linux下的os.bin
并到空的软盘映像中,装入bochs中,天哪,我们的os终于可以在任何位置显示任何格式的任何字符串了!整整4小时,终于搞定了,差不多二进制os.bin的每个字节基本上都快熟悉了!
entry_point地址是将idapro反汇编的地址跟linux下gcc汇编代码比对得到的!
如何,现在我们已经坐上了火车,在起飞前,需要将安全带绑好!前面风景更美好!
未完待续
上传的附件:
2011-1-3 22:28
0
游客
登录 | 注册 方可回帖
返回
//