<!-- 正文begin -->
2007.8.26
今天我们通过bochs来分析boot0.
本文参考了《FreeBSD 5.2.1 boot0(启动扇区代码分析)》,转载请注明,谢谢!
源码位于:sys/boot/i386/boot0/boot0.S
安装bochs: ./configure --enable-debugger --enable-disasm make; make install; rehash # cp .bochsrc /root/
修改.bochsrc如下两行: ata0-master: type=disk, mode=flat, path="/kerndebug/server.img", cylinders=0 vga: extension=none 其中/kerndebug/server.img为虚拟系统磁盘(可以用qemu的磁盘)
BIOS把引导扇区的512字节的内容读入到了0:0x7c00处,然后就跳转到0:0x7C00处去执行. 我们启动bochs,在0x7c00处设置断点。 # bochs -q -f /root/.bochsrc ======================================================================== Bochs x86 Emulator 2.3 Build from CVS snapshot on August 27, 2006 ======================================================================== 00000000000i[ ] reading configuration from /root/.bochsrc 00000000000e[ ] /root/.bochsrc: ataX-master/slave CHS set to 0/0/0 - autodetection enabled 00000000000i[ ] installing x module as the Bochs GUI 00000000000i[ ] using log file bochsout.txt Next at t=0 (0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b ; ea5be000f0 <bochs:1> b 0x7c00 <bochs:2> c (0) Breakpoint 1, 0x00007c00 in ?? () Next at t=981593 (0) [0x00007c00] 0000:7c00 (unk. ctxt): cld ; fc <bochs:3> disassemble size = 16 <bochs:4> u/10 00007c00: ( ): cld ; fc 00007c01: ( ): xor ax, ax ; 31c0 00007c03: ( ): mov es, ax ; 8ec0 00007c05: ( ): mov ds, ax ; 8ed8 00007c07: ( ): mov ss, ax ; 8ed0 00007c09: ( ): mov sp, 0x7c00 ; bc007c 00007c0c: ( ): mov si, sp ; 89e6 00007c0e: ( ): mov di, 0x0600 ; bf0006 00007c11: ( ): mov cx, 0x0100 ; b90001 00007c14: ( ): rep movsw word ptr es:[di], word ptr ds:[si] ; f3a5 这段代码把代码从0:7c00处的搬移到0:0x600-0x7ff处。 <bochs:6> b 0x7c16 <bochs:7> c (0) Breakpoint 2, 0x00007c16 in ?? () Next at t=982113 (0) [0x00007c16] 0000:7c16 (unk. ctxt): mov bp, di ; 89fd <bochs:8> x/4x 0x7c00 [bochs]: 0x00007c00 <bogus+ 0>: 0x8ec031fc 0x8ed88ec0 0x7c00bcd0 0x00bfe689 <bochs:9> x/4x 0x600 [bochs]: 0x00000600 <bogus+ 0>: 0x8ec031fc 0x8ed88ec0 0x7c00bcd0 0x00bfe689 为什么要搬移呢?因为后面的boot2也会被加载到ox7c00。看来官僚无处不在。
接下来,把0x800开始的16个字节清0,第二个字节置为1,表示我们已经读取了一个分区. 后面会用到这块内存。然后跳转到搬运后的代码0x622处执行: 00007c16: ( ): mov bp, di ; 89fd 00007c18: ( ): mov cl, 0x08 ; b108 00007c1a: ( ): rep stosw word ptr es:[di], ax ; f3ab 00007c1c: ( ): inc byte ptr ds:[di+0xfff2] ; fe45f2 00007c1f: ( ): jmp .+0x8a00 ; e9008a
<bochs:15> info r eax: 0x0 0 ecx: 0x120000 1179648 edx: 0x80 128 ebx: 0x80 128 esp: 0x7c00 31744 ebp: 0x800 2048 esi: 0x7e00 32256 edi: 0x810 2064 eip: 0x622 eflags 0x202 cs: 0x0 ss: 0x0 ds: 0x0 es: 0x0 fs: 0x0 gs: 0x0 注意,BIOS把磁盘的引导扇区读入到内存之后,其dl的内容表示启动设备,这里是0x80,代表的是第一个物理磁盘的第一个分区。 这里bp的值为0x800,也就是搬移后代码的尾端,我们后面会经常用它加上一个偏移页取值: _NXTDRV,-0x48 00 # Next drive _OPT,-0x47 00 # Default option _SETDRV,-0x46 80 # Drive to force _FLAGS,-0x45 0F # Flags _TICKS,-0x44 B6 # Timeout ticks
以下内容转至: http://www.lslnet.com/linux/docs/linux-3232.htm
硬盘主引导扇区 = 硬盘主引导记录(MBR)+ 硬盘分区表(DPT) -------------------------------------------------------------- 物理位置:0面0道1扇区(clindyer 0, side 0, sector 1) 大小: 512字节 其中:MBR 446字节(0000--01BD),DPT 64字节(01BE--01FD),结束标志2字节(55 AA) 功能:MBR通过检查DPT分区信息引导系统跳转至DBR;
详解: 000H--08AH MBR启动程序(寻找开机分区) 08BH--0D9H MBR启动字符串 0DAH--1BCH 保留("0") 1BEH--1FDH 硬盘分区表 1FEH--1FFH 结束标志(55AA)
活动分区主引导扇区(DBR) -------------------------- 物理位置:1面0道1扇区(clindyer 0, side 1, sector 1) 大小: FAT16 1扇区 512字节 FAT32 3扇区 1536字节 功能:包含机器CMOS等信息(0000--0059), 核对该信息并引导指定的系统文件, 如NTLDR等;
详解: 000H--002H 3 BYTE的跳转指令(去启动程序, 跳到03EH) 003H--03DH BIOS参数区 03EH--19DH DOS启动程序 19EH--1E5H 开机字符串 1E6H--1FDH 文件名(IO.SYS, MSDOS.SYS) 1FEH--1FFH 结束标记(55AA)
硬盘分区表(DPT) --------------------- 偏移地址 字节数 含义分析 01BE 1 分区类型:00表示非活动分区:80表示活动分区;其他为无效分区。 01BF~01C1 3 *分区的起始地址(面/扇区/磁道),通常第一分区的起始地址开始 于1面0道1扇区,因此这三个字节应为010100 01C2 1 #分区的操作系统的类型。 01C3~01C5 3 *该分区的结束地址(面/扇/道) 01C6~01C9 4 该分区起始逻辑扇区 01CA~01CD 4 该分区占用的总扇区数
注释: * 注意分区的起始地址(面/扇区/磁道)和结束地址(面/扇/道)中字节分配:
00000000 01000001 00010101 ~~~~~~~~ ==^^^^^^ ========
~ 面(磁头) 8 位 ^ 扇区 6 位 = 磁道 10 位
# 分区的操作系统类型(文件格式标志码) 4---DOS FAT16<32M 5---EXTEND 6---DOS FAT16>32M 7---NTFS(OS/2) 83---LINUX>64M
DPT 总共64字节(01BE--01FD), 如上所示每个分区占16个字节, 所以可以表示四个分区, 这也 就是为什么一个磁盘的主分区和扩展分区之和总共只能有四个的原因.
以上是引导扇区的一些基础知识,我们需要关注的是DPT: <bochs:43> x/16x 0x7dbe [bochs]: 0x00007dbe <bogus+ 0>: 0x00010180 0xffff0fa5 0x0000003f 0x001ffdc1 0x00007dce <bogus+ 16>: 0x00000000 0x00000000 0x00000000 0x00000000 0x00007dde <bogus+ 32>: 0x00000000 0x00000000 0x00000000 0x00000000 0x00007dee <bogus+ 48>: 0x00000000 0x00000000 0x00000000 0x00000000 这里,我们可以看到,只有一个分区,而且是活动分区,操作系统的类型是a5,其它的我们暂时不用关注。 如果我们用hexdump查看系统的/boot/boot0,会发现在0x1be偏移处的64个字节都是0,难道是没有分区表吗? 不是的,因为真正用到的boot0是写在磁盘内的,分区表信息是fdisk写进去的。可以用dd来查看: dd if=/dev/md0s1 of=log bs=512 count=1
好了,我们继续来看: testb $0x20,_FLAGS(%bp) # Set drive number? jnz main.1 # Yes testb %dl,%dl # Drive number valid? js main.2 # Possibly (0x80 set) 查看flag,其值为0x0F,不会跳转;bl的值为0x80,我们来看它的“笨猪跳”:
main.2: movb %dl,_FAKE(%bp) # Save drive number callw putn # To new line pushw %dx # Save drive number 保存代表启动分区的0x80到0x800,在屏幕上打印一个换行
movw $(partbl+0x4),%bx # Partition table (+4) xorw %dx,%dx # Item number main.3: movb %ch,-0x4(%bx) # Zero active flag (ch == 0) btw %dx,_FLAGS(%bp) # Entry enabled? jnc main.5 # No 现在要开始检测分区表了,partbl的基址是0x7BE,为什么要用它的值加4赋给bx呢? 这时为了在4次循环后能产生一个进位信号,从而退出循环,后面可以看到(0x7BE+4=0x7c2,每次循环加0x10)
main.3: movb %ch,-0x4(%bx) # Zero active flag (ch == 0) btw %dx,_FLAGS(%bp) # Entry enabled? jnc main.5 # No ch的值为0,我们把标志分区活动的位清0,因为后面我们还会重写该值。
movb (%bx),%al # Load type movw $tables,%di # Lookup tables movb $TBL0SZ,%cl # Number of entries repne # Exclude scasb # partition? je main.5 # Yes 把操作系统的类型加载到al,值为0xa5,然后查表,来判断类型。tables(0x0787)定义如下: tables: .byte 0x0, 0x5, 0xf .byte 0x1, 0x6, 0x7, 0xb, 0xc, 0xe, 0x83 .byte 0x9f, 0xa5, 0xa6, 0xa9 .byte os_dos-. # DOS .byte os_dos-. # DOS .byte os_dos-. # Windows .byte os_dos-. # Windows .byte os_dos-. # Windows .byte os_dos-. # Windows .byte os_linux-. # Linux .byte os_bsd-. # BSD/OS .byte os_freebsd-. # FreeBSD .byte os_bsd-. # OpenBSD .byte os_bsd-. # NetBSD .byte os_misc-. # Unknown 其中前3个是无效的,这里TBL0SZ为3,就是先看该类型是否无效,如果是就检测下一个分区。 我们是0xa5,不用怕!
movb $TBL1SZ+1,%cl # Number of entries repne # Locate scasb # type addw $TBL1SZ-1, %di # Adjust movb (%di),%cl # Partition addw %cx,%di # description callw putx # Display it 接下来检测有效的分区类型,scasb结束时di指向的是0xa6,TBL1SZ为11,加上9刚好指向os_freebsd。 os_freebsd: .ascii "Free" os_bsd: .ascii "BS"; .byte 'D'|0x80 <bochs:63> x/8c 0x7a9 [bochs]: 0x000007a9 <bogus+ 0>: F r e e B S \xC4 f 接着调用putx打印出如下一行: F1 FreeBSD 这里有个技巧,最后一个字节保存的是'D'和0x80或的结果,这是为了打印字符串的时候标志结束。打印时会 把这个0x80给去掉的。
main.5: incw %dx # Next item addb $0x10,%bl # Next entry jnc main.3 # Till done dx用于标志Fx中的'x',需要加1;bl指向的是分区表,加上16个字节,如果没有进位,则跳转到main.3,继续 分析其它的分区表。这里我们可以知道,该循环会执行4次。由于后3个type都为0x0,所以会在无效类型处直接 进入下一个循环。
popw %ax # Drive number subb $0x80-0x1,%al # Does next cmpb NHRDRV,%al # drive exist? (from BIOS?) jb main.6 # Yes 这里是为了检测磁盘是否有效。恢复先前保存的磁盘号(0x80),减去0x79得到1,NHRDRV(0x475)处是bios保存的 磁盘数目[FIXME],这里为1 <bochs:8> x/b 0x475 [bochs]: 0x00000475 <bogus+ 0>: 0x01
decw %ax # Already drive 0? jz main.7 # Yes 判断是否为第0个磁盘,这里我们会跳转。
main.7: movw $prompt,%si # Display callw putstr # prompt movb _OPT(%bp),%dl # Display decw %si # default callw putkey # key 这里打印Default: F1
main.10: movb $ASCII_BEL,%al # Signal callw putchr # beep! xorb %ah,%ah # BIOS: Get int $0x1a # system time movw %dx,%di # Ticks when addw _TICKS(%bp),%di # timeout beep一下,引起你的注意,然后取得当前系统时间,加上_TICKS(0xB6)作为循环等待结束的时间。 然后就是检测用户是否输入并判断是否有效,超时则启动default,不细说了。
main.13: pushw %bx # Save testb $0x40,_FLAGS(%bp) # No updates? jnz main.14 # Yes movw $start,%bx # Data to write movb $0x3,%ah # Write sector callw intx13 # to disk main.14: popw %si # Restore popf # Restore 这里把信息重新写入MBR,这样可以保存您选择的结果。
main.15: movw $LOAD,%bx # Address for read movb $0x2,%ah # Read sector callw intx13 # from disk jc main.10 # If error cmpw $MAGIC,0x1fe(%bx) # Bootable? jne main.10 # No movw $crlf,%si # Leave some callw puts # space jmp *%bx # Invoke bootstrap 加载所选分区的bootsector(boot2)到0x7c00,检测最后一个world是否为0xaa55。 打印一个空格,然后跳转到0x7c00执行。
[ 本帖最后由 qiuhanty 于 2007-10-14 18:17 编辑 ]
|
|