MBR引导程序源码理解
目录MBR引导程序源码理解序参考链接开机流程简述与MBR引导程序的关系进入 BIOS 确认开机启动磁盘获取引导磁盘第一扇区MBR数据反汇编MBR.bin源码解读`00000000EB63jmp short 0x65``00000065FAcli``0000006690nop``0000006790nop``00000068F6C280test
目录
- MBR引导程序源码理解
- 开机流程简述与MBR引导程序的关系
- 进入 BIOS 确认开机启动磁盘
- 获取引导磁盘第一扇区MBR数据
- 反汇编MBR.bin
- 源码解读
- `00000000 EB63 jmp short 0x65`
- `00000065 FA cli`
- `00000066 90 nop`
- `00000067 90 nop`
- `00000068 F6C280 test dl,0x80`
- `0000006B 7405 jz 0x72`
- `00000072 B280 mov dl,0x80`
- `00000074 EA797C0000 jmp word 0x0:0x7c79`
- `00000079 31C0 xor ax,ax`
- `0000007B 8ED8 mov ds,ax`
- `0000007D 8ED0 mov ss,ax`
- `0000007F BC0020 mov sp,0x2000`
- `00000082 FB sti`
- `00000083 A0647C mov al,[0x7c64]`
- `00000086 3CFF cmp al,0xff`
- `00000088 7402 jz 0x8c`
- `0000008C 52 push dx`
- `0000008D BE057C mov si,0x7c05`
- `00000090 B441 mov ah,0x41`
- `00000092 BBAA55 mov bx,0x55aa`
- `00000095 CD13 int 0x13`
- `00000097 5A pop dx`
- `00000098 52 push dx`
- `00000099 723D jc 0xd8`
- `0000009B 81FB55AA cmp bx,0xaa55`
- `0000009F 7537 jnz 0xd8`
- `000000A1 83E101 and cx,byte +0x1`
- `000000A4 7432 jz 0xd8`
- `000000A6 31C0 xor ax,ax`
- `000000A8 894404 mov [si+0x4],ax`
- `000000AB 40 inc ax`
- `000000AC 8844FF mov [si-0x1],al`
- `000000AF 894402 mov [si+0x2],ax`
- `000000B2 C7041000 mov word [si],0x10`
- `000000B6 668B1E5C7C mov ebx,[0x7c5c]`
- `000000BB 66895C08 mov [si+0x8],ebx`
- `000000BF 668B1E607C mov ebx,[0x7c60]`
- `000000C4 66895C0C mov [si+0xc],ebx`
- `000000C8 C744060070 mov word [si+0x6],0x7000`
- `000000CD B442 mov ah,0x42`
- `000000CF CD13 int 0x13`
- `000000D1 7205 jc 0xd8`
- `000000D3 BB0070 mov bx,0x7000`
- `000000D6 EB76 jmp short 0x14e`
- `0000014E 60 pushaw`
- `0000014F 1E push ds`
- `00000150 B90001 mov cx,0x100`
- `00000153 8EDB mov ds,bx`
- `00000155 31F6 xor si,si`
- `00000157 BF0080 mov di,0x8000`
- `0000015A 8EC6 mov es,si`
- `0000015C FC cld`
- `0000015D F3A5 rep movsw`
- `0000015F 1F pop ds`
- `00000160 61 popaw`
- `00000161 FF265A7C jmp word [0x7c5a]`
- 以上MBR引导程序执行过的指令,通过绿色块标识
MBR引导程序源码理解
序
本文的作用,不在于研究MBR引导程序,MBR引导程序有很多,而且去找GRUB2的源码,比反编译来的高效。
但是本文的作用是记录了一个学习过程,这个学习过程本身值得记录下来
参考链接
- Linux嵌入式开发第四阶段
- 如何反汇编原始的16位x86机器代码?
- 鸟哥的Linux私房菜
- 汇编语言debug命令与指令机器码
- 为什么主引导记录的内存地址是0x7C00?
- 为什么 BIOS 将 MBR 加载到 x86 中的 0x7C00 中?
- 一步步编写操作系统 08 bios跳转到神奇的内存地址0x7c00
- CPU指令集——AVX2
- 【编程语言/汇编】BIOS中断
- 大容量硬盘的读写操作:BIOS扩展13号中断
- 汇编语言常见的标志位:CF、PF、AF、ZF、SF、TF、IF、DF、OF
- grub2的源码
- ARM中的—汇编指令
- 扩展int13h调用详解(修正)
- INT 13
- 使用 BIOS 访问磁盘 (INT 13h)
- MBR (x86)
- Intel® 64 and IA-32 架构软件开发人员手册下载地址
- GRUB引导程序之第一阶段stage1.S分析
- movsb movsw movsd 指令详解
开机流程简述与MBR引导程序的关系
引入鸟哥的Linux私房菜一书中的原文:
- 载入 BIOS 的硬件信息与进行自我检测(自检),并根据设置取得第一个可启动的装置;
- 读取并执行第一个启动设备内的 MBR 的启动引导程序(亦即是grub2、spfdisk等程序);
- 根据启动引导程序的设置加载 Kernel , Kernel 会开始检测硬件与加载驱动程序;
- 在硬件驱动成功后,Kernel 会主动调用 systemd 程序,并以 default.target 流程启动;
systemd 执行 sysinit.target 初始化系统及 basic.target 准备操作系统;
systemd 启动 multi-user.target 下的本机与服务器服务;
systemd 执行 multi-user.target 下的/etc/rc.d/rc.local
文件;
systemd 执行 multi-user.target 下的 getty.target 及登录服务;
systemd 执行 graphical 需要的服务。由于我的系统软件大多放置到硬盘中,所以 BIOS 会指定启动的设备好让我们可以读取磁盘中的操作系统内核文件。但由于不同的操作系统它的文件系统格式不相同,因此我们必须要以一个启动引导程序来处理内核文件加载(load)的问题,因此这个启动引导程序就被称为 boot loader。那这个 boot loader 程序安装在哪里?就在启动设备的第一个扇区(sector)中,也就是我们一直谈到的 MBR(Master Boot Record,主引导记录)。
而最终 boot loader 的功能就是【加载内核文件】。
在 BIOS 读完信息后,接下来就是会到第一个启动设备的 MBR 去读取 boot loader 了。
Linux 将 boot loader 的程序代码执行与设置值加载分成两个阶段(stage)
- Stage 1:执行 boot loader 主程序
第一阶段为执行 boot loader 的主程序,这个主程序必须要被安装在启动区,亦即是 MBR 或启动扇区(boot sector)。但如前所述,因为 MBR 实在太小了,所以 MBR 或启动扇区通常仅安装 boot loader 的最小主程序,并没有安装 loader 的相关配置文件。- Stage 2:主程序加载配置文件
第二阶段为通过 boot loader 加载所有配置文件与相关的环境参数文件(包括文件系统定义与主要配置文件 grub.cfg),一般来说,配置文件都在/boot
下面。这些与 gurb2 有关的文件都放置到
/boot/grub2
中。
进入 BIOS 确认开机启动磁盘
上图Removable Devices
分类下没有设备,
所以排第一顺位的是Bootable Add-in Cards
这个设备。
其次是VMware Virtual SCSI Hard Drive (0:0)
,这就是我们在虚拟机配置的唯一一块硬盘。
经过试验,将
VMware Virtual SCSI Hard Drive (0:0)
调整为第一顺位,然后开机,开机过程和结果完全没有变化,都是正常开机,仿佛Bootable Add-in Cards
没有产生任何效果一般。
那么上面这个Bootable Add-in Cards
是什么呢?直接中文翻译就是“可引导开机的附加卡”,我猜测它可能的作用是在不需要进 BIOS 调整引导顺序的情况下,快速从U盘或者其他存储卡进行引导。
就是说,你插入了引导U盘,那么优先从U盘引导开机,如果没有U盘,那么还是从第二顺位的硬盘引导启动。
总结:
在正常引导开机进入CentOS系统的情况下,其实引导磁盘是VMware Virtual SCSI Hard Drive (0:0)
,我们只要在CentOS中找到对应的硬盘设备可以进行下一步了。
获取引导磁盘第一扇区MBR数据
确认磁盘挂载
[root@localhost ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 60G 0 disk
├─sda1 8:1 0 200M 0 part /boot
└─sda2 8:2 0 59.8G 0 part
├─centos-root 253:0 0 37.8G 0 lvm /
├─centos-swap 253:1 0 10G 0 lvm [SWAP]
└─centos-home 253:2 0 12G 0 lvm /home
sr0 11:0 1 1024M 0 rom
我暂时不知道具体的手段,来证明
/dev/sda
这个设备就是VMware Virtual SCSI Hard Drive (0:0)
。我只是因为我只给虚拟机配置了唯一一块虚拟硬盘,才能够确定是/dev/sda
复制数据作为文件
[root@localhost niao]# dd if=/dev/sda of=MBR.bin bs=512 count=1
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.00241201 s, 212 kB/s
说明:
1+0 records in
里面的数字1的单位是扇区,指的是一共读了磁盘上1个扇区的数据。
最终会得到一个文件:MBR.bin
内容是:
[root@localhost niao]# hexdump -C MBR.bin
00000000 eb 63 90 10 8e d0 bc 00 b0 b8 00 00 8e d8 8e c0 |.c..............|
00000010 fb be 00 7c bf 00 06 b9 00 02 f3 a4 ea 21 06 00 |...|.........!..|
00000020 00 be be 07 38 04 75 0b 83 c6 10 81 fe fe 07 75 |....8.u........u|
00000030 f3 eb 16 b4 02 b0 01 bb 00 7c b2 80 8a 74 01 8b |.........|...t..|
00000040 4c 02 cd 13 ea 00 7c 00 00 eb fe 00 00 00 00 00 |L.....|.........|
00000050 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00 |................|
00000060 00 00 00 00 ff fa 90 90 f6 c2 80 74 05 f6 c2 70 |...........t...p|
00000070 74 02 b2 80 ea 79 7c 00 00 31 c0 8e d8 8e d0 bc |t....y|..1......|
00000080 00 20 fb a0 64 7c 3c ff 74 02 88 c2 52 be 05 7c |. ..d|<.t...R..||
00000090 b4 41 bb aa 55 cd 13 5a 52 72 3d 81 fb 55 aa 75 |.A..U..ZRr=..U.u|
000000a0 37 83 e1 01 74 32 31 c0 89 44 04 40 88 44 ff 89 |7...t21..D.@.D..|
000000b0 44 02 c7 04 10 00 66 8b 1e 5c 7c 66 89 5c 08 66 |D.....f..\|f.\.f|
000000c0 8b 1e 60 7c 66 89 5c 0c c7 44 06 00 70 b4 42 cd |..`|f.\..D..p.B.|
000000d0 13 72 05 bb 00 70 eb 76 b4 08 cd 13 73 0d 5a 84 |.r...p.v....s.Z.|
000000e0 d2 0f 83 de 00 be 85 7d e9 82 00 66 0f b6 c6 88 |.......}...f....|
000000f0 64 ff 40 66 89 44 04 0f b6 d1 c1 e2 02 88 e8 88 |d.@f.D..........|
00000100 f4 40 89 44 08 0f b6 c2 c0 e8 02 66 89 04 66 a1 |.@.D.......f..f.|
00000110 60 7c 66 09 c0 75 4e 66 a1 5c 7c 66 31 d2 66 f7 |`|f..uNf.\|f1.f.|
00000120 34 88 d1 31 d2 66 f7 74 04 3b 44 08 7d 37 fe c1 |4..1.f.t.;D.}7..|
00000130 88 c5 30 c0 c1 e8 02 08 c1 88 d0 5a 88 c6 bb 00 |..0........Z....|
00000140 70 8e c3 31 db b8 01 02 cd 13 72 1e 8c c3 60 1e |p..1......r...`.|
00000150 b9 00 01 8e db 31 f6 bf 00 80 8e c6 fc f3 a5 1f |.....1..........|
00000160 61 ff 26 5a 7c be 80 7d eb 03 be 8f 7d e8 34 00 |a.&Z|..}....}.4.|
00000170 be 94 7d e8 2e 00 cd 18 eb fe 47 52 55 42 20 00 |..}.......GRUB .|
00000180 47 65 6f 6d 00 48 61 72 64 20 44 69 73 6b 00 52 |Geom.Hard Disk.R|
00000190 65 61 64 00 20 45 72 72 6f 72 0d 0a 00 bb 01 00 |ead. Error......|
000001a0 b4 0e cd 10 ac 3c 00 75 f4 c3 00 00 00 00 00 00 |.....<.u........|
000001b0 00 00 00 00 00 00 00 00 fe 8b 0b 00 00 00 80 20 |............... |
000001c0 21 00 83 9f 06 19 00 08 00 00 00 40 06 00 00 9f |!..........@....|
000001d0 07 19 8e fe ff ff 00 48 06 00 00 b8 79 07 00 00 |.......H....y...|
000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200
反汇编MBR.bin
利用nasm
命令反汇编
# 安装nasm
yum install nasm
[root@localhost niao]# ndisasm MBR.bin
00000000 EB63 jmp short 0x65
00000002 90 nop
00000003 108ED0BC adc [bp-0x4330],cl
00000007 00B0B800 add [bx+si+0xb8],dh
0000000B 008ED88E add [bp-0x7128],cl
0000000F C0FBBE sar bl,byte 0xbe
00000012 007CBF add [si-0x41],bh
00000015 0006B900 add [0xb9],al
00000019 02F3 add dh,bl
0000001B A4 movsb
0000001C EA21060000 jmp word 0x0:0x621
00000021 BEBE07 mov si,0x7be
00000024 3804 cmp [si],al
00000026 750B jnz 0x33
00000028 83C610 add si,byte +0x10
0000002B 81FEFE07 cmp si,0x7fe
0000002F 75F3 jnz 0x24
00000031 EB16 jmp short 0x49
00000033 B402 mov ah,0x2
00000035 B001 mov al,0x1
00000037 BB007C mov bx,0x7c00
0000003A B280 mov dl,0x80
0000003C 8A7401 mov dh,[si+0x1]
0000003F 8B4C02 mov cx,[si+0x2]
00000042 CD13 int 0x13
00000044 EA007C0000 jmp word 0x0:0x7c00
00000049 EBFE jmp short 0x49
0000004B 0000 add [bx+si],al
0000004D 0000 add [bx+si],al
0000004F 0000 add [bx+si],al
00000051 0000 add [bx+si],al
00000053 0000 add [bx+si],al
00000055 0000 add [bx+si],al
00000057 0000 add [bx+si],al
00000059 0000 add [bx+si],al
0000005B 800100 add byte [bx+di],0x0
0000005E 0000 add [bx+si],al
00000060 0000 add [bx+si],al
00000062 0000 add [bx+si],al
00000064 FF db 0xff
00000065 FA cli
00000066 90 nop
00000067 90 nop
00000068 F6C280 test dl,0x80
0000006B 7405 jz 0x72
0000006D F6C270 test dl,0x70
00000070 7402 jz 0x74
00000072 B280 mov dl,0x80
00000074 EA797C0000 jmp word 0x0:0x7c79
00000079 31C0 xor ax,ax
0000007B 8ED8 mov ds,ax
0000007D 8ED0 mov ss,ax
0000007F BC0020 mov sp,0x2000
00000082 FB sti
00000083 A0647C mov al,[0x7c64]
00000086 3CFF cmp al,0xff
00000088 7402 jz 0x8c
0000008A 88C2 mov dl,al
0000008C 52 push dx
0000008D BE057C mov si,0x7c05
00000090 B441 mov ah,0x41
00000092 BBAA55 mov bx,0x55aa
00000095 CD13 int 0x13
00000097 5A pop dx
00000098 52 push dx
00000099 723D jc 0xd8
0000009B 81FB55AA cmp bx,0xaa55
0000009F 7537 jnz 0xd8
000000A1 83E101 and cx,byte +0x1
000000A4 7432 jz 0xd8
000000A6 31C0 xor ax,ax
000000A8 894404 mov [si+0x4],ax
000000AB 40 inc ax
000000AC 8844FF mov [si-0x1],al
000000AF 894402 mov [si+0x2],ax
000000B2 C7041000 mov word [si],0x10
000000B6 668B1E5C7C mov ebx,[0x7c5c]
000000BB 66895C08 mov [si+0x8],ebx
000000BF 668B1E607C mov ebx,[0x7c60]
000000C4 66895C0C mov [si+0xc],ebx
000000C8 C744060070 mov word [si+0x6],0x7000
000000CD B442 mov ah,0x42
000000CF CD13 int 0x13
000000D1 7205 jc 0xd8
000000D3 BB0070 mov bx,0x7000
000000D6 EB76 jmp short 0x14e
000000D8 B408 mov ah,0x8
000000DA CD13 int 0x13
000000DC 730D jnc 0xeb
000000DE 5A pop dx
000000DF 84D2 test dl,dl
000000E1 0F83DE00 jnc word 0x1c3
000000E5 BE857D mov si,0x7d85
000000E8 E98200 jmp word 0x16d
000000EB 660FB6C6 movzx eax,dh
000000EF 8864FF mov [si-0x1],ah
000000F2 40 inc ax
000000F3 66894404 mov [si+0x4],eax
000000F7 0FB6D1 movzx dx,cl
000000FA C1E202 shl dx,byte 0x2
000000FD 88E8 mov al,ch
000000FF 88F4 mov ah,dh
00000101 40 inc ax
00000102 894408 mov [si+0x8],ax
00000105 0FB6C2 movzx ax,dl
00000108 C0E802 shr al,byte 0x2
0000010B 668904 mov [si],eax
0000010E 66A1607C mov eax,[0x7c60]
00000112 6609C0 or eax,eax
00000115 754E jnz 0x165
00000117 66A15C7C mov eax,[0x7c5c]
0000011B 6631D2 xor edx,edx
0000011E 66F734 div dword [si]
00000121 88D1 mov cl,dl
00000123 31D2 xor dx,dx
00000125 66F77404 div dword [si+0x4]
00000129 3B4408 cmp ax,[si+0x8]
0000012C 7D37 jnl 0x165
0000012E FEC1 inc cl
00000130 88C5 mov ch,al
00000132 30C0 xor al,al
00000134 C1E802 shr ax,byte 0x2
00000137 08C1 or cl,al
00000139 88D0 mov al,dl
0000013B 5A pop dx
0000013C 88C6 mov dh,al
0000013E BB0070 mov bx,0x7000
00000141 8EC3 mov es,bx
00000143 31DB xor bx,bx
00000145 B80102 mov ax,0x201
00000148 CD13 int 0x13
0000014A 721E jc 0x16a
0000014C 8CC3 mov bx,es
0000014E 60 pushaw
0000014F 1E push ds
00000150 B90001 mov cx,0x100
00000153 8EDB mov ds,bx
00000155 31F6 xor si,si
00000157 BF0080 mov di,0x8000
0000015A 8EC6 mov es,si
0000015C FC cld
0000015D F3A5 rep movsw
0000015F 1F pop ds
00000160 61 popaw
00000161 FF265A7C jmp word [0x7c5a]
00000165 BE807D mov si,0x7d80
00000168 EB03 jmp short 0x16d
0000016A BE8F7D mov si,0x7d8f
0000016D E83400 call word 0x1a4
00000170 BE947D mov si,0x7d94
00000173 E82E00 call word 0x1a4
00000176 CD18 int 0x18
00000178 EBFE jmp short 0x178
0000017A 47 inc di
0000017B 52 push dx
0000017C 55 push bp
0000017D 42 inc dx
0000017E 2000 and [bx+si],al
00000180 47 inc di
00000181 656F gs outsw
00000183 6D insw
00000184 004861 add [bx+si+0x61],cl
00000187 7264 jc 0x1ed
00000189 204469 and [si+0x69],al
0000018C 736B jnc 0x1f9
0000018E 005265 add [bp+si+0x65],dl
00000191 61 popaw
00000192 640020 add [fs:bx+si],ah
00000195 45 inc bp
00000196 7272 jc 0x20a
00000198 6F outsw
00000199 720D jc 0x1a8
0000019B 0A00 or al,[bx+si]
0000019D BB0100 mov bx,0x1
000001A0 B40E mov ah,0xe
000001A2 CD10 int 0x10
000001A4 AC lodsb
000001A5 3C00 cmp al,0x0
000001A7 75F4 jnz 0x19d
000001A9 C3 ret
000001AA 0000 add [bx+si],al
000001AC 0000 add [bx+si],al
000001AE 0000 add [bx+si],al
000001B0 0000 add [bx+si],al
000001B2 0000 add [bx+si],al
000001B4 0000 add [bx+si],al
000001B6 0000 add [bx+si],al
000001B8 FE8B0B00 dec byte [bp+di+0xb]
000001BC 0000 add [bx+si],al
000001BE 802021 and byte [bx+si],0x21
000001C1 00839F06 add [bp+di+0x69f],al
000001C5 1900 sbb [bx+si],ax
000001C7 0800 or [bx+si],al
000001C9 0000 add [bx+si],al
000001CB 40 inc ax
000001CC 06 push es
000001CD 0000 add [bx+si],al
000001CF 9F lahf
000001D0 07 pop es
000001D1 198EFEFF sbb [bp-0x2],cx
000001D5 FF00 inc word [bx+si]
000001D7 48 dec ax
000001D8 06 push es
000001D9 0000 add [bx+si],al
000001DB B87907 mov ax,0x779
000001DE 0000 add [bx+si],al
000001E0 0000 add [bx+si],al
000001E2 0000 add [bx+si],al
000001E4 0000 add [bx+si],al
000001E6 0000 add [bx+si],al
000001E8 0000 add [bx+si],al
000001EA 0000 add [bx+si],al
000001EC 0000 add [bx+si],al
000001EE 0000 add [bx+si],al
000001F0 0000 add [bx+si],al
000001F2 0000 add [bx+si],al
000001F4 0000 add [bx+si],al
000001F6 0000 add [bx+si],al
000001F8 0000 add [bx+si],al
000001FA 0000 add [bx+si],al
000001FC 0000 add [bx+si],al
000001FE 55 push bp
000001FF AA stosb
源码解读
整个MBR(512 Byte)会被BIOS加载到内存0x7C00
位置
格式说明
以第一条指令为例,对格式进行说明:0: eb 63 jmp 0x65
内存偏移地址(IP寄存器) | 机器操作码 | 汇编助记码及其操作数 |
---|---|---|
00000000 | EB63 | jmp short 0x65 |
00000000 EB63 jmp short 0x65
CPU执行jmp short 0x65
指令的过程:
- CPU根据 CS:IP (假设是
7C00:00
)指向的内存单元读取jmp short 0x65
指令,读取的指令进入指令缓冲器; - IP=IP+所读指令的长度(2),从而指向下一个指令,此时CS:IP=
7C00:02
; - CPU执行指令
jmp short 0x65
,IP寄存器的值更新为65
这里会引出一个疑问:第一行就是转移指令,跨过了很大一块区域(02H~65H),这里难道不是指令?
事后对比GRUB2的源码就知道了:
此空间用于 BIOS 参数块!!!! 不要更改第一个跳转,也不要在该区域之后的任何地方开始代码(即下表蓝色区域)。
00000065 FA cli
- CPU读取 CS:IP=
7C00:65
处的指令(cli
)到指令缓冲器。 - 更新CS:IP=
7C00:66
- CPU执行
cli
指令,这是禁止CPU中断发生,确保当前运行的代码不会被打断。
00000066 90 nop
- CPU读取 CS:IP=
7C00:66
处的指令(nop
)到指令缓冲器。 - 更新CS:IP=
7C00:67
- CPU执行
nop
指令,这是个空操作指令,只是用于保证上下指令之间增加一个稳定时间。
00000067 90 nop
- CPU读取 CS:IP=
7C00:67
处的指令(nop
)到指令缓冲器。 - 更新CS:IP=
7C00:68
- CPU执行
nop
指令,这是个空操作指令,只是用于保证上下指令之间增加一个稳定时间。
00000068 F6C280 test dl,0x80
test指令说明:
Test对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。
TEST AX,BX 与 AND AX,BX 命令有相同效果,只是Test指令不改变AX和BX的内容,而AND指令会把结果保存到AX中。
同时也会更新flag寄存器中的这几个标志位:C,O,P,Z,S(其中C与O两个标志会被设为0)
flag寄存器的运算结构标志位说明:
- CF:进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
- OF:溢出标志位。一般情况下,在进行有符号数运算的时候,运算结果超过了机器所能表示的范围称为溢出,1表示有溢出,0表示无溢出
- PF:奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。1表示偶数,0表示奇数。实现原理,从第0位开始,逐位与相邻位取同或,直到第7位,同或结果就是PF值。
- ZF:零标志位。它记录相关指令执行后,其结果是否为0。1表示结果是0,0表示结果非0。
- SF:符号标志位。它记录相关指令执行后,其结果是否为负。1表示结果为负,0表示结果非负。
- CPU读取 CS:IP=
7C00:68
处的指令(test dl,0x80
)到指令缓冲器。 - 更新CS:IP=
7C00:6B
- 执行指令
test dl,0x80
dl寄存器代表驱动器号(80H到FFH);软驱从0开始,硬盘从80开始。那么这里测试dl寄存器是不是80开头的,很明显就是测试是否是硬盘驱动了。
dl寄存器没有赋过值,它是0x0
,将0x80
和0x0
进行与运算,得到结果是0x0
,而标志寄存器记录的状态为:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
0000006B 7405 jz 0x72
- CPU读取 CS:IP=
7C00:6B
处的指令(jz 0x72
)到指令缓冲器。 - 更新CS:IP=
7C00:6D
- 执行指令
jz 0x72
jz指令,它的作用是,判断flag寄存器的ZF标志位是否为1,如果为1则进行转移。所以je指令是根据上一条指令的执行结果作为条件来进行执行的。
可知上一条指令test dl,0x80
的结果是ZF=1
,那么指令实际含义是:如果dl寄存器的值不是80开头,代表不是硬盘驱动,那么就更新IP寄存器的值为72
。
此时CS:IP=7C00:72
00000072 B280 mov dl,0x80
- CPU读取 CS:IP=
7C00:72
处的指令(mov dl,0x80
)到指令缓冲器。 - 更新CS:IP=
7C00:74
- 执行指令
mov dl,0x80
,将80
送入dl寄存器
该指令和上一条指令配合,共同实现:判断dl寄存器是否80开头(代表硬盘驱动器),如果不是,则直接将
0x80
送入dl覆盖。
00000074 EA797C0000 jmp word 0x0:0x7c79
- CPU读取 CS:IP=
7C00:74
处的指令(jmp word 0x0:0x7c79
)到指令缓冲器。 - 更新CS:IP=
7C00:79
- 执行指令
jmp word 0x0:0x7c79
。 将CS:IP寄存器改为0x7c79
ljmp 到下一条指令,因为一些虚假的 BIOS 跳转到 07C0:0000 而不是 0000:7C00。为了以防万一,用此命令进行纠正。
00000079 31C0 xor ax,ax
- CPU读取 CS:IP=
00:7C79
处的指令(xor ax,ax
)到指令缓冲器。 - 更新CS:IP=
00:7C7B
- 执行指令
xor ax,ax
。 将AX寄存器的值,与自身做逻辑异或计算,始终得到0x0
,并将结果送入AX寄存器。 - flag寄存器PF奇偶标记位更新为1
0000007B 8ED8 mov ds,ax
- CPU读取 CS:IP=
00:7C7B
处的指令(mov ds,ax
)到指令缓冲器。 - 更新CS:IP=
00:7C7D
- 执行指令
mov ds,ax
。 此时AX=0x0
,将AX的值设置到DS寄存器中,最终DS=0x0
CPU 要读写一个内存单元的时候,必须先给出这个内存单元的地址,在 8086CPU 中,内存地址由段地址和偏移地址组成。8086CPU 中有一个 DS 寄存器,通常用来存放要访问数据的段地址。比如我们要读取 10000H 单元的内容,可以用如下的程序段进行。
mov bx,1000H mov ds,bx mov al,[0]
上面
[...]
形式表示一个内存单元,而[0]
中的0
只是一个内存单元的偏移地址,而整个mov al,[0]
指令都没有明确指定一个段地址,像这种指令执行的时候,CPU 会自动从 DS 寄存器中取值作为[...]
这个内存单元的段地址。
例如mov al,[0]
就是指把1000:0
这个内存单元的值传送给 al 寄存器。
0000007D 8ED0 mov ss,ax
- CPU读取 CS:IP=
00:7C7D
处的指令(mov ss,ax
)到指令缓冲器。 - 更新CS:IP=
00:7C7F
- 执行指令
mov ss,ax
。 此时AX=0x0
,将AX的值设置到SS寄存器中,最终SS=0x0
8086CPU 中,有两个寄存器,段寄存器 SS 和寄存器 SP,栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中。任意时刻,
SS:SP
指向栈顶元素。push
指令和pop
指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。
0000007F BC0020 mov sp,0x2000
- CPU读取 CS:IP=
00:7C7F
处的指令(mov sp,0x2000
)到指令缓冲器。 - 更新CS:IP=
00:7C82
- 执行指令
mov sp,0x2000
。 将SP寄存器的值设为SP=0x2000
此时 SS 和 SP 两个寄存器都有了明确的值,代表此段代码执行到这里,正式构建了一个栈空间。
SS:SP=00:2000
00000082 FB sti
- CPU读取 CS:IP=
00:7C82
处的指令(sti
)到指令缓冲器。 - 更新CS:IP=
00:7C83
- 执行指令
sti
。 允许CPU中断发生。
这条指令和上面00000065 FA cli
指令形成配合,在cli
和sti
包围中的代码不会被外部中断,以确保它们能够一次正确运行。
00000083 A0647C mov al,[0x7c64]
从 DS 寄存器取出值0x0
作为[0x7c64]
内存单元的段地址,合起来就是:0:7C64
。
内存地址0:7C64
所指位置代表的是启动盘:从中加载内核的磁盘,0xff表示使用启动盘。
指令mov al,[0x7c64]
的意思就是将AX低字节AL寄存器的值设为0xff
00000086 3CFF cmp al,0xff
cmp指令说明:
CMP(比较)指令执行从目的操作数中减去源操作数的隐含减法操作,并且不修改任何操作数
比较两个无符号数,则标志位结果表示如下表所示:
CMP结果 ZF CF 目的操作数 < 源操作数 0 1 目的操作数 > 源操作数 0 0 目的操作数 = 源操作数 1 0 比较两个有符号数,则标志位结果表示如下表所示:
CMP结果 标志位 目的操作数 < 源操作数 SF ≠ OF 目的操作数 > 源操作数 SF=OF 目的操作数 = 源操作数 ZF=1
cmp al,0xff
指令的意思是,比较AL寄存器的值和0xff
的大小。
此时AL=0xff
,cmp结果相等,即是flag寄存器标记位ZF=1;
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
00000088 7402 jz 0x8c
jz指令根据flag寄存器标记位ZF是否等于1来进行转移。
此时ZF=1,jz指令进行转移,将IP的值设置为0x8c
。
这里检查我们是否有强制磁盘引用
0000008C 52 push dx
- CPU读取 CS:IP=
00:7C8C
处的指令(push dx
)到指令缓冲器。 - 更新CS:IP=
00:7C8D
- 执行指令
push dx
。
栈是一种具有特殊的访问方式的存储空间,它的特性在于,最后进入这个空间的数据,最先出去。
入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。
栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)8086CPU 中,有两个寄存器,段寄存器 SS 和寄存器 SP,栈顶的段地址存放在 SS 中,偏移地址存放在 SP 中。任意时刻,
SS:SP
指向栈顶元素。push
指令和pop
指令执行时,CPU 从 SS 和 SP 中得到栈顶的地址。
push dx
的执行,由以下步骤完成。
- 取出寄存器记录的栈顶地址SS:SP=
00:2000
- 判断数据宽度:DX 寄存器数据是16位,所以SP=SP-2=
0X1FFE
- 取出 DX 寄存器的值,由上面
00000072 B280 mov dl,0x80
指令可知 DX 的低位寄存器 dl 被设置过值0x80
, 而整个 DX =0x0080
- 将 DX 中的内容送入 SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶
此时栈内状态如下表(H代表16进制数):
内存单元地址 | 数据 | SS:SP所在位置 | 来源 |
---|---|---|---|
1FFEH | 80 | SS:SP | DX |
1FFFH | 00 | ||
2000H |
0000008D BE057C mov si,0x7c05
寄存器赋值:SI=0x7C05
SI 寄存器是4个可以用在
[...]
中来进行内存单元寻址的寄存器之一。其他三个分别是:BX、DI、BP。
只要在
[...]
中使用寄存器 BP,而指令中没有显性地给出段地址,段地址就默认在 SS 中,除此之外,段地址默认在 DS 中。
这一步是将 si 寄存器设置为磁盘地址包的地址
00000090 B441 mov ah,0x41
寄存器赋值:AH=0x41
ah寄存器代表的是即将执行的中断例程的功能号。
int 13 ah=41代表的是,检查是否支持LBA寻址模式。
00000092 BBAA55 mov bx,0x55aa
寄存器赋值:BX=0X55AA
00000095 CD13 int 0x13
- CPU读取 CS:IP=
00:7C95
处的指令(int 0x13
)到指令缓冲器。 - 更新CS:IP=
00:7C97
- 执行指令
int 0x13
。
int 指令的格式为:int n,n 为中断类型码,它的功能是引发中断过程。
CPU 执行 int n 指令,相当于引发一个 n 号中断的中断过程,执行过程如下:
- 根据中断类型码 n,从中断向量表中取得对应的中断程序入口地址(段地址和偏移地址)
- 标志寄存器入栈,IF=0,TF=0;
此时SS:SP=00:1FFE
,标志寄存器16位,SP=SP-2=1FFC
;
标志寄存器此时的值为:FLAG=0x0044
=0000 0000 0100 0100
入栈后,栈顶表现为:0000:1FFD=0x00
、0000:1FFC=0x44
- CS 入栈;16位,SP=SP-2=
1FFC
:0000:1FFB=0x00
、0000:1FFA=0x00
- IP 入栈;16位,SP=SP-2=
1FF8
:0000:1FF9=0x7C
、0000:1FF8=0x97
- (IP)=(n*4),计算结果IP=
0000:004C
内存单元的16位的值 - (CS)=(n*4+2),计算结果CS=
0000:004E
内存单元的16位的值
在最后一步完成后,CPU 开始执行 n 号(0x13
号)中断的中断处理程序。
flag寄存器的状态控制标志位说明:
状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
- TF(追踪标志位):当TF被置为1 时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。
注意:指令系统中没有专门的指令来改变标志位TF的值,但程序员可用 pushf 入栈后修改栈内值再 popf 出栈来修改TF标志位的值。- IF(中断允许标志位):用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求,当IF=1时,CPU响应CPU外部的可屏蔽中断发出的中断请求,当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
注意:不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。- DF(方向标志位):在串处理指令中,每次操作后,如果DF=0,则si、di递增,如果DF=1,则si、di递减。
注意:DF的值是由程序员进行设定的。(cld命令是将DF置为0,std命令是将DF值为1)。
中断向量表:
CPU 用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。中断向量表,就是中断处理程序入口地址的列表。
中断向量表在内存中保存,其中存放着 256 个中断源所对应的中断处理程序的入口。
对于 8086CPU 机,中断向量表指定放在内存地址0处,从内存 0000:0000 到 0000:03FF 的 1024 个单元中存放着中断向量表。
中断例程:
中断例程即中断向量表指向的中断处理程序,名称使用惯例而已。
CPU 执行 int 指令进入终端例程之前,标志寄存器、当前的 CS 和 IP 被压入栈中,在执行完中断例程后,应该用 iret 指令恢复 int 指令执行前的标志寄存器和 CS、IP 的值,从而接着执行应用程序。
int 指令和 iret 指令的配合使用与 call 指令和 ret 指令的配合使用具有相似的思路。只是 iret 比 ret 多了一个标志寄存器。所有中断例程的最后一个指令都是 iret。它用汇编指令来理解就相当于:
pop IP pop CS popf
int 13H ah=41H
API说明
功能:检验扩展功能是否存在
入口:
- ah = 41H
- BX = 55AAH
- DL = 驱动器号
返回:
- CF = 0
- ah = 扩展功能的主版本号
- al = 内部使用
- BX = AA55H
- CX = 接口支持的位掩码
- 1 使用数据包结构的设备访问
- 2 驱动器锁定和弹出
- 4 增强的磁盘驱动支持(EDD)
- CF = 1
- ah = 错误码01H,无效命令
这个调用检验对特定的驱动器是否存在扩展功能. 如果进位标志置 1 则此驱动器不支持扩展功能. 如果进位标志为 0, 同时 BX = AA55h, 则存在扩展功能. 此时 CX 的 0 位表示是否支持第一个子集, 1位表示是否支持第二个子集.
对于 1.x 版的扩展 Int13H 来说, 主版本号 AH = 1. AL 是副版本号,但这仅限于 BIOS 内部使用, 任何软件不得检查 AL 的值.
int 13H ah=41H
中断例程执行前
int 0x13
指令执行前的标志寄存器状态如下表:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
int 0x13
指令执行期间,中断例程执行前的栈空间状态如下表:
内存单元地址 | 数据 | SS:SP所在位置 | 来源 |
---|---|---|---|
1FF8H | 97 | SS:SP | IP |
1FF9H | 7C | ||
1FFAH | 00 | CS | |
1FFBH | 00 | ||
1FFCH | 44 | FLAG | |
1FFDH | 00 | ||
1FFEH | 80 | DX | |
1FFFH | 00 | ||
2000H |
int 0x13 AH=41H
指令执行期间,中断例程执行前的各入口参数寄存器状态如下表:
寄存器 | 数据 | 参数意义 |
---|---|---|
ah | 0x41 | 检测扩展中断功能是否安装 |
BX | 0x55AA | 为了主动避免BX原值跟中断例程的结果混淆,在中断前先赋值为0x55AA |
dl | 0x80 | 驱动器号(80H到FFH);软驱从0开始,硬盘从80开始 |
int 13H ah=41H
中断例程执行过程
寄存器赋值:BX=0XAA55
int 13H ah=41H
中断例程执行后
中断例程的最后一条指令 iret 执行,恢复CS:IP和标志寄存器,返回继续执行主程序指令。
- 出栈,恢复主程序IP=
0x7C97
- 出栈,恢复主程序CS=
0x0000
- 出栈,恢复主程序FLAG=
0x0044
int 0x13 AH=41H
指令执行期间,中断例程执行后的各出口参数寄存器状态如下表:
寄存器 | 数据 | 参数意义 |
---|---|---|
BX | 0xAA55 | 成功时,它被例程更新为BX=0xAA55,否则它保持原值不变 |
ah | 0x00 | 成功时,ah=版本号,否则ah=1 |
CS | 0x0000 | 出栈恢复中断前 |
IP | 0x7C97 | 出栈恢复中断前 |
FLAG | 0x0044 | 出栈恢复中断前,但是CF标志位成功是为0,否则为1 |
iret 指令执行后的栈空间状态:
内存单元地址 | 数据 | SS:SP所在位置 | 来源 |
---|---|---|---|
1FFEH | 80 | SS:SP | DX |
1FFFH | 00 | ||
2000H |
00000097 5A pop dx
出栈:恢复DX=0x0080
内存单元地址 | 数据 | SS:SP所在位置 | 来源 |
---|---|---|---|
2000H | SS:SP |
00000098 52 push dx
入栈:保存DX=0x0080
内存单元地址 | 数据 | SS:SP所在位置 | 来源 |
---|---|---|---|
1FFEH | 80 | SS:SP | DX |
1FFFH | 00 | ||
2000H |
%dl 可能已被 INT 13 破坏,AH=41H。 例如,在 AST BIOS 1.04 中会发生这种情况。所以通过重复出入栈来纠正。
00000099 723D jc 0xd8
- CPU读取 CS:IP=
00:7C99
处的指令(jc 0xd8
)到指令缓冲器。 - 更新CS:IP=
00:7C9B
- 执行指令
jc 0xd8
。
jc 和 jnc 指令说明:
jc:标志位CF=1则跳转否则不跳转
jnc:标志位CF=0则跳转否则不跳转
当前标志寄存器:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
所以jc 0xd8
不进行跳转。
jc
指令与上面的int 13H ah=41H
中断例程形成配合。后者的作用是判断 BIOS 是否支持扩展int13中断,如果支持,则CF=0,否则CF=1,那么jc
指令就可以根据 BIOS 是否支持扩展int13中断来执行不同位置的“子程序指令”。一般现在新的支持LBA模式的主板和Win98自带的DOS7操作系统是支持扩展INT 13的。
0xd8 处的指令是 CHS 寻址模式的,即是说如果不支持 LBA 寻址模式,则使用 CHS。
0000009B 81FB55AA cmp bx,0xaa55
- CPU读取 CS:IP=
00:7C9B
处的指令(cmp bx,0xaa55
)到指令缓冲器。 - 更新CS:IP=
00:7C9F
- 执行指令
cmp bx,0xaa55
。
cmp上文出现过,它是比较两个数值的大小。
此时 BX=0xAA55
,与第二个立即数0xAA55
相等,所以标志寄存器更新为:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
0000009F 7537 jnz 0xd8
- CPU读取 CS:IP=
00:7C9F
处的指令(jnz 0xd8
)到指令缓冲器。 - 更新CS:IP=
00:7CA1
- 执行指令
jnz 0xd8
。
此时标志位ZF不等于0,所以jnz 0xd8
不进行跳转。
谨慎起见,这条指令和上一条指令配合使用,继jc 0xd8
之后,再次对int 13H ah=41H
中断指令的结果进行确认。确认BIOS支持扩展int13。
0xd8 处的指令是 CHS 寻址模式的,即是说如果不支持 LBA 寻址模式,则使用 CHS。
000000A1 83E101 and cx,byte +0x1
- CPU读取 CS:IP=
00:7CA1
处的指令(and cx,byte +0x1
)到指令缓冲器。 - 更新CS:IP=
00:7CA4
- 执行指令
and cx,byte +0x1
。
1 byte=8 bit
byte +0x1
= 0000 0001
and 指令说明:按位与运算
二进制下,每个对应位两者都为1则为1,否则为0;
AND 指令总是清除溢出和进位标志位,并根据目标操作数的值来修改符号标志位、零标志位和奇偶标志位。
结果:
由于 int13 ah=41
检查是否支持 LBA了,不知道怎么了CX的最低位被设置了1,就代表支持LBA。
并且and指令执行完毕后,cx=0x01
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 0 | 1 | 0 |
000000A4 7432 jz 0xd8
- CPU读取 CS:IP=
00:7CA4
处的指令(jz 0xd8
)到指令缓冲器。 - 更新CS:IP=
00:7CA6
- 执行指令
jz 0xd8
。
此时标志位ZF=0
所以不进行跳转
000000A6 31C0 xor ax,ax
- CPU读取 CS:IP=
00:7C79
处的指令(xor ax,ax
)到指令缓冲器。 - 更新CS:IP=
00:7C7B
- 执行指令
xor ax,ax
。 将AX寄存器的值,与自身做逻辑异或计算,始终得到0x0
,并将结果送入AX寄存器。
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
000000A8 894404 mov [si+0x4],ax
上面有指令将 si 寄存器设置为磁盘地址包的地址0x7c05
[si+0x4]
作为内存偏移地址,而默认段地址是ds寄存器的值。ds=0x0,所以内存地址就是00:7c09
已知AX=0x00
把AX寄存器的值(16位),写入到内存00:7c09
处的连续两个字节。
那么这一步就是要把扩展功能的主版本号存入内存。
00:7c0aH | 00:7c09H |
---|---|
0x00 | 0x00 |
000000AB 40 inc ax
inc指令只有一个操作数,作用就是将操作数+1,结果送回到操作数。
已知AX=0x00
把ax寄存器的值(16位),自增1,结果为0x01,结果送回AX
此时AX=0x01
000000AC 8844FF mov [si-0x1],al
[si-0x1]
和ds寄存器,共同表示内存地址=00:7c04
将AX=0x0001(16位),那么其低字节寄存器al=0x01
写入内存00:7c04
处1个字节。
00:7c0aH | 00:7c09H | … | 00:7c04H |
---|---|---|---|
0x00 | 0x00 | … | 0x01 |
000000AF 894402 mov [si+0x2],ax
已知此时AX=0x0001,ds=0x0,si=0x7c05
[si-0x1]
和ds寄存器,共同表示内存地址=00:7c07
将ax寄存器的值写入内存:
00:7c0aH | 00:7c09H | 00:7c08H | 00:7c07H | … | 00:7c04H |
---|---|---|---|---|---|
0x00 | 0x00 | 0x00 | 0x01 | … | 0x01 |
000000B2 C7041000 mov word [si],0x10
已知ds=0x0,si=0x7c05
[si]
和ds寄存器,共同表示内存地址=00:7c05
将立即数0x10
写入内存:
00:7c0aH | 00:7c09H | 00:7c08H | 00:7c07H | … | 00:7c05H | 00:7c04H |
---|---|---|---|---|---|---|
0x00 | 0x00 | 0x00 | 0x01 | … | 0x10 | 0x01 |
000000B6 668B1E5C7C mov ebx,[0x7c5c]
ebx是bx的延伸,bx是16位寄存器,ebx是32位寄存器,也可以理解为bx是ebx的低字寄存器。
将内存00:7c5c
处连续4个字节(2个字)的数,写入到ebx寄存器中。
从磁盘头部数据表中可以发现,[0x7c5c]
对应的是下表的4个红色字节块:
磁盘数据写入内存之后如下表表示:
00:7c5fH | 00:7c5eH | 00:7c5dH | 00:7c5cH |
---|---|---|---|
0x00 | 0x00 | 0x00 | 0x01 |
将这段内存数据写入ebx=0x00000001
000000BB 66895C08 mov [si+0x8],ebx
已知:ebx=0x00000001,ds=0x00,si=0x7c05
[si+0x8]
和ds共同表示一个内存地址:00:7c0d
将ebx的值写入内存:
00:7c10H | 00:7c0fH | 00:7c0eH | 00:7c0dH | … | 00:7c0aH | 00:7c09H | 00:7c08H | 00:7c07H | … | 00:7c05H | 00:7c04H |
---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 0x00 | 0x00 | 0x01 | … | 0x00 | 0x00 | 0x00 | 0x01 | … | 0x10 | 0x01 |
000000BF 668B1E607C mov ebx,[0x7c60]
已知:ebx=0x00000001,ds=0x00
[0x7c60]
和ds共同表示一个内存地址:00:7c60
从磁盘头部数据表中可以发现,[0x7c60]
对应的是下表的4个红色字节块:
磁盘数据写入内存之后如下表表示:
00:7c63H | 00:7c62H | 00:7c61H | 00:7c60H |
---|---|---|---|
0x00 | 0x00 | 0x00 | 0x00 |
将这段内存数据写入ebx=0x00000000
000000C4 66895C0C mov [si+0xc],ebx
已知:ebx=0x00000000,ds=0x00,si=0x7c05
[si+0xc]
与ds共同表示内存地址:00:7c11
将ebx的值写入内存:
00:7c14H | 00:7c13H | 00:7c12H | 00:7c11H | 00:7c10H | 00:7c0fH | 00:7c0eH | 00:7c0dH | … | 00:7c0aH | 00:7c09H | 00:7c08H | 00:7c07H | … | 00:7c05H | 00:7c04H |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x01 | … | 0x00 | 0x00 | 0x00 | 0x01 | … | 0x10 | 0x01 |
000000C8 C744060070 mov word [si+0x6],0x7000
已知:ds=0x00,si=0x7c05
[si+0x6]
和ds共同表示内存地址:00:7c0b
将立即数0x7000写入内存:
00:7c14H | 00:7c13H | 00:7c12H | 00:7c11H | 00:7c10H | 00:7c0fH | 00:7c0eH | 00:7c0dH | 00:7c0cH | 00:7c0bH | 00:7c0aH | 00:7c09H | 00:7c08H | 00:7c07H | … | 00:7c05H | 00:7c04H |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x01 | 0x70 | 0x00 | 0x00 | 0x00 | 0x00 | 0x01 | … | 0x10 | 0x01 |
000000CD B442 mov ah,0x42
将立即数0x42送入ah
ah作为下一条中断指令的功能编号。
000000CF CD13 int 0x13
再次执行了一条int13
中断指令,跟上次执行的不同的是,这次ah=42,代表的功能不同了。
ah=42的功能api介绍:
读取磁盘:
- 在磁盘地址包结构中设置适当的值
- 设置 DS:SI -> 内存中的磁盘地址包
- 设置 AH = 0x42
- 设置 DL = “驱动器号”——“C”驱动器通常为 0x80
- 发出一个 INT 0x13。
如果在传输过程中出现任何错误,将设置进位标志。AH 成功时应设置为 0。
磁盘地址包格式:
Offset | Size | Description |
---|---|---|
0 | 1 | 数据包大小(24 字节) |
1 | 1 | 总是 0 |
2 | 2 | 要传输的扇区数(某些 BIOS 上最多 127 个) |
4 | 4 | 传输缓冲区(16 位段:16 位偏移)(见注释 #1) |
8 | 4 | 48位起始 LBA 的低 32 位 |
12 | 4 | 48位起始 LBA 的高 16 位 |
已知:
- DS:SI =
00:7c05
,代表“磁盘地址包”的地址 - 磁盘地址内容(结合DS:SI和磁盘地址包格式,对已知数据进行分段展示):
-
数据包大小(块的大小)
00:7c05H 0x10 -
要传输的扇区数(某些 BIOS 上最多 127 个)
00:7c08H 00:7c07H 0x00 0x01 -
传输缓冲区(16 位段:16 位偏移)
段h 段l 偏移地址h 偏移地址l 00:7c0cH 00:7c0bH 00:7c0aH 00:7c09H 0x70 0x00 0x00 0x00 -
48位起始 LBA 的低 32 位
00:7c10H 00:7c0fH 00:7c0eH 00:7c0dH 0x00 0x00 0x00 0x01 -
48位起始 LBA 的高 16 位
00:7c14H 00:7c13H 00:7c12H 00:7c11H 0x00 0x00 0x00 0x00
-
- AH = 0x42
- dl = 0x80
将编号80的驱动器(c盘)的第一个扇区(512 Byte)读到内存地址0x7000
处
注意:文档本身所描述的汇编代码是存在内存
0x7c00
处,地址不同,不要混淆,也不会覆盖。
000000D1 7205 jc 0xd8
- CPU读取 CS:IP=
00:7CD1
处的指令(jc 0xd8
)到指令缓冲器。 - 更新CS:IP=
00:7CD3
- 执行指令
jc 0xd8
。
jc 和 jnc 指令说明:
jc:标志位CF=1则跳转否则不跳转
jnc:标志位CF=0则跳转否则不跳转
假设int 13H ah=42H
读取磁盘数据到内存成功执行了,那么CF标志位复位0,所以当前标志寄存器:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF溢出 | DF | IF | TF | SF符号 | ZF零 | AF | PF奇偶 | CF进位 | |||||||
0 | 0 | 1 | 1 | 0 |
所以jc 0xd8
不进行跳转。
0xd8
是指当磁盘读取失败,则改用CHS寻址模式再尝试。
000000D3 BB0070 mov bx,0x7000
- CPU读取 CS:IP=
00:7CD3
处的指令(mov bx,0x7000
)到指令缓冲器。 - 更新CS:IP=
00:7CD6
- 执行指令
mov bx,0x7000
。
寄存器赋值:BX=0x7000
000000D6 EB76 jmp short 0x14e
已知CS:IP=00:7CD6
无条件跳转到0x14e
地址的指令处。即CS:IP=00:7D4E
0000014E 60 pushaw
pusha: push all
通用寄存器压(按规定顺序)入栈
0000014F 1E push ds
将寄存器DS压入栈,将此时的ds保存,因为后续有指令对其赋值更新,压入栈以便再次恢复。
00000150 B90001 mov cx,0x100
寄存器赋值:CX=0x100
,作为后续rep
指令的重复次数
00000153 8EDB mov ds,bx
已知BX=0x7000
寄存器赋值DS=0x7000
00000155 31F6 xor si,si
- CPU读取 CS:IP=
00:7D55
处的指令(xor si,si
)到指令缓冲器。 - 更新CS:IP=
00:7D57
- 执行指令
xor si,si
。 将AX寄存器的值,与自身做逻辑异或计算,始终得到0x0
,并将结果送入SI寄存器。 - flag寄存器PF奇偶标记位更新为1
此时DS:SI = 7000:00
00000157 BF0080 mov di,0x8000
注意:
0x8000
是GRUB引导机内核地址
寄存器赋值DI=0x8000
0000015A 8EC6 mov es,si
已知:SI=0x00
寄存器赋值ES=0x00
0000015C FC cld
已知:
- SI=
0x00
- DI=
0x8000
标志寄存器更新:DF=0
flag寄存器的状态控制标志位说明:
状态控制标志位是用来控制CPU操作的,它们要通过专门的指令才能使之发生改变。
- TF(追踪标志位):当TF被置为1 时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求。这种方式主要用于程序的调试。
注意:指令系统中没有专门的指令来改变标志位TF的值,但程序员可用 pushf 入栈后修改栈内值再 popf 出栈来修改TF标志位的值。- IF(中断允许标志位):用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求,当IF=1时,CPU响应CPU外部的可屏蔽中断发出的中断请求,当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。
注意:不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。- DF(方向标志位):在串处理指令中,每次操作后,如果DF=0,则si、di递增,如果DF=1,则si、di递减。
注意:DF的值是由程序员进行设定的。(cld命令是将DF置为0,std命令是将DF值为1)。
0000015D F3A5 rep movsw
已知:
- DS:SI=
7000:00
- ES:DI=
00:8000
- CX=
0x100
- DF=
0
指令说明:
rep
:(repeat)重复
rep指令作用是重复 后面的指令,是个前缀,例如这里rep movsw
,就是重复执行movsw
指令
rep指令每次执行的时候都从ecx寄存器里面读取值,当cx/ecx大于0,就执行后面语句,执行完以后,会让cx/ecx减1,然后再执行rep后面的指令。movsw
:数据传送指令,都是从源地址向目的地址传送数据
16位模式下:
源地址是DS:SI,目的地址是ES:DI
32位模式下:
源地址是DS:ESI,目的地址是ES:EDI
注意:在传送完成之后,SI和DI(或者ESI和EDI)会增加或者减小。
当DF=0 时,表示正向传送,传送之后SI和DI(或者ESI和EDI)的值会增加;
当DF=1 时,表示反向传送,传送之后SI和DI(或者ESI和EDI)的值会减小;movsb
:传送一个字节,之后SI和DI(或者ESI和EDI)加/减1
movsw
:传送一个字,之后SI和DI(或者ESI和EDI)加/减2
movsd
:传送一个双字,之后SI和DI(或者ESI和EDI)加/减4单纯的movsb/ movsw/ movsd只能执行一次,如果希望处理器自动地反复执行,可以加上指令前缀rep;在寄存器CX(16位模式)或者ECX(32位模式)中设置传送的次数。当CX/ECX不等于0时,则执行movsb/ movsw/ movsd,执行后,CX/ECX的值减一,直到减为0为止。
指令作用:
将7000:00
至7000:100
之间的内存数据,完整传送到00:8000
至00:8100
之间。
0000015F 1F pop ds
恢复DS寄存器的值,消除本小段代码对寄存器值的破坏
00000160 61 popaw
恢复各通用寄存器的值,消除本小段代码对寄存器值的破坏
00000161 FF265A7C jmp word [0x7c5a]
根据磁盘位置找到这个位置,[0x7c5a]
的值是0x8000
那么就相当于,开始执行在00:8000
处的内核指令了。
至此,MBR引导程序跑完了。
以上MBR引导程序执行过的指令,通过绿色块标识
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)