途中小憩:了解几个数据结构
在继续之前,先了解几个知识点一、BIOS的简短介绍。
1、IBM推出个人计算机,简称:IBM PC。定义了BIOS规范。最初的BIOS由IBM工程师独立完成。后来为了普及IBM PC,IBM 公开了PC内部结构以及BIOS的编程规范,接口并鼓励协助各厂商开发BIOS
2、各BIOS厂商迅速崛起,代表厂商有:AMI、Award、Phoenix等。逐渐发展成为一个小厂商被大厂商吞并的时代。
3、BIOS的基于工作任务:
● POST:开机自检
● 初始化阶段:对DRAM、芯片组,外围设备等设置
● 保存相应数据:在COMS RAM 及 BIOS 数据区保存相应的数据
● 驻入服务例程:写入中断向量表及BIOS的中断服务例程等
二、 中断向量表
1、BIOS启动初期的一个重要工作就是在内存最低端设置相应的BIOS中断向量表,若启动了DOS系统,DOS的系统还负责设置相应的DOS中断向量表,典型的代表是INT 21h服务。
2、中断服务程序获取的算法:
● Interrupt Handler = IDTR.base + vector × vector size
● X86 支持 256(FF)中断。
● 处理器RESET 后处于 16 位real mode,IDTR.base 初化为 0,
因此:整个BIOS 及 DOS 系统的中断向量表位于:0 + 0 × 4 ~ 0 + FF × 16,也就是 0 ~ 3FFh 的物理地址。
3、BIOS及DOS的整个中断向量表布局如下(0 ~ 3FF):
中断号 地址 说明
00 0000 除0错
01 0004 单步执行
02 0008 不可屏蔽中断
03 000C 断点调试
04 0010 溢出中断
05 0014 BIOS打印屏幕中断
06 0018 无效指令错
07 001C 无效浮点指令
08 0020 IRQ0 定时器中断
09 0024 IRQ1 键盘中断
0A 0028 IRQ2 串联次中断控制器
0B 002C IRQ3 COM2
0C 0030 IRQ4 COM1
0D 0034 IRQ5 LPT2
0E 0038 IRQ6 软盘控制器
0F 003C IRQ7 LPT1
10 0040 BIOS 视频服务例程
11 0044 BIOS 外围设备检查
12 0048 BIOS 检测内存大小
13 004C BIOS 磁盘例程
14 0050 BIOS 通信服务例程
15 0054 BIOS 扩展系统例程
16 0058 BIOS 键盘服务例程
17 005C BIOS 打印服务例程
18 0060 ROM BASIC 例程
19 0064 引导系统
1A 0068 BIOS 时间/RTC
1B 006C BIOS Ctrl-Break处理程序
1C 0070 Int 8h调用的计时子例程
1D 0074 视频参数表
1E 0078 软盘驱动器参数表
1F 007C 字符点阵表
20 0080 DOS 程序终止
21 0084 DOS 系统服务例程
22 0088 DOS 程序结束地址
23 008C DOS Ctrl-Break处理程序
24 0090 DOS 程序严重错误处理
25 0094 DOS 读磁盘例程
26 0098 DOS 写磁盘例程
27 009C DOS TSR
28 00A0 DOS 空闲中断
29 00A4 DOS 字符输出
2A 00A8 网络接口
2B ~ 2D 00AC ~ 00B4 保留
2E 00B8 DOS Shell传递参数
2F 00BC DOS 多重功能中断
30 00C0 保留
31 00C4 DOS 保护模式接口
32 00C8 保留
33 00CC 鼠标服务例程
34 ~ 3E 00D0 ~ 00F8 浮点仿真运算拦截码入口
3F 00FC 覆盖管理
40 0100 软盘中断服务
41 0104 硬盘1参数表
42 0108 视频中断(用于EGA)
43 010C EGA 参数表
44 0110 EGA 点阵字符表
45 0114 保留
46 0118 硬盘2参数表
47 ~ 49 011C ~ 0124 保留
4A 0128 CMOS/RTC 报警中断
4B ~ 66 012C ~ 0198 保留
67 019C 扩展内存管理程序
68 ~ 6F 01A0 ~ 01BC 保留
70 01C0 IRQ8 CMOS/RTC 中断
71 01C4 IRQ9 重定向到 Int 0A
72 01C8 IRQ10 PnP设备
73 01CC IRQ11 PnP 设备
74 01D0 IRQ12 PS/2、USB 设备使用
75 01D4 IRQ13 数字协处理器
76 01D8 IRQ14 IDE设备
77 01DC IRQ15 IDE 设备
78 ~ FF 01E0 ~ 03FC 保留
4、关于中断向量表的后续话题
1)中断向量表的重定位
Interrupt Vector Table 的基地址存储在 IDTR寄存器。Processor 复位后,IDTR.base = 0。也即中断向量表在物理位置0地址上。中断向量表的基地址可以通过 LIDT 指令更改。但实模式的DOS系统不作出任何改变。
2)中断向量表的变迁
保护模式的现代操作系统重新对中断向量表进行了定位。Interrupt Vector Table 被 Interrupt Descriptor Table 取代了。前者是存放真实的中断服务程序的入口地址。后者是存放的称为门符的描述符结构。描述符定义了相应的属性及权限。真实的中断服务程序被门符间接索 引。
3)中断向表的变迁
时至今日,中断体系的变迁,保护模式下的中断向量的含义大部分发生了改变。如:13号向量是 #GP(General Protection)异常。发生了如系统数据结构产生违例访问或越权访问等就产生保护异常。
4)旧的中断向量表结构
旧的中断向量表以及中断服务程序依然位于内存的低位。这份由BIOS维护的中断向量表结构只在系统启动初期产生一些作用。成功引导操作系统后,将被 操作系统所抛弃,操作系统将建立属于自己的一套中断向量表以及中断服务程序。典型的是Linux 系统定义了 80h 号向量作为system call 门符。
三、CMOS 数据区域
CMOS 数据区存放一些基本系统数据,如RTC(真实的时间戳),它是一个主板上的一个RAM,为了使CMOS RAM里面的数据不丢失,主板上提供了一个电池供电。
1、CMOS RAM 的访问方式
访问 CMOS RAM 是过I/O 端口在IO Space进行的,CMOS RAM 大小为128 字节,地址范围从0 ~ 7Fh。
● 70h 端口:这个端口是个索引地址端口,通过向这个端口输送一个地址值,这个地址值位于 CMOS RAM里。
地址索引值的结构如下:
Bit7:最高位为 NMI Disable 位。置1则 Disable NMI
Bit6 ~ 0:CMOS RAM 地址索引,范围从 0 ~ 7Fh
● 71h 端口:这个端口是数据端口。从这个端口获取71h端口输送索引地址的内容。
● 72h 端口:这个端口如同71h端口一样,但这个端口可以访问整个256字节空间。也就是它的Bit7是有效索引值。
● 73h 端口:这个端口如同72h 端口一样,获取从72端口输送索引地址的内容。
错误:此处有误,谢谢 zx_wing 指出.
正确为:
● 72h 端口:这个端口如同70h端口一样,但这个端口可以访问整个256字节空间。也就是它的Bit7是有效索引值。
● 73h 端口:这个端口如同71h 端口一样,获取从72端口输送索引地址的内容。
2、CMOS RAM 数据内容
地址索引 含义
00 ~ 09h 相应的系统时间域
0A Bit7:0-时间可读 1-等待更新再读
Bit6 ~ 4:除法器频率
Bit3 ~ 0:时间中断频率
0B Bit7:0-设定时间,但仍在计时状态 1-设定时间,但在停止计时状态
Bit6:同期性中断,0-Disable 1- Enable
Bit5:时间警报中断,0 – Disable 1-Enable
Bit4:1-允许中断在更新时间时 0-Disable
Bit3:方波设定 0 – Disable 1-Enable
Bit2:日期/时间格式 0 – BCD格式 1- Binary格式
Bit1:时间制式 0- 12时制 1- 24时制
Bit0:日光节约时间 0-Disable 1-Enable
0C Bit7:IRQ标志
Bit6:周期性中断标志
Bit5:警报中断标志
Bit4:更新中断标志
Bit3~0:保留
0D Bit7:CMOS RAM内容正常性,0正常 1-异常
Bit6~0:保留,为0
0E Bit7:CMOS/RTC 芯片电源,0正常 1异常
Bit6:CMOS RAM CheckSum 0 正常 1 异常
Bit5:CMOS RAM 配置状态,0配置与检测的一致 1不一致
Bit4:CMOS RAM 内存状态,0记录的内存与检测一致 1不一致
Bit3:硬盘C启动状态 0启动通过可Boot 1失败,无法Boot
Bit2:时间记录 0 正常无误 1记录异常
Bit1~0:保留,为0
0F 00: 软件复位
01:实模式/保护模式下发生RESET或实模下芯片组初始化时发生RESET
02:实模式/保护模式下内存检查通过后RESET
03:实模式/保护模式下内存检查失败后RESET
04:通过INT 19h 重新开机(Boot)
05:清除键盘中断(EOI),跳到40:67记录的位置
06:保护模式下测试成功后RESET跳到40:67记录的位置
07:保护模式下测试失败后RESET
08:由POST切到保护模式下进行内存检测
09:BIOS INT1使用
0A:返回跳到40:67记录的程序入口
0B:IRET回到40:67 记录的程序入口
0C:RET回到40:67记录的程序入口
0D~FF: 上电时初始复位
10h ~ 2Fh ISA系统配置
30h ~ 3Fh BIOS设置
40h ~ 7Fh 芯片组配置
四、BIOS 记录数据区域
1、如下图所示,紧接着中断向量表结构的是BIOS用来记录数据的区域,地址范围:400h ~ 600h 共 512 字节。这个区域被 BIOS 用来检测系统状态而记录使用的区域,以及 BIOS 定义的某些数据结构(例如:键盘缓冲区)。
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onclick="if(!this.resized) {return true;} else {window.open(this.src);}" onmousewheel="return imgzoom(this);" alt="" border="0">
2、在 BIOS 执行初期 BIOS 将检测 BIOS 数据区域的某些数据确定当前机器状态。例如:BIOS 通过检查 [0472] 的内容是否是 1234H 从而确定机器是热启动还是冷启动。
3、BIOS 定义了键盘缓冲区在 01Eh 开始的 32 个字节的。
第三站 跟着流程走(一):
far jmp后发生什么?
接下来用IDA pro打开ex38dq6.BIN观察,这个是BIOS的主体文件,跟着流程走,看看far jmp后BIOS做什么工作。
一、下面是第一张流程图:
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onclick="if(!this.resized) {return true;} else {window.open(this.src);}" onmousewheel="return imgzoom(this);" alt="" border="0">
1、第一条指令 jmp far ptr F000:E05B 经过几个跳转,跳到F000:F46C处
2、以下是F000:F46C的代码:
seg000:FF46C cli
seg000:FF46D cld
seg000:FF46E xchg bx, bx
seg000:FF470 smsw ax
seg000:FF473 test al, 1
seg000:FF475 jz short near ptr 0F480h
seg000:FF477 cli
seg000:FF478 mov al, 0FEh ; '?
seg000:FF47A out 64h, al ; AT Keyboard controller 8042.
seg000:FF47A ; Resend the last transmission
seg000:FF47C cli
seg000:FF47D hlt
--------------------------------------------------------------
取机器状态字,也就是CR0寄存器,测试CR0.PE是否为1,判断CPU是否处于实模式状态,若不是则停机。
若处于实模式转到F000:F480继续处理。
3、转到F000:F480又经过一道跳转,来到F000:E043进行处理
4、下面是F000:E043的代码:
seg000:FE043 mov al, 8Fh ; '? ; disable NMI# and get 0Fh offset register
seg000:FE045 out 70h, al ; CMOS Memory:
seg000:FE045 ;
seg000:FE047 out 0EBh, al
seg000:FE049 in al, 71h ; get OFh offset register data
seg000:FE04B out 0EBh, al
seg000:FE04D or al, al ; is RESET ?
seg000:FE04F jmp near ptr 0F483h
在这里,取 CMOS RAM 中位于0F处1个字节的数据,通过测试这个字节是否为0,判断是否属正常启动。
5、正常启动的话,调用F000:54DE这个子过程进行处理,否则跳到F000:3468。
二、下面看看F000:54DE的处理,以下是第二张流程图:
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onclick="if(!this.resized) {return true;} else {window.open(this.src);}" onmousewheel="return imgzoom(this);" alt="" border="0">
下面proc_F54DE的代码:
seg000:F54DE mov ax, 0
seg000:F54E1 mov es, ax
seg000:F54E3 cmp word ptr es:472h, 1234h
seg000:F54EA jnz short near ptr 54F8h
seg000:F54EC mov al, 8Fh ; '?
seg000:F54EE out 70h, al ; CMOS Memory:
seg000:F54EE ;
seg000:F54F0 out 0EBh, al
seg000:F54F2 mov al, 0AAh ; '?
seg000:F54F4 out 71h, al ; CMOS Memory:
seg000:F54F4 ;
seg000:F54F6 out 0EBh, al
seg000:F54F8 mov dx, 3C4h
seg000:F54FB mov al, 1
seg000:F54FD out dx, al ; EGA: sequencer address reg
seg000:F54FD ; clocking mode. Data bits:
seg000:F54FD ; 0: 1=8 dots/char; 0=9 dots/char
seg000:F54FD ; 1: CRT bandwidth: 1=low; 0=high
seg000:F54FD ; 2: 1=shift every char; 0=every 2nd char
seg000:F54FD ; 3: dot clock: 1=halved
seg000:F54FE inc dl
seg000:F5500 in al, dx ; EGA port: sequencer data register
seg000:F5501 or al, 20h
seg000:F5503 out dx, al ; EGA port: sequencer data register
seg000:F5504 call near ptr 76FBh
seg000:F5507 retn
1、在BIOS数据区的0472处存放着一个复位标志:
seg000:F54E3 cmp word ptr es:472h, 1234h
通过比较 [0472] 是否1234h,标志1234h是一个热启动标志位,机器热启动时,例如:按下CTRL+ALT+DEL 三个键时,由键盘中断处理程序在[0472]处写标志1234h。
2、是热启动的话,将写入AA标志到CMOS RAM 的0F处。
3、接着设置EGA相应的工作状态。
4、在proc_F76FB过程里置定时器1的状态。
5、最后调用过程proc_F2941进行芯片组的初始化。
三、下面是本站节的重点,初始化某部分芯片组,下面是流程图:
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL+Mouse wheel to zoom in/out';}" onclick="if(!this.resized) {return true;} else {window.open(this.src);}" onmousewheel="return imgzoom(this);" alt="" border="0">
1、下面重点理解 write_pci_byte这个BIOS提供的rontine,代码如下:
seg000:FF798 xchg ax, cx ; write_byte routine
seg000:FF799 shl ecx, 10h
seg000:FF79D xchg ax, cx
seg000:FF79E mov ax, 8000h ; Bus 0
seg000:FF7A1 shl eax, 10h
seg000:FF7A5 mov ax, cx
seg000:FF7A7 and al, 0FCh
seg000:FF7A9 mov dx, 0CF8h ; config_address register
seg000:FF7AC out dx, eax
seg000:FF7AE add dl, 4 ; config_data register
seg000:FF7B1 mov al, cl
seg000:FF7B3 and al, 3
seg000:FF7B5 add dl, al
seg000:FF7B7 mov eax, ecx
seg000:FF7BA shr eax, 10h
seg000:FF7BE out dx, al
seg000:FF7BF retn
将这个routine功能简化为C代码形式来看比较直观:
void wirte_pci_byte(int offset_number, int mask)
{
if (number == -1)
jmp_7666();
do_wirte_pci_byte(offset_number, mask);
}
这段routine固定写PCI的Bus0,Device0,Function0,offset 值放在cx中,由调用者传来,置什么值放在al寄存器,这是1个字节的值。
Bus0,Dev0,Fun0是hostbrige控制器(NorthBridge),也即是DRAM控制器的地址所在。这段代码是典型的写PCI设置的手法。PCI设置地址送入config_address_register中,然后往config_data_register里写数据,这个PCI设备地址将映射到PCI设备的寄存器,如前面介绍的地址空间图所示,PCI设备地址范围是E000_0000 ~ EFFF_FFFF,这段空间提交到相应的PCI设备。
2、现在回过头来看调用者,cx=95,al=33 这个参数传给 write_pci_byte。Offset是95,mask码是33。Offset 95在write_pci_byte将被置为94,这将是DRAM控制器的PAM4寄存器,PAM4寄存器控制D_8000 ~ D_FFFF内存空间的属性。写入33,结果是:将这段空间置为read/write属性,这将是所有访问这段空间的操作会提交到DRAM。而不再是ROM。
3、Offset 96的结果和offset 95一样,在write_pci_byte的掩码中被置为offset 94
没有评论:
发表评论