--------------------------------------------------------------------------------------
操作系统的启动
启动的英文是boot,但又被称为引导,boot原本意思是靴子的意思,有一句话是这样说的
"pull oneself up by one's bootstraps"
让计算机运行程序是一件非常矛盾的事情,因为计算机只有运行程序才能启动计算机,但是不启动计算机怎么能运行程序?,所以这是一个很矛盾的问题,所以工程师费劲心思制造出了一块固有的程序,叫boot程序,也就是引导程序,boot程序被放进内存中,用以做一些初始化的工作。
首先有一块固有的ROM芯片,里面是写好的一组程序这个程序叫做bios,当计算机加点启动时,首先执行的就是bios里的程序
BIOS里固化的程序有
自诊断程序(会读取CMOS RAM(可读写)的参数即BIOS设定系统参数的存放场所),也称作POST操作(Power On Self-Test)
CMOS设置程序(你可以通过敲击相应的热键进入BIOS设置界面,在那里你可以更改BIOS的设置,最终写入的地方就是CMOS RAM,所以BIOS设置也就是对CMOS RAM设置),
系统自装载程序(也就是根据磁盘活动分区的主引导记录找到引导程序,用以将操作系统内核装载进内存),
I/O驱动程序(在这区间,需要必要的通信),
中断服务(用于驱动硬件做一些事情)
boot启动分四个阶段:
1.开机会最开始访问0xFFFF0地址,然后跳转到BIOS的初始化程序,把BIOS ROM中的初始化程序复制到内存中执行。BIOS会进行自检,其具体操作是读取CMOS RAM里的一些信息,若出现错误,机器会报不同声响的蜂鸣声。这个操作也称为POST(POST很短,只是检测非常重要的设备->640k的常规内存),Power On Self-Test,因为作为最底层的硬件设备(例如内存,显卡等最基础的硬件设备),初始化需要保证他们必须没有错误,所以它的优先级是最高的。细节:先对内存进行一个检索,看它是否存在,或者内存有问题,当然,此时只会检索640kb的内存,因为此时在实模式下。接下来BIOIS会查找显卡的BIOS,存放显卡的ROM芯片的起始地址通常在0xC0000,系统BIOS如果找到它,会调用它的BIOS的初始化代码,多数显卡会显示一些基础信息,都是一闪而过的。系统BIOS会接着查找其他设备的BIOS程序,找到之后同样调用它们的BIOS初始化代码。查找完其它设备的BIOS后,系统BIOS才会显示它自己的画面,接着会测试所有的RAM。内存测试通过后,接着系统BIOS会初始化一些标准硬件,包括硬盘,CD-ROM,串口,并口等设备,标准设备检测完毕后。系统BIOS支持即插即用Pnp的代码会开始检测和配置这些设备,没找到一个就会显示该设备的基础信息,同时为该设备分配中断(注意这里的中断时指最底层的硬件设备的中断),DMA通道(Direct Memory Access 直接内存访问,指不经过cpu,直接通过通道传输数据),接下来就是BIOS与CMOS的信息交互。如果有问题,BIOS会直接控制喇叭发声来报告错误,喇叭声音的长短和大小代表了错误的类型。
常规内存在内存分配表中占用最前面的位置,从0KB到640KB(地址00000H~9FFFFH),共占640KB的容量。
640k内存图如下,注意,启动的时候用到的全是真实内存
2.硬件没有问题,那么就进入下一阶段。BIOS 开始搜寻可引导的存储设备(即根据用户指定的引导顺序来寻找引导程序),这个称为Boot Sequence,一般就是从硬盘引导内核,如果磁盘内的操作系统数据受到破坏,那么这个时候就需要从usb或其它可移动设备进行引导,需要更改引导顺序。一般来说这时候会读取磁盘中的MBR信息,也就是主引导记录,它前面的440字节是引导程序,会将它读入内存,如果引导程序没有问题会把控制权交给其活动分区的PBR(Partition Boot Record)。
其主要实现代码如下,注释已经完整给出,最好动态调试下,了解流程,具体动态调试。用IDA就可以很方便的调试,选择Remote GDB Debugger 然后填上相应的代码即可,具体操作在吾爱上有教程。注意,用IDA调试的时候,在0x7C00处下断点要注意不要勾选硬件断点,因为此时我们是处于实模式下,而硬件断点需要DR寄存器,我之前就不小心下了个硬件断点,然后就彻底崩掉了…
MEMORY:7C00 xor ax, ax ;可以看出引导程序是被复制到0x7c00处执行的
MEMORY:7C02 mov ss, ax
MEMORY:7C04 mov sp, 7C00h ; 0:7c00
MEMORY:7C07 sti
MEMORY:7C08 push ax
MEMORY:7C09 pop es
MEMORY:7C0A push ax
MEMORY:7C0B pop ds ; es ds = 0
MEMORY:7C0C cld
MEMORY:7C0D mov si, 7C1Bh
MEMORY:7C10 mov di, 61Bh
MEMORY:7C13 push ax
MEMORY:7C14 push di ; di = 61bh,jmp to 61b
MEMORY:7C15 mov cx, 1E5h
MEMORY:7C18 rep movsb
MEMORY:7C1A retf
MEMORY:7C1B ; ---------------------------------------------------------------------------
MEMORY:7C1B mov bp, 7BEh
MEMORY:7C1E mov cl, 4 ;cx记录着分区表的项数
MEMORY:7C20
MEMORY:7C20 loc_7C20:
MEMORY:7C20 cmp [bp+0], ch ;[bp+0]的一个字节跟ch进行比较,ch为0 0x80换算成1字节为-128
MEMORY:7C23 jl short loc_7C2E ;这里判断是否为活动分区,是的话跳转
MEMORY:7C25 jnz short loc_7C3A ;分区若出现了其他值,跳到loc_7c3a执行错误处理
MEMORY:7C27 add bp, 10h
MEMORY:7C2A loop loc_7C20
MEMORY:7C2C int 18h ; TRANSFER TO ROM BASIC 错误处理
MEMORY:7C2C
MEMORY:7C2E
MEMORY:7C2E loc_7C2E: ;这个是判断有无其他分区的
MEMORY:7C2E mov si, bp ;这时候说明找到了第一个活动分区
MEMORY:7C30
MEMORY:7C30 loc_7C30:
MEMORY:7C30 add si, 10h ;si = bp + 0x10 si是一个循环器 每次加0x10
MEMORY:7C33 dec cx ; cx--
MEMORY:7C34 jz short loc_7C4F
MEMORY:7C36 cmp [si], ch ;ch恒为0 若其它均为0,直到cx == 0 会跳到正常步骤里,否则继续输出那串字符串
MEMORY:7C38 jz short loc_7C30
MEMORY:7C3A
MEMORY:7C3A loc_7C3A: ; CODE XREF: MEMORY:7C25↑j
MEMORY:7C3A mov al, byte_7B5 ;byte_7B5是 0x2c
MEMORY:7C3D
MEMORY:7C3D loc_7C3D:
MEMORY:7C3D
MEMORY:7C3D mov ah, 7 ;ah = 7
MEMORY:7C3F mov si, ax
MEMORY:7C41
MEMORY:7C41 loc_7C41:
MEMORY:7C41 lodsb ;ds:si -> es:di si是0x072c 即invalid partition table di为0x800一片空白的区
MEMORY:7C42 ;字节会装入ax里
MEMORY:7C42 loc_7C42:
MEMORY:7C42 cmp al, 0 ;会执行死循环
MEMORY:7C44 jz short loc_7C42 ;0x072c这里是一个ascii字符串
MEMORY:7C46 mov bx, 7
MEMORY:7C49 mov ah, 0Eh ;ah = 0xE bl = 0x7
MEMORY:7C4B int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
MEMORY:7C4B ; AL = character, BH = display page (alpha modes)
MEMORY:7C4B ; BL = foreground color (graphics modes)
MEMORY:7C4D jmp short loc_7C41 ;每次显示一个字符
MEMORY:7C4F ; ---------------------------------------------------------------------------
MEMORY:7C4F
MEMORY:7C4F loc_7C4F: ;假如分区没问题
MEMORY:7C4F mov [bp+10h], cl
MEMORY:7C52 call near ptr unk_7C9B ;检测是否有扩展样式
MEMORY:7C55 jnb short loc_7C81 ;
MEMORY:7C57
MEMORY:7C57 loc_7C57:
MEMORY:7C57 inc byte ptr [bp+10h] ;LBA高两字节
MEMORY:7C5A cmp byte ptr [bp+4], 0Bh ;offset为4 是文件类型 若是FAT32文件类型,则跳到loc_7C6B
MEMORY:7C5E jz short loc_7C6B
MEMORY:7C60 cmp byte ptr [bp+4], 0Ch ;同样也是FAT32分区
MEMORY:7C64 jz short loc_7C6B
MEMORY:7C66 mov al, byte_7B6 ;Error loading operating system
MEMORY:7C69 jnz short loc_7C3D ;如果不是FAT32,那么就说明文件类型有问题
MEMORY:7C6B
MEMORY:7C6B loc_7C6B:
MEMORY:7C6B ; MEMORY:7C64↑j
MEMORY:7C6B add byte ptr [bp+2], 6 ;扇区号加6,可能这是FAT32的特点?
MEMORY:7C6F add word ptr [bp+8], 6 ;LBA起始值加6
MEMORY:7C73 adc word ptr [bp+0Ah], 0
MEMORY:7C77 call near ptr unk_7C9B
MEMORY:7C7A jnb short loc_7C81
MEMORY:7C7C mov al, byte_7B6 ;Error Loading operating system
MEMORY:7C7F jmp short loc_7C3D
MEMORY:7C81 ; ---------------------------------------------------------------------------
MEMORY:7C81
MEMORY:7C81 loc_7C81: ;检测最后两字节
MEMORY:7C81 ; MEMORY:7C7A↑j
MEMORY:7C81 cmp word_7DFE, 0AA55h ;检测最后两字节
MEMORY:7C87 jz short loc_7C94 ;如果有结束标志,则跳到loc_7C94
MEMORY:7C89 cmp byte ptr [bp+10h], 0 ;如果LBA高两位为0,则跳到Loc_7c57检测文件类型
MEMORY:7C8D jz short loc_7C57
MEMORY:7C8F mov al, byte_7B7 ;Missing operating system
MEMORY:7C92 jmp short loc_7C3D
MEMORY:7C94 ; ---------------------------------------------------------------------------
MEMORY:7C94
MEMORY:7C94 loc_7C94: ;这里已经将PBR装入0:7c00处了,现在要跳到那
MEMORY:7C94 mov di, sp ;di = 0x7c00
MEMORY:7C96 push ds
MEMORY:7C97 push di ;retAddr = 0x7c00
MEMORY:7C98 mov si, bp
MEMORY:7C9A retf
MEMORY:7C9A ; ---------------------------------------------------------------------------
MEMORY:7C9B
MEMORY:7C9B loc_7C9B: ;正常来说 检查分区表没问题会跳到这里
MEMORY:7C9B
MEMORY:7C9B mov di, 5 ;di = 5
MEMORY:7C9E mov dl, [bp+0] ;dl = 0x80
MEMORY:7CA1 mov ah, 8
MEMORY:7CA3 int 13h ; 读取驱动器参数,AH = 0x8 DL是驱动器这里为0x80 0x80是硬盘的第一个
MEMORY:7CA3 ; cf = 0表示无错
MEMORY:7CA5 jb short loc_7CCA ;若有错 则 cf = 1,会跳转到loc_7cca处,尝试将磁盘读入,进行磁盘复位
MEMORY:7CA7 mov al, cl
MEMORY:7CA9 and al, 3Fh ;取出扇区数
MEMORY:7CAB cbw ;转换为字指令,AL的内容扩展到AH,形成AX中的字
MEMORY:7CAC mov bl, dh ;bl存磁头数
MEMORY:7CAE mov bh, ah ;bh高位清0
MEMORY:7CB0 inc bx ;bx++
MEMORY:7CB1 mul bx ;扇区*磁头数
MEMORY:7CB3 mov dx, cx ;dx存放柱面和扇区相应地址
MEMORY:7CB5 xchg dl, dh ;交换一下高低位,方便取出柱面
MEMORY:7CB7 mov cl, 6
MEMORY:7CB9 shr dh, cl
MEMORY:7CBB inc dx ;dx里存放着柱面
MEMORY:7CBC mul dx ;获得LBA地址
MEMORY:7CBE cmp [bp+0Ah], dx ;[bp+0x0a]是LBA的高位地址,根据little-endian而言,offset为0xa是LBA的高位判断高位
MEMORY:7CC1 ja short loc_7CE6 ;若磁盘填入的大于实际的,则判断是否有扩展样式
MEMORY:7CC3 jb short loc_7CCA ;若磁盘的LBA地址小于实际的LBA,将扇区读入内存
MEMORY:7CC5 cmp [bp+8], ax ;[bp+8]是低位
MEMORY:7CC8 jnb short loc_7CE6 ;判断是否有扩展样式
MEMORY:7CCA
MEMORY:7CCA loc_7CCA:
MEMORY:7CCA
MEMORY:7CCA mov ax, 201h
MEMORY:7CCD mov bx, 7C00h
MEMORY:7CD0 mov cx, [bp+2]
MEMORY:7CD3 mov dx, [bp+0] ;al = 01,ah = 02 读取相应扇区到7c00处
MEMORY:7CD6 int 13h ; DISK - READ SECTORS INTO MEMORY
MEMORY:7CD6 ; AL = number of sectors to read, CH = track, CL = sector
MEMORY:7CD6 ; DH = head, DL = drive, ES:BX -> buffer to fill
MEMORY:7CD6 ; Return: CF set on error, AH = status, AL = number of sectors read
MEMORY:7CD8 jnb short locret_7D2B ;若不出现错误,则返回
MEMORY:7CDA dec di ;di 为尝试复位次数
MEMORY:7CDB jz short locret_7D2B
MEMORY:7CDD xor ah, ah
MEMORY:7CDF mov dl, [bp+0]
MEMORY:7CE2 int 13h ; DISK - RESET DISK SYSTEM 磁盘系统复位 继续读取扇区
MEMORY:7CE2 ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)
MEMORY:7CE4 jmp short loc_7CCA
MEMORY:7CE6 ; ---------------------------------------------------------------------------
MEMORY:7CE6
MEMORY:7CE6 loc_7CE6: ; 说明现在可能有扩展样式
MEMORY:7CE6 ; MEMORY:7CC8↑j
MEMORY:7CE6 mov dl, [bp+0] ;磁盘号
MEMORY:7CE9 pusha
MEMORY:7CEA mov bx, 55AAh
MEMORY:7CED mov ah, 41h ; 'A'
MEMORY:7CEF int 13h ; DISK - Check for INT 13h Extensions
MEMORY:7CEF ; BX = 55AAh, DL = drive number
MEMORY:7CEF ; Return: CF set if not supported
MEMORY:7CEF ; AH = extensions version
MEMORY:7CEF ; BX = AA55h
MEMORY:7CEF ; CX = Interface support bit map
MEMORY:7CF1 jb short loc_7D29 ;表明现在样式存在问题。
MEMORY:7CF3 cmp bx, 0AA55h
MEMORY:7CF7 jnz short loc_7D29
MEMORY:7CF9 test cl, 1
MEMORY:7CFC jz short loc_7D29
MEMORY:7CFE popa
MEMORY:7CFF
MEMORY:7CFF loc_7CFF: ; 表明支持扩展样式,则扩展读
MEMORY:7CFF pusha
MEMORY:7D00 push 0
MEMORY:7D02 push 0
MEMORY:7D04 push word ptr [bp+0Ah] ;push进去了 LBA地址
MEMORY:7D07 push word ptr [bp+8]
MEMORY:7D0A push 0
MEMORY:7D0C push 7C00h
MEMORY:7D0F push 1
MEMORY:7D11 push 10h
MEMORY:7D13 mov ah, 42h ; 'B'
MEMORY:7D15 mov si, sp
MEMORY:7D17 int 13h ; DISK - IBM/MS Extension - EXTENDED READ (DL - drive, DS:SI - disk address packet)
MEMORY:7D19 popa
MEMORY:7D1A popa
MEMORY:7D1B jnb short locret_7D2B
MEMORY:7D1D dec di
MEMORY:7D1E jz short locret_7D2B
MEMORY:7D20 xor ah, ah
MEMORY:7D22 mov dl, [bp+0]
MEMORY:7D25 int 13h ; DISK - RESET DISK SYSTEM
MEMORY:7D25 ; DL = drive (if bit 7 is set both hard disks and floppy disks reset)
MEMORY:7D27 jmp short loc_7CFF
MEMORY:7D29 ; ---------------------------------------------------------------------------
MEMORY:7D29
MEMORY:7D29 loc_7D29:
MEMORY:7D29
MEMORY:7D29 popa
MEMORY:7D2A stc ;令cf为置1,表示出现问题
MEMORY:7D2B
MEMORY:7D2B locret_7D2B:
MEMORY:7D2B
MEMORY:7D2B retn
3.引导程序执行完后会交给活动分区的引导程序,是否为活动分区在分区表里会有体现,然后读取这个活动分区的第一扇区,也叫做卷引导记录,卷引导记录的作用j就是指出操作系统在磁盘的位置,让计算机可以加载操作系统到内存。当然如果操作系统在扩展分区的话,情况会复杂点,扩展分区会类似于一个结构体的模型,里面含有一个逻辑分区的地址和一个指向下一个扩展分区的指针,操作系统会不断的寻找,直到找到活动分区,不过这种情况很少见,一般操作系统安装到扩展分区,会进行下面的一种操作。这种情况下读取完MBR的440字节后,不把控制权交给某个分区,而是启动一个 启动管理器(boot manager),由用户决定启动哪一个操作系统。
4.Windows 2000/XP/Windows 2003系统下,内核版本为NT 5.X,会将控制权交给NTLDR这个启动管理器而Windows Vista、Windows 7、windows 8/8.1和windows 10内核版本为NT 6.X 中使用的新的启动管理器,称为bootmgr启动管理器。当然,在读取活动分区的第一扇区的时候,PBR会搜寻到启动管理器,对于bootmgr而言,它会会初始化一个最小的文件系统,为了方便读取c盘目录下的文件。然后读取\boot\bcd(bios config data启动配置参数)文件,类似于xp系统的boot.ini文件,然后假如存在多个操作系统的话,且等待时间不为0,那么会在显示器界面上显示操作系统的选项,供用户选择要启动的操作系统。bootmgr会去启动盘寻找C:\Windows\system32\winload.exe,之后就把控制权交给操作系统了。
Windowsn Vista等等启动过程类似于。注意的是bootmgr必须由版本最高的启动管理器负责管理所有的操作系统,启动管理器只具备向下兼容性、不具备向上兼容性。因此,启动管理器的版本必须严格按照高低先后的顺序正确排列,而不能像 Windows 的正式名称一样胡乱设置,否则便有可能无法正确配置多重操作系统共存。
默认情况下bcd配置文件在c:\Windows\System32目录中,程序名叫BCDEdit.exe
运行后会出现一些配置信息
指出了bootmgr的位置,和winload的位置
BIOS->MBR->DPT->PBR->bootmgr->\boot\bcd->选择相应操作系统->Winload.exe->内核加载->操作系统启动
对于xp系统而言,它的启动管理器是NTLDR,即NT LoaDeR。NTLDR被安装后,处理器会从实模式转为32 flat memory model(32位平坦模式,即段处理器无用,其偏移可以寻址到任意地址空间),接着NTLDR会初始化一个基础的文件系统(因为要从磁盘分区寻找某些中重要文件,如后文中提到的Ntoskrnl.exe文件,还有相当重要的注册表,注册表目录在C:\Windows\System32\Config,分多个文件存放),用以读取带有启动参数的文件boot.ini,然后假如有多个操作系统供选择,则等待操作系统的选择,接着就会将操作系统装载入内存(boot.ini会有参数给出操作系统在哪个分区,并且选择菜单由boot.ini给出,但是boot.ini并不是必需的,但却非常重要,如果丢失,那么默认从c盘开始启动操作系统了),然后,假若windows NT/windows 2000/windows XP/windows server 2003被选择(若不是NT操作系统,那么会将控制权交给bootsect.dos,例如非常古老的Windows9x系列),那么NTLDR会启动Ntdetect.com,来检测相应的硬件,存在于c盘目录下,主要是收集一些硬件信息,将硬件列表传入NTLDR,然后将硬件信息填入HKLM\HARDWARE。然后NTLDR会按一下顺序装载Ntoskrnl.exe(NT OS Kernel,仅仅装载,还没有初始化内核),Hal.dll(用于解决硬件的复杂性,硬件抽象层,同样也是为了硬件的可移植性质考虑的),kdcom.dll(内核调试器硬件扩展DLL),bootvid.dll(用于windows徽标和侧滚动栏和系统信息的集合(可以通过msinfo32.exe查看系统信息,也即是加载了HKEY_LOCAL_MACHINE/System注册表键)。Ntoskrnl(非常重要的内核初始化程序)存储了启动 logo 画面。也就是非常令人熟悉的那个过场动画。在进程中以System进程名来表示。如果在Windows注册表中定义了多个硬件配置,则此时将提示用户选择一个)。默认情况下,ControlSet001是系统真实的配置信息。可是为了避免序号混乱,windows启动时会从ControlSet001复制一份副本,作为操作系统当前的配置信息。也就是CurrentControlSet。我们对于计算机配置进行的改动都会写入到CurrentControlSet中,重新启动的过程中,CurrentControlSet的内容会覆盖掉ControlSet001中的内容,以保证两个键的内容相同。这样,CurrentControlSet002中的内容就是“最近一次成功配置的信息”。并且引人注意的是System/select子项,有几个整数键,Current表示选中的ControlSet控件组,Default下次采用的控件组,Failed表示失败后保存的控件组(此控件组在用户第一次调用“近期一次的正确配置”选项之前并不实际存在,因为我从来没用过,所以这里就是0.你失败的话会为你保留),LastKnownGood,最近一次正确的配置。Ntldr会根据载入的Select键的Default内容判断接下来需要载入哪个ControlSet注册表键(这些键会决定随后系统将载入哪些设备驱动或者启动哪些服务)。服务见下表。对于驱动而言,也是有必要的,主要的是在注册表中找start键的值为0和1的设备驱动 ,start键的值为0.1.2.3.4,数值越小,启动越早。1,比0稍晚一些,2是从登录界面出现的时候,3表示需要手动加载,4表示禁止加载。
HKLM\SYSTEM\CurrentControlSet\Services //启动的服务
HKLM\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder //启动服务的顺序
可以看下xp系统的boot.ini的具体的东西
选择操作系统的时间是30秒,操作系统所处于的位置是磁盘0的1扇区处的WINDOWS目录下
操作系统有两种选择,第二个是正常的我们的Windows操作系统,第一项是我为了用windbg调试而增加了/debug选项,并且用的串口是com2,波特率是115200bit。
注册表中的System项:
之后便交给操作系统来处理了,具体的细节我还不太懂,待完全知晓后再补坑。