重头戏FreeLdr.sys
简单介绍
代码位置: D:/ReactOS/ReactOS_src/boot/freeldr/freeldr/
先说下freeldr.sys,这个东东包含了内存管理、文件系统、缓存、UI、磁盘管理等要要的操作系统的功能。再加上点进程管理就可以成一个小操作系统了。那好我们就分析下freeldr以便我们能更好的理解操作系统。
来到代码目录下,好多代码,代码的入口函数在那个文件呀,怎么找呀。我啰嗦下,但不知道对不对,我们代开D:/ReactOS/ReactOS_src/boot/freeldr/freeldr.rbuild文件
像这样:
<?xml version="1.0"?>
<!DOCTYPE group SYSTEM "../../tools/rbuild/project.dtd">
<group xmlns:xi="http://www.w3.org/2001/XInclude">
<directory name="bootsect">
<xi:include href="bootsect/bootsect.rbuild" />
</directory>
<directory name="freeldr">
<xi:include href="freeldr/freeldr_startup.rbuild" />
<xi:include href="freeldr/freeldr_base64k.rbuild" />
<xi:include href="freeldr/freeldr_base.rbuild" />
<xi:include href="freeldr/freeldr_arch.rbuild" />
<xi:include href="freeldr/freeldr_main.rbuild" />
<xi:include href="freeldr/setupldr_main.rbuild" />
<xi:include href="freeldr/freeldr.rbuild" />
<xi:include href="freeldr/setupldr.rbuild" />
</directory>
<directory name="fdebug">
<xi:include href="fdebug/fdebug.rbuild" />
</directory>
</group>
看第一个是什么呀?对呀是: freeldr/freeldr_startup.rbuild,那就去子目录找下,找到了,内容如下:
<?xml version="1.0"?>
<!DOCTYPE module SYSTEM "../../../tools/rbuild/project.dtd">
<module name="freeldr_startup" type="objectlibrary">
<include base="freeldr_startup">include</include>
<include base="ntoskrnl">include</include>
<compilerflag>-fno-inline</compilerflag>
<compilerflag>-fno-zero-initialized-in-bss</compilerflag>
<directory name="arch">
<if property="ARCH" value="i386">
<directory name="i386">
<file first="true">fathelp.asm</file>
<file>arch.S</file>
</directory>
</if>
<if property="ARCH" value="amd64">
<directory name="amd64">
<file first="true">fathelp.S</file>
<file>arch.S</file>
</directory>
</if>
</directory>
</module>
前面已经假定我们的机器是i386的了,看到带颜色的那几句吗?尤其是蓝色那句,好像是第一个程序源码的第一个文件到底是不是呢?我用IDA看了下freeldr的反汇编确实是.就是那个fathelp.asm,那好我们来看下fathelp.asm..
Fathelp.asm
代码路径: D:/ReactOS/ReactOS_src/boot/freeldr/freeldr/arch/i386/fathelp.asm
看到代码
; This code will be stored in the first 512 bytes
; of freeldr.sys. The first 3 bytes will be a jmp
; instruction to skip past the FAT helper code
; that is stored in the rest of the 512 bytes.
;
; This code is loaded at 0000:8000 so we have to
; encode a jmp instruction to jump to 0000:8200
global _mainCRTStartup ; For Mingw32 builds where the linker looks for this symbol
_mainCRTStartup:
global start
start:
db 0xe9
db 0xfd
db 0x01
….以下省略
看到这我就不分析下面的啦,正如上面的注释所说,这个代码储存在freeldr.sys的前512字节中,前3个字节会跳过这段代码,剩下的部分放在接下来的剩余空间内,这里为什么跳1fa呢?对呀,因为e9 01fd自身就3字节,加起来这好是0x200h,即512字节.
arch.s
那好我们看下一个文件,下一个文件是什么了?怎么看?不会这么快就忘了吧,在freeldr_startup.rbuild里呀!马上看下,哦是”arch.S”,咦?*.s是什么文件呢?不知道?我也不知道.那google呗.哈..知道了,原来也是汇编格式呀,只不过是AT&T汇编格式,和我们的nasm差不多,就是源/目的操作数的位置颠倒了下,这里我就不多说这个汇编的语法了,毕竟我们是分析代码嘛,大家可以去网上search下,大有文章在.
看代码:
.text
.code16
/*支持C语言的注释和宏定义还有include哈,不错不错找个时间好好看下这个汇编*/
#define ASM
#include <arch.h>
#include <multiboot.h>
/*入口点*/
EXTERN(RealEntryPoint)
/*关中断*/
cli
/*寄存器初始化*/
xorw %ax,%ax
movw %ax,%ds
movw %ax,%es
movw %ax,%fs
movw %ax,%gs
movw %ax,%ss
/* Setup a stack */
movw stack16,%sp
sti
/* 进入保护模式,这是个重点,走,一起去看这个函数的具体实现过程*/
call switch_to_prot
/*现在我们在保护模式下*/
.code32
/* 设置全局变量以后在freeldr.sys的其他地方会用着,先记着 */
xorl %eax,%eax
movl %eax,(_BootDrive)
movl %eax,(_BootPartition)
/* Store the boot drive */
movb %dl,(_BootDrive)
/* Store the boot partition */
movb %dh,(_BootPartition)
/* GO! */
/* 这里知道这个_BootMain是什么函数吗?是freeldr.sys的一个用C写的函数在
*D:/ReactOS/ReactOS_src/boot/freeldr/freeldr/freeldr.c里
*,这也说明我们的汇编要over了
* 还有呀大家知道函数调用时一个字都不能错,但是但freeldr.c里定义的是BootMain,这里怎么多了个”_”(下横杠)呀,
* 不会出错?原来这是使用C语言的__cdecl调用方式,MSDN中说” Underscore character (_) is prefixed to names”
* 就是说在编译的时候会用”_”来修改函数名称,所以这里用了_BootMain…切记,其他方式大家查MSDN吧
pushl %eax
call _BootMain
/* 再次转换到实模式*/
call switch_to_real
.code16
/* int 0x19是干嘛用的呀*/
/* INT19H是怎么处理启动的?
INT19会将MBR的512字节装载到内存0x7c00中,然后JUMP到0x7c00处,开始执行MBR的可执行程序(masterbooter),Masterbooter最起码需要做这些事情:
*/
int $0x19
/* We should never get here */
stop:
jmp stop
nop
nop
/*
* Switches the processor to protected mode
* 转换处理器到保护模式
* it destroys eax
* 这里注意下,会改变eax中的内容
*/
EXTERN(switch_to_prot)
.code16
cli /* None of these */
/* We don't know what values are currently */
/*我不并不知道下面这些寄存器现在的值是多少*/
/* in the segment registers. So we are */
/* 所以现在我们重新加载这些值**/
/* going to reload them with sane values. */
/* Of course CS has to already be valid. */
/* 当然CS已经是被设置好的了*/
/* We are currently in real-mode so we */
/* 我们现在还在实模式下所以我们呢需要实模式的段值*/
/* need real-mode segment values. */
xorw %ax,%ss
/* Get the return address off the stack */
/*保存要返回的地址*/
popw (code32ret)
/* Save 16-bit stack pointer */
movw %sp,stack16
/* 这里必须懂得保护模式和实模式*/
/* 加载GDT */
lgdt gdtptr
/* 加载 IDT */
lidt i386idtptr
/*修改cr0寄存器,来开启保护模式状态 */
mov %cr0,%eax
orl $CR0_PE_SET,%eax
mov %eax,%cr0
/* Clear prefetch queue & correct CS */
ljmp $PMODE_CS,$inpmode
/* 进入32位程序段*/
.code32
inpmode:
/* Setup segment selectors */
movw $PMODE_DS,%ss
movl stack32,%esp
/* Put the return address back onto the stack */
/*将返回地址压入堆栈*/
pushl (code32ret)
/* Now return in p-mode! */
/* 返回主函数,我们现在是在保护模式下了*/
ret
/*
* Switches the processor back to real mode
* it destroys eax
*/
EXTERN(switch_to_real)
.code32
/* We don't know what values are currently */
/* in the segment registers. So we are */
/* going to reload them with sane values. */
/* Of course CS has to already be valid. */
/* We are currently in protected-mode so we */
/* need protected-mode segment values. */
movw $PMODE_DS,%ss
/* Get the return address off the stack */
popl (code16ret)
/* Save 32-bit stack pointer */
movl %esp,stack32
/* jmp to 16-bit segment to set the limit correctly */
ljmp $RMODE_CS,$switch_to_real16
switch_to_real16:
.code16
/* Restore segment registers to correct limit */
movw $RMODE_DS,%ss
/* 关闭保护模式 */
mov %cr0,%eax
andl $CR0_PE_CLR,%cr0
/* Clear prefetch queue & correct CS */
ljmp $0,$inrmode
inrmode:
movw %cs,%ss
/* Clear out the high 16-bits of ESP */
/* This is needed because I have one */
/* machine that hangs when booted to dos if */
/* anything other than 0x0000 is in the high */
/* 16-bits of ESP. Even though real-mode */
/* code should only use SP and not ESP. */
xorl %esp,%esp
movw stack16,%sp
/* Put the return address back onto the stack */
pushw (code16ret)
/* Load IDTR with real mode value */
lidt rmode_idtptr
sti /* These are ok now */
/* Now return in r-mode! */
ret
/*
* Needed for enabling the a20 address line
*/
.code16
empty_8042:
.word 0x00eb,0x00eb // jmp $+2,jmp $+2
inb $0x64,%al
cmp $0xff,%al // legacy-free machine without keyboard
jz empty_8042_ret // controllers on Intel Macs read back 0xFF
testb $0x02,%al
jnz empty_8042
empty_8042_ret:
ret
/*下面的两个函数根本就没用到过*/
/*
* Enable the A20 address line (to allow access to over 1mb)
*/
EXTERN(_EnableA20)
.code32
pushal
call switch_to_real
.code16
call empty_8042
movb $0xD1,%al // command write
outb %al,$0x64
call empty_8042
mov $0xDF,%al // A20 on
out %al,$0x60
call empty_8042
call switch_to_prot
.code32
popal
ret
/*
* Disable the A20 address line
*/
EXTERN(_DisableA20)
.code32
pushal
call switch_to_real
.code16
call empty_8042
movb $0xD1,$0x64
call empty_8042
mov $0xDD,%al // A20 off
out %al,$0x60
call empty_8042
call switch_to_prot
.code32
popal
ret
…..略
.code32
/* 16-bit stack pointer */
stack16:
.word STACK16ADDR
/* 32-bit stack pointer */
stack32:
.long STACK32ADDR
/* 16-bit return address */
code16ret:
.long 0
/* 32-bit return address */
code32ret:
.long 0
.p2align 2 /* force 4-byte alignment */
gdt:
/* NULL Descriptor */
.word 0x0000
.word 0x0000
.word 0x0000
.word 0x0000
/* 32-bit flat CS */
.word 0xFFFF
.word 0x0000
.word 0x9A00
.word 0x00CF
/* 32-bit flat DS */
.word 0xFFFF
.word 0x0000
.word 0x9200
.word 0x00CF
/* 16-bit real mode CS */
.word 0xFFFF
.word 0x0000
.word 0x9E00
.word 0x0000
/* 16-bit real mode DS */
.word 0xFFFF
.word 0x0000
.word 0x9200
.word 0x0000
/* GDT table pointer */
gdtptr:
.word 0x27 /* Limit */
.long gdt /* Base Address */
/* Initial GDT table pointer for multiboot */
gdtptrhigh:
.word 0x27 /* Limit */
.long gdt + INITIAL_BASE - FREELDR_BASE /* Base Address */
/* Real-mode IDT pointer */
rmode_idtptr:
.word 0x3ff /* Limit */
.long 0 /* Base Address */
mb_info:
.fill MB_INFO_SIZE,1,0
cmdline:
.fill CMDLINE_SIZE,0
EXTERN(_BootDrive)
.long 0
EXTERN(_BootPartition)
.long 0
好,看完了,总结下…
1,数据初始化
2,加载GDT
3,加载LDT
4,修改CR0的PE位进入保护模式
5,跳转到保护模式下
6,保存全局变量
8,修改CR0的PE位进入实模式
9,加载实模式的LDT
10,重新启动.
虽然下面的EnableA20和DisableA20没用到吧,这里我也说下他们是干嘛的,其实代码作者也注释上了”to allow access to over 1mb”就是为访问大于1M的内存地址,参考《自己动手写操作系统》上的一段内容是:
什么是A20呢?这是一个历史问题,8086是采用SEG:OFFSET的模式分段的,所以他的最大内存是FFFF:FFFF即10FFEFh,但是8086只有20根地址总线,只能寻址到1Mb,如果试图访问超过1MB的地址时会怎样呢?实际上系统并不会发生异常.而是回卷回去,重新从地址0开始寻址,可是到了80286系列时,真的可以访问到1MB以上的内存地址了,如果遇到同样的情况系统不会再回卷寻址,这就造成了向上不兼容,为了保证百分之百兼容,IBM想出一个办法使用键盘来控制第20个地址位,这就是A20地址线,如果不开A20地址线总会是0.为了访问所有内存,我们需要打开A20地址线,默认是关闭的,但是怎么打开A20地址线呢?我们可以通过92h端口来达到目的..
知识点:
1,AT&T汇编
2,实模式和保护模式和其相互转换(具体参见《自己动手写操作系统》一书)
3,A20地址的打开方法.
5,Int 19h是干什么用的?(代码里的红字介绍了)