ReactOS 源码分析 (二) By:ProgrammeBoy
重头戏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是怎么处理启动的? 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,保存全局变量 7,调用_BootMain调用FreeLdr 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地址的打开方法. 4,几种C语言函数的调用方式. 5,Int 19h是干什么用的?(代码里的红字介绍了) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |