分享
 
 
 

从linux0.11引导代码小窥内存分段机制

王朝system·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

其实只是一点笔记,所以肯定会有错误,好在计算机科学是实践性很强的科学,一切都将以实验数据得出,我绝不会妄下结论,这也就减少了错误的发生。

阅读本文手头上应该有一份linux0.11源代码,引导程序调试软件bochs(其实是个虚拟机,不过它的调试功能实在是完美)和配套的linux0.11内核img(linux-0.11-devel-040329.zip)。最好再有一本代码注释,推荐赵炯博士的《Linux内核完全注释——内核版本0.11》。显然,bochs的使用方法必须知道,具体操作请参阅《Linux内核完全注释》第14章;在bochs能够正确运行之后,使用bochsdbg进行调试,其使用方法见“Linux内核调试基本方法”。要想很好的理解操作系统,应具备一定的底层知识,推荐《深入理解计算机系统》。如果对老版本的linux很有兴趣,建议去OldLinux论坛,感谢赵炯博士的无私奉献。

调试环境的的建立

下载linux-0.11-devel-040329.zip,解压缩到bochs的安装目录,其中包含一个bochs2.1.1的安装程序和linux内核img,找到bochsrc-hd.bxrc文件的12、36行,修改其中的$ BXSHARE为bochs的安装路径,如果就是上级目录,则可直接改为“..”,如:

12> romimage: file=..\BIOS-bochs-latest, address=0xf0000

编辑run.bat文件,将其中所有内容改为:

"..\bochsdbg" -q -f bochsrc-Hd.bxrc

运行run.bat,即启动调试工具bochsdbg。

实模式下的分段寻址

在0x0000:0x7c00处设置一个断点,在Linux引导程序开始处暂停。命令行如下:

<bochs:1> vbreak 0x0:0x7c00

<bochs:2> c

(0) Breakpoint 1, 0x7c00 (0x0:0x7c00)

Next at t=16252460

(0) [0x00007c00] 0000:7c00 (unk. ctxt): mov ax, 0x7c0 ; b8c007

0x0000:0x7c00即以实模式下分段机制书写的逻辑地址,其物理地址的计算方法为0x0000<<4 + 0x7c00 = 0x00007c00,从输出信息可以看到程序在物理地址0x00007c00处暂停。

引导程序一开始将其自身代码从0x7c00处复制到0x90000处,这个过程首先将0x7c0赋值给ds做为源数据段,将0x9000赋值给es做为目的数据段,即将ds:si的数据复制到es:di所在位置。现在通过调试来验证这一复制过程,以及进一步了解实模式下的分段行为。由ds:si => 0x7c0:0x0 => 0x7c0<<4+0x0 = 0x7c00,由es:di => 0x9000:0x0 => 0x9000<<4+0x0 = 0x90000,对比这两个绝对地址0x7c00和0x90000的数据即可验证上述复制过程。命令行如下:

<bochs:3> x /8 0x7c00

[bochs]:

0x00007c00 <bogus+ 0>: 0x8e07c0b8 0x9000b8d8 0x00b9c08e

0x29f62901

0x00007c10 <bogus+ 16>: 0xeaa5f3ff 0x90000018 0xd88ec88c

0xd08ec08e

<bochs:4> u /10

00007c00: ( ): mov ax, 0x7c0 ; b8c007

00007c03: ( ): mov ds, ax ; 8ed8

00007c05: ( ): mov ax, 0x9000 ; b80090

00007c08: ( ): mov es, ax ; 8ec0

00007c0a: ( ): mov cx, 0x100 ; b90001

00007c0d: ( ): sub si, si ; 29f6

00007c0f: ( ): sub di, di ; 29ff

00007c11: ( ): rep movsw word ptr es:[di], word ptr ds:[si] ;

f3a5

00007c13: ( ): jmp far 9000:0018 ; ea18000090

00007c18: ( ): mov ax, cs ; 8cc8

<bochs:5> break 0x7c13

<bochs:6> c

(0) Breakpoint 2, 0x7c13 in ?? ()

Next at t=16252723

(0) [0x00007c13] 0000:7c13 (unk. ctxt): jmp far 9000:0018 ; ea18000090

<bochs:7> x /8 0x90000

[bochs]:

0x00090000 <bogus+ 0>: 0x8e07c0b8 0x9000b8d8 0x00b9c08e

0x29f62901

0x00090010 <bogus+ 16>: 0xeaa5f3ff 0x90000018 0xd88ec88c

0xd08ec08e

在反汇编代码中看到jmp far 9000:0018这一行,通过调试可看到其实际效果:将cs段设置为0x9000,从偏移量0x18处开始执行,也就是设置eip为0x18。命令行如下:

<bochs:8> info r

……

eip 0x7c13 0x7c13

eflags 0x246 582

cs 0x0 0

……

<bochs:9> n

Next at t=16252724

(0) [0x00090018] 9000:0018 (unk. ctxt): mov ax, cs ; 8cc8

<bochs:10> info r

……

eip 0x18 0x18

eflags 0x246 582

cs 0x9000 36864

……

红色标记的cs和eip组合起来的值cs:eip即指向实模式下的代码逻辑位置。同样通过cs<<4 + eip来计算出实际地址。

建立GDT表

在保护模式下,段寄存器所存储的将是段描述符表的某个索引值,索引值指定的段描述符项中含有需要寻址的内存段的基地址、段的最大长度值和段的访问级别等信息。计算线性地址的示意图如下:

[/url]

图1: 实模式和保护模式下寻址方式比较(摘自《Linux内核完全注释》)

因此,在进入保护模式之前,需要建立GDT表,并让gdtr指向该表基址。在进入保护模式之前,Setup.s中的代码将设置GDT表,其指令为:

end_move:

mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)

mov ds,ax

lidt idt_48 ! load idt with 0,0

lgdt gdt_48 ! load gdt with whatever appropriate

首先进入Setup程序(0x9020:0x0000处),找到lgdt gdt_48的指令位置,继续调试,命令行如下:

<bochs:11> break 0x90200

<bochs:12> c

(0) Breakpoint 3, 0x90200 in ?? ()

Next at t=16483610

(0) [0x00090200] 9020:0000 (unk. ctxt): mov ax, 0x9000 ; b80090

<bochs:13> u /100

……

0009029d: ( ): lidt ds:0x12c ; 0f011e2c01

000902a2: ( ): lgdt ds:0x132 ; 0f01163201

……

<bochs:14> break 0x902a2

<bochs:15> c

(0) Breakpoint 4, 0x902a2 in ?? ()

Next at t=16750806

(0) [0x000902a2] 9020:00a2 (unk. ctxt): lgdt ds:0x132 ; 0f01163201

反汇编代码lgdt ds:0x132表明将ds:0x132所在位置的数据赋给gdtr,lgdt总共需要6个字节,其中两个字节为GDT表的长度,另外4个字节表明GDT表的基址。通过调试可以看到这条指令的实际作用,命令行如下:

<bochs:16> info r

……

ds 0x9020 36896

……

<bochs:17> xp /4 0x9020:0x132

[bochs]:

0x00090332 <bogus+ 0>: 0x03140800 0x00000009 0x00000000

0x00000000

<bochs:18> n

Next at t=16750807

(0) [0x000902a7] 9020:00a7 (unk. ctxt): call .+0x109 ; e85f00

<bochs:19> dump_cpu

……

gdtr:base=0x90314, limit=0x800

idtr:base=0x0, limit=0x0

……

0x00090332开始的8个字节分别是:0x03140800 0x00000009,intel机器采用的小端法,即0x0009为GDT表基址的高16位,0x0314为GDT表基址的低16位,0x0800为GDT表的长度。调试输出信息gdtr:base=0x90314, limit=0x800即验证这一结果。这些常数数据在Setup.s的205到224行定义。可以通过GDT表基址来查看一下GDT表,命令行如下:

<bochs:20> x /10 0x90314

[bochs]:

0x00090314 <bogus+ 0>: 0x00000000 0x00000000 0x000007ff

0x00c09a00

0x00090324 <bogus+ 16>: 0x000007ff 0x00c09200 0x00000000

0x08000000

0x00090334 <bogus+ 32>: 0x00090314 0x00000000

按照一个描述符8字节长度整理一下得:

0x00000000 0x00000000 ! dummy

0x000007ff 0x00c09a00 ! 内核代码段描述符

0x000007ff 0x00c09200 ! 内核数据段描述符

0x00000000 0x08000000 !

0x00090314 0x00000000 ! GDT表项设置后紧接的idt_48,gdt_48的常数数据,在这个临时GDT表中无意义,实际也不会被索引到

接下来将进入保护模式,并使用这个临时GDT表进行寻址。

保护模式下的分段寻址

在Setup.s中找到进入保护模式的代码:

mov ax,#0x0001 ! protected mode (PE) bit

lmsw ax ! This is it!

jmpi 0,8 ! jmp offset 0 of segment 8 (cs)

前两行指令设置保护模式比特位PE,第三行代码用保护模式下的寻址方式进行跳转。首先进入程序找到jmpi 0,8这一行代码所在位置,命令行如下:

<bochs:21> u /100

……

000902fe: ( ): mov ax, 0x1 ; b80100

00090301: ( ): lmsw ax ; 0f01f0

00090304: ( ): jmp far 0008:0000 ; ea00000800

……

jmp far 0008:0000指令的实际效果是设置cs为0x0008,设置eip为0x0000,这里的0x0008即为保护模式下的段选择符,写成二进制形式0000000000001000,前两位00表示特权级0,第三位0表示该选择符用于选择全局描述符表,高13位0000000000001表示使用全局描述符的第一项,即前面提到的内核代码段选择符:0x00007fff 0x00c09a00,0x00007fff表示这个段基址为0x0000,段限长0x7fff。调试验证这一分析结果,命令行如下:

<bochs:22> break 0x00090304

<bochs:23> c

(0) Breakpoint 5, 0x90304 in ?? ()

Next at t=16750869

(0) [0x00090304] 9020:00000104 (unk. ctxt): jmp far 0008:0000 ; ea000008

00

<bochs:24> n

Next at t=16750870

(0) [0x00000000] 0008:00000000 (unk. ctxt): mov eax, 0x10 ; b8100000

00

在执行完jmp指令后,程序跳转到绝对地址0x00000000处,也就是保护模式下的逻辑地址0x0008:0x00000000,这实际上就是Head.s的代码了。Head.s的开始代码首先将ds,es,gs,fs各个段寄存器的值设置为0x10,这个段选择符写成二进制形式:0000000000010000,它表示特权级0,选择全局描述符表的第2项,即前面提到的内核数据选择符:0x000007ff 0x00c09200,这个段基址为0x0000,段限长0x7fff。

Head.s一开始重新设置IDT表和GDT表,建立方法和在Setup.s中相差不大,下面来看看重新建立的GDT表。命令行如下:

<bochs:25> u /10

00000000: ( ): mov eax, 0x10 ; b810000000

00000005: ( ): mov ds, ax ; 8ed8

00000007: ( ): mov es, ax ; 8ec0

00000009: ( ): mov fs, ax ; 8ee0

0000000b: ( ): mov gs, ax ; 8ee8

0000000d: ( ): lss ds:0x192a4 ; 0fb225a4920100

00000014: ( ): call .+0x6f ; e856000000

00000019: ( ): call .+0x9f ; e881000000

0000001e: ( ): mov eax, 0x10 ; b810000000

00000023: ( ): mov ds, ax ; 8ed8

<bochs:26> b 0x1e

<bochs:27> c

(0) Breakpoint 6, 0x1e in ?? ()

Next at t=16752168

(0) [0x0000001e] 0008:0000001e (unk. ctxt): mov eax, 0x10 ; b8100000

00

<bochs:28> dump_cpu

……

gdtr:base=0x5cb8, limit=0x7ff

idtr:base=0x54b8, limit=0x7ff

……

<bochs:29> x /10 0x5cb8

[bochs]:

0x00005cb8 <bogus+ 0>: 0x00000000 0x00000000 0x00000fff

0x00c09a00

0x00005cc8 <bogus+ 16>: 0x00000fff 0x00c09200 0x00000000

0x00000000

0x00005cd8 <bogus+ 32>: 0x00000000 0x00000000

这个GDT表的值是由Head.s代码末尾234行开始的常数数据定义的:

_gdt: .quad 0x0000000000000000 /* NULL descriptor */

.quad 0x00c09a0000000fff /* 16Mb */

.quad 0x00c0920000000fff /* 16Mb */

.quad 0x0000000000000000 /* TEMPORARY - don't use */

.fill 252,8,0 /* space for LDT's and TSS's etc */

由此可以看到,这个GDT表的第0项未定义,第1项是内核代码段,第2项是内核数据段,第3项未定义,剩余的252项用于放置创建任务的局部描述符和任务状态段描述符。Linux内核使用的描述符表在内存中的示意图如下:

[url=http://blog.csdn.net/images/blog_csdn_net/lijingze2003/28214/r_Linux内核使用描述符表的示意图.JPG]

图2 :Linux内核使用描述符表示意图(摘自《Linux内核完全注释》)

后记

在建立完GDT表后,Head.s代码将继续进行分页,并开启分页机制,Linux将以分段机制将逻辑地址转换成线性地址,以分页机制将线性地址转换成物理地址。在其后的多任务作业时,GDT表末尾252项将得到填充使用。

Linus有句名言:“Read the F**king code”,事实上以调试的方法来辅助阅读是相当有益的。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有