2009年3月31日 星期二

●DJGPP和保護模式

本文轉貼於http://hengch.blog.163.com/
DOS不能工作在保護模式
讓CPU工作在保護模式很容易,但CPU工作在保護模式時你無法調用DOS和BIOS服務,為什麼呢?因為DOS和BIOS的代碼是按照在真實模式下運行的方式寫的,不符合保護模式程式的規範,比如,在真實模式下,DOS下的程式碼可以把任意數值放到段寄存器(CS、DS、ES、SS)中,只要不超過64K就可以,但在保護模式下,段寄存器只能放一個已經存在的selector的值,任何其它的值都將引起一個“General Protection Fault”錯誤異常。
所以,如果程式讓CPU進入保護模式,並且調用DOS服務,比如列印一行資訊,將馬上使系統崩潰,如果你不明白這一點,恐怕連一個最簡單的“Hello World”程式都寫不出來。
更糟糕的是,雖然應用程式不能調用DOS和BIOS服務,但DOS和BIOS卻必須要運行,比如時鐘晶片產生的硬體中斷,每秒18.2次的時鐘中斷等,時鐘中斷是BIOS的一部分,工作在真實模式。
所以,哪怕程式不調用任何真實模式代碼,一些非同步的系統事件仍然會發生,機器仍然會很快崩潰,所以,要在DOS下進入保護模式,必須首先解決DOS/BIOS和保護模式之間的這種衝突。
DOS Extender允許DOS和保護模式共存
如果你不想寫一個保護模式下的作業系統來完全取代DOS和BIOS,解決衝突的辦法是在你的應用程式和DOS/BIOS之間加入一個軟體層,這個軟體層可以視情況讓CPU在真實模式和保護模式之間切換,這個軟體層叫做DOS Extender。
在DOS Extender下,當保護模式下的程式要調用真實模式下的服務時,Extender給這個調用設置一個陷阱,把CPU切換到真實模式,再重新發出 這個調用,等待其完成調用,然戶再切換回保護模式,並返回到那個調用真實模式代碼的應用程式中,像時鐘中斷、鍵盤這樣的硬體中斷,也會被Extender設 置一個陷阱,並產生保護模式到真實模式的切換和返回。
你可能會想,這種方式會使應用程式運行變的很慢,然而在實際應用中,大多數程式並不會頻繁地調用OS的服務,即使調用很多,由於大多數的OS服務都是存取外部設備,比如硬碟,而這些設備的速度比起CPU而言是非常慢的,所以很少有人注意到模式切換上的系統開銷。
DJGPP v1.X和go32 Extender
在DJGPP v1.x中使用的go32程式,就是這樣一個DOS Extender,每個程式啟動時都會自動地裝載go32,go32除了完成DOS Extender的任務外,還接管下面一些與DJGPP相關的任務。
• 裝載應用程式,並為運行做好準備
由於DJGPP的執行檔使用COFF格式,DOS看不懂這種格式,go32負責讀取 COFF頭並初始化代碼、資料和其它檔頭中的分段
• 提供UNIX形式的命令列擴展
• 保護模式下浮點運算模擬
• 圖形支援
go32中有一些特殊的代碼,使其能夠適應各種各樣的進入保護模式的方法並管理擴展記憶體,所以他可以工作在任何DOS配置下,但這也使其產生一些不能忽視的缺陷,就是Extender必須被裝入常設記憶體,並且每個程式實例大約要佔用130KB的記憶體,大多數情況下DOS啟動以後都會有500-600K剩餘的常設記憶體,這意味著一個DJGPP程式只能有3-4級的嵌套。這是一個很嚴重的限制,DJGPP v2.x已經解決了這個問題。
DJGPP v2.X與DPMI服務
DJGPP v2.x放棄了Extender,取而代之的是需要一個已經運行的DPMI服務,DPMI:DOS Protected-Mode Interface的縮寫,是一個特殊的API,它允許保護模式的應用程式在DOS的上層運行,他定義了 一些函數,使保護模式下的程式(稱為DPMI客戶)可以做一些諸如:進入保護模式、分配記憶體和段描述符、調用真實模式的服務、連接中斷等等,許多使用 Intel CPU的作業系統都有DPMI服務,包括windows的所有版本,OS/2,以及LINUX DOS模擬器都是著名的例子,還有一些持有專 利的DOS下的DPMI伺服器,通常和DOS的記憶體管理器捆綁在一起,比如QEMM和386MAX,FreeDOS 也包含有一個DPMI伺服器作為缺省設置的一部分,對於那些沒有DPMI伺服器的系統,DJGPP v2.x提供一個免費的DPMI伺服器,叫做 CWSDPMI,CWSDPMI很多地方使用了go32的代碼,DJGPP啟動代碼檢查DPMI服務,如果沒有,將自動搜索並載入 cwsdpmi.exe----CWSDPMI伺服器。
DPMI伺服器(又稱為DPMI HOST)可以解決運行在DOS上層的保護模式程式的大部分問題,餘下的那些在1.x版本中由go32完成的函數,在v2.x中被DJGPP的啟動代碼接管並放在低級函式程式庫中,下面簡短地說一下DJGPP啟動代碼的兩個特點。
DJGPP v2.x的啟動代碼
DJGPP v2.x啟動代碼包括兩部分:短小的裝載程式和庫啟動模組,前者是由組合語言寫成,是一段有特殊作用的組合語言程式,叫做djasm,它是一段 16-bit的DOS可執行程式,這個短小的裝載程式會被連結到每一個DJGPP程式中,是唯一一部分可以被DOS識別的部分,其餘部分----COFF 可執行文檔----在DOS看來只是一些奇怪的資料。
第二部分是一個庫模組,它包括許多模組,有些用C寫成,有些用彙編寫成,當裝載程式把程式調入並初始化完成後,這裡就是COFF格式程式的入口點。
裝載程式完成以下工作:
• 為傳輸緩衝區申請記憶體
這個緩衝區用於在DOS服務和程式之間傳送資料。
• 檢查是否DPMI服務已經運行
以下兩種情況說明DPMI已經啟動
(1)有一個常駐記憶體的DPMI伺服器,比如windows中內置的DPMI伺服器
(2)當前程式是一個嵌套的DJGPP程式,他的父程式已經啟動了CWSDPMI
如果DPMI服務還沒有啟動,則裝入CWSDPMI。首先在目前的目錄下搜索cwsdpmi.Exe, 然後到環境變數PATH指定的目錄下去查找。
• 把COFF可執行部分的檔頭裝入記憶體
需要知道要為DJGPP程式申請多少記憶體
• 調用DPMI host提供的入口點,把CPU切換到保護模式
注意:裝入程式的其餘部分運行在保護模式下
• 為程式碼和資料申請記憶體空間
通過DPMI的功能調用可以為代碼和資料申請段描述符和記憶體空間,並設置基底位址、 界限和許可權。
• 把COFF格式的可執行部分調入記憶體
通過DPMI服務調用DOS(主要指檔操作)服務,把代碼、資料和BSS節讀入上 面申請的記憶體中,DPMI服務允許你從保護模式下調用真實模式下的服務。
• 跳轉到COFF鏡像的入口點執行
這個入口點在上面提到過的庫啟動模組中。
下面是庫啟動代碼部分完成的工作
• 生成一個不受約束的空頁
這將產生一個NULL Pointer dereference的錯誤,並將觸發一個異常處理,程式 會收到SIGSEGV信號,但這個功能並非基本DPMI 0.9規範中的一部分,所以 windows和其它許多有專利的DPMI伺服器並不支持這個功能,但CWSDPMI支援這 一功能。
• 改變申請記憶體的資料段的大小
這個聽起來簡單,但由於DPMI記憶體調用的特殊性,實際上是非常複雜的,例如: 它需要把一段真實模式下的16-bit代碼調入常設記憶體的緩衝區並運行。
• 設置程式的堆疊
DJGPP程式堆疊的缺省大小是512KB,但應用程式或改變設置都可以改變堆疊大小
• 為存取常設記憶體申請selector
與DOS/BIOS函數之間傳遞資料,或者像視頻界面上的顯示緩衝區那種使用記憶體 映射的設備,許多DOS程式需要存取常設記憶體,但由於在缺省情況下,常設記憶體並 沒有映射在程式的資料段中,為了存取常設記憶體,使用了一個特殊的selector---- _dos_ds。
• 初始化信號管理
需要連結一些硬體中斷,例如:按下CTRL-時產生的SIGINT信號。還有時鐘中 斷產生的SIGPROF信號等。
• 拷貝程式的環境變數到environ[]陣列中
• 讀出定義了DJGPP附加環境變數的檔
• 獲得並解釋命令列參數
• 如果需要,設置x87 FPU並載入浮點運算模擬器
• 調用靜態構造函數
• 調用用用程式的主函數

博客心情

博客大陸人稱呼部落格為博客,不知為何寫博客會上癮,可能是我這ㄍ宅男實在太無趣ㄌ,所以才透過文字抒發心情,然而每次寫完一篇就會覺得心情好過些,就好像每天早上ㄉ那杯咖啡,我目前的工作整天就是在跟記憶體測試軟體打交道,雖說不上心得滿滿,但從剛開始的牙牙學語,到現在一年半載,至少該懂的應該也有七八成,Debug也成為我的嗜好之一,一開始抱持著以前寫ap都搞不懂(應該說領域不同,所以沒機會接觸更深入)的,這回有機會一定要以頃城之力來回報自己內心的不踏實。雖然我很想跳出這個dram產業去接觸更多‧‧‧,但其實不那麼迫切,因為我覺的自己還有成長的空間,學習x86這一塊,讓我感觸良多,畢竟這是整個IT產業的最大宗,然而我相信搞好這一塊,要跳到其它嵌入式系統應該熟悉度會加快。雖說技術一日千里,但只要你夠耐心及細心,我想你也可以日進萬里(內心ㄉ成長)。

●中斷、保護模式、分頁‧‧真是夠ㄌ___PART2


以下ㄉCODE是截取"自己動手寫作業系統",其中還有其他範例您就自己捨得花錢買一本吧。買這種書不會讓你看ㄌ一兩年後會過時ㄉ感覺 ,所以買ㄅ。 用其他顏色是我ㄉ註解。
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN

[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 32
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
; GDT 结束
;以上3個描述子請參閱pm.inc你才能真的懂。第一ㄍ為空描述子,一定要ㄉ,什麼?誰規定ㄉ,當然是intel
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限;由這ㄍ界定的定義dw就知道gdt表最多可以容納8192個描述子:dw=2ㄉ16次方=65536/8=8192
dd 0 ; GDT基地址

; GDT 选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs;程式ㄉ啟始點
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h;因為是com檔所以是100h

; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah

; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
;lgdt作ㄌ什麼事ㄚ(就是將GdtPtr這個標籤所指ㄉ6ㄍbytesㄉ資料載入到gdtr暫存器;待進入保護模式,段式保護機制就生效ㄌ,從此過這和以前一樣ㄉ日子,只要和ds es fs gs打交道就可以ㄌ。
; 关中断
cli
; 打开地址线A20;a20位址線問題我已經有另闢文書說明請自行參閱
in al, 92h or al, 00000010b
out 92h, al
; 准备切换到保护模式;短短3行你就進入保護模式。簡單ㄅ
mov eax, cr0 or eax, 1 mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0
; 执行以上這一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0處;請仔細品嘗這ㄍ註解1000遍,直到你真的懂為止。
; END of [SECTION .s16]
[SECTION .s32]
; 32 位代码段. 由实模式跳入.
[BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax
; 视频段选择子(目的)
mov edi, (80 * 10 + 0) * 2
; 屏幕第 10 行, 第 0 列。 mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, 'P' mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32 ;
END of [SECTION .s32]
;==========================================
; pm.inc 有些描述必須參考上圖
;==========================================
(1) P: 存在(Present)位。 ; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; ; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 ; ; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 ; ; (3) S: 说明描述符的类型。 ; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 ; ; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。 ; ; ; 数据段类型 类型值 说明 ;
; 0 只读 ; 1 只读、已访问 ; 2 读/写 ; 3 读/写、已访问 ; 4 只读、向下扩展 ; 5 只读、向下扩展、已访问 ; 6 读/写、向下扩展 ; 7 读/写、向下扩展、已访问 ; ; ; 类型值 说明 ; 代码段类型 ; 8 只执行 ; 9 只执行、已访问 ; A 执行/读 ; B 执行/读、已访问 ; C 只执行、一致码段 ; D 只执行、一致码段、已访问 ; E 执行/读、一致码段 ; F 执行/读、一致码段、已访问 ; ; ; 系统段类型 类型编码 说明 ;; 0 <未定义>
; 1 可用286TSS
; 2 LDT
; 3 忙的286TSS
; 4 286调用门
; 5 任务门
; 6 286中断门
; 7 286陷阱门
; 8 未定义
; 9 可用386TSS
; A <未定义>
; B 忙的386TSS
; C 386调用门
; D <未定义>
; E 386中断门
; F 386陷阱门
;
; (5) G: 段界限粒度(Granularity)位。
; G=0 表示界限粒度为字节;
; G=1 表示界限粒度为4K 字节。
; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
;
; (6) D: D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。
; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。
; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;
; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。
; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。
; ① D=1表示段的上部界限为4G;
; ② D=0表示段的上部界限为64K,这是为了与80286兼容。
; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。
; ① D=1表示使用32位堆栈指针寄存器ESP;
; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。
;
; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; DA_ : Descriptor Attribute
; D : 数据段
; C : 代码段
; S : 系统段
; R : 只读
; RW : 读写
; A : 已访问
; 其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32 EQU 4000h ; 32 位段

DA_DPL0 EQU 00h ; DPL = 0
DA_DPL1 EQU 20h ; DPL = 1
DA_DPL2 EQU 40h ; DPL = 2
DA_DPL3 EQU 60h ; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
DA_DR EQU 90h ; 存在的只读数据段类型值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值
DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值
DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
;----------------------------------------------------------------------------
; 系统段描述符类型值说明
;----------------------------------------------------------------------------
DA_LDT EQU 82h ; 局部描述符表段类型值
DA_TaskGate EQU 85h ; 任务门类型值
DA_386TSS EQU 89h ; 可用 386 任务状态段类型值
DA_386CGate EQU 8Ch ; 386 调用门类型值
DA_386IGate EQU 8Eh ; 386 中断门类型值
DA_386TGate EQU 8Fh ; 386 陷阱门类型值

; RPL(Requested Privilege Level): 请求特权级,用于特权检查。
;
; TI(Table Indicator): 引用描述符表指示位
; TI=0 指示从全局描述符表GDT中读取描述符;
; TI=1 指示从局部描述符表LDT中读取描述符。
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; SA_ : Selector Attribute

SA_RPL0 EQU 0 ; ┓
SA_RPL1 EQU 1 ; ┣ RPL
SA_RPL2 EQU 2 ; ┃
SA_RPL3 EQU 3 ; ┛

SA_TIG EQU 0 ; ┓TI
SA_TIL EQU 4 ; ┛

; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限 1 (2 字节)
dw %1 & 0FFFFh ; 段基址 1 (2 字节)
db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节)
db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节)
%endmacro ; 共 8 字节

; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 <<>> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
/*****************************************************/
以下我們來看memtest86+如何進入保護模式:請自行參閱head.S,用/****/隔開表示代碼為不連續性。
#define RSTART startup_32
/* Fixup the gdt_descr */
leal gdt@GOTOFF(%ebx), %eax
movl %eax, 2 + gdt_descr@GOTOFF(%ebx)
/*****************************************************/
/* Load the global descriptor table */
addr32 lgdt gdt_descr - RSTART //
addr32這ㄍ標紀是什麼意思我還沒查到,但可以確定ㄉ一點是現在早已在保護模式,因為setup.S時就切進去ㄌ。
/*****************************************************/
gdt:
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x0000000000000000 /* not used */
.quad 0x00cf9a000000ffff /* 0x10 main 4gb code at 0x000000 */
.quad 0x00cf92000000ffff /* 0x18 main 4gb data at 0x000000 */

.word 0xFFFF # 16bit 64KB - (0x10000*1 = 64KB)
.word 0 # base address = SETUPSEG
.byte 0x00, 0x9b # code read/exec/accessed
.byte 0x00, 0x00 # granularity = bytes


.word 0xFFFF # 16bit 64KB - (0x10000*1 = 64KB)
.word 0 # base address = SETUPSEG
.byte 0x00, 0x93 # data read/write/accessed
.byte 0x00, 0x00 # granularity = bytes

gdt_end:

●First Instruction Executed




剖析First Instruction Executed
一、Processor 寄存器初始狀態,如上圖。
1、CR0.PE = 0,處理器處於真實模式
2、RIP = FFF0h
3、CS.Base = FFFF_0000h
4、由於在真實模式下,第一條指令的物理位址就在:CS.Base + RIP = FFFF_FFF0h。

二、MCH(NorthBridge)的處理
FFFF_FFF0h位址由Processor送到MCH進行解析,這個位址落在MCH固定為High BIOS 分配的區域中,MCH直接通過DMI介面送到ICH進行處理。MCH相當於一個派發者角色,根據定義的位址映射分派到不同的介面。

三、ICH(SouthBridge)的處理
1、ICH對MCH提交過來的位址進行解碼。
2、ICH的位址映射機制中:FFFF_FFF0地址落 FFF8_0000 ~ FFFF_FFFFh這個範圍中,這個範圍的位址將直接分派到LPC介面的Frimware Hub進行處理,LPC 介面相當於一個PCI-to-ISA 橋,BIOS寄居在LPC bus上,從而訪問BIOS區域。
3、在物理器件上,ICH Firmware Hub的IDSEL選擇固定位址設為FFF8_0000 ~ FFFF_FFFF範圍,LPC介面寄存器(B#0, D#30,F#0)中的,Firmware Hub Decode Enable寄存器的bit15固定為1,允許FFF8_0000 ~ FFFF_FFFF位址提交到Firmware Hub。這樣確保FFFF_FFF0提交到LPC bus。
ICH相當於一個解碼器角色,解碼後分配任務給設備執行。

四、第一條指令執行
經典地在FFFF_FFF0絕大多數是一條far jmp指令:jmp far ptr 0F000h:0E05Bh,不同的BIOS跳轉的位址或許不同。這條指令跳轉到FE05B這個位址上執行。同時刷新CS.Selector、CS.Base以及EIP寄存器。引一段 Intel 的話:

The first instruction that is fetched and executed following a hardware reset is
located at physical address FFFFFFF0H. This address is 16 bytes below the
processor’s uppermost physical address. The EPROM containing the software initialization
code must be located at this address.
The address FFFFFFF0H is beyond the 1-MByte addressable range of the processor
while in real-address mode. The processor is initialized to this starting address as
follows. The CS register has two parts: the visible segment selector part and the
hidden base address part. In real-address mode, the base address is normally
formed by shifting the 16-bit segment selector value 4 bits to the left to produce a
20-bit base address. However, during a hardware reset, the segment selector in the
CS register is loaded with F000H and the base address is loaded with FFFF0000H. The
starting address is thus formed by adding the base address to the value in the EIP
register (that is, FFFF0000 + FFF0H = FFFFFFF0H).

以上這段文字表明,Intel從架構上規定了,processor復位後,FFFFFFF0H地址上必須包含一些初始化程式,BIOS必須在定位在這段位址區域上。
MCH與ICH的配合從物理上通過位址映射機制保證了這一點的實施。

五、驗證一下FFFF_FFF0位址上是否是一條far jmp指令,可以簡單地打開cmd視窗執行debug命令。
C:>debug
-d F000:FFF0
F000:FFF0 EA 5B E0 00 F0 30 32 2F 32 37 2F 30 38 00 FC
EA 5B E0 00 F0 就是 jmp far ptr F000:E05B 指令,這條指令跳轉到FE05B的物理位址上,這將是 BIOS 的 BOOTBLOCK。

六、BIOS位址空間高端與低端的別名機制

1、在整個位址空間中 FFE0_0000 ~ FFFF_FFFF 這段2M 區域真正地被固定分配給 BIOS 使用,包括基本的 BIOS 區以及一些設備擴展的 BIOS 區。這段空間是不能提交至 DRAM 的。

2、實際上,BIOS的低端地址C_0000 ~ F_FFFF 屬於PAM區 (即:Programmed Attibute Memory) ,這段區域可被賦為4個訪問屬性:disable,read-only,write-only 以及 read/write。根據訪問屬性的不同可被提交到ICH,也可以提交DRAM,但初始化的屬性是Disable,為DRAM不可用,初始狀態是通過DMI提交到ICH處理的,但是可以在 BIOS 代碼修改的,從而提交至DRAM。

3、C_0000 ~ F_FFFF 與 FFFC_0000 ~ FFFF_FFFF 指向同一個區域,被冠以別名稱呼,即C_0000 ~ F_FFFF 被映射到 FFFC_0000 ~ FFFF_FFFF。

AMD 如是說:參閱圖4
Accesses to BIOS space in the low megabyte (between 000C_0000h and 000F_FFFFh) are mapped to the top megabyte (between FFFC_0000h and FFFF_FFFFh) on the LPC bus; the OAR locks for these apply to these accesses based on the remapped address at the top megabyte.

●無論是C_0000 ~ F_FFFF,還是 FFFC_0000 ~ FFFF_FFFF 最終結果都將送到LPC bus上的FFFC_0000 ~ FFFF_FFFF 地址上。參閱圖5

2009年3月30日 星期一

●分解BIOS

注意:本網站討論的部分內容不詳,還沒瞭解透,定義為:不清昕,可能有錯誤的。

這裡主要以Intel平臺的BIOS檔討論,輔助參考AMD平臺的BIOS文件。要分解的BIOS檔選了當下較新的X38晶片組平臺的ex38dq6.f2 這個BIOS檔,而BIOS的檔是ma79xds4.f4,這是AMD的最新的7系晶片組其中的790X晶片組平臺。好,下面開始進行分解ex38dq6.f2這個BIOS檔。

一、工具的使用
1、Ex38dq6.f2是Award Bios,有一個圖形化的BIOS編輯軟體awdbedit,可以很方便的將BIOS的元件分解出來。
2、通用的BIOS編輯軟體cbrom,這裡使用的是cbrom182版本。下面是使用cbrom182顯示BIOS元件的清單,命令列下使用:Cbrom182 ex38dq6.f2 /D,結果如下(部分):

******** ex38dq6.f2 BIOS component ********
No. Item-Name Original-Size Compressed-Size Original-File-Name
================================================================================
0. System BIOS 20000h(128.00K)15478h(85.12K)ex38dq6.BIN
1. XGROUP CODE 0FC40h(63.06K)0B0ECh(44.23K)awardext.rom
2. ACPI table 04E16h(19.52K)0193Ch(6.31K)ACPITBL.BIN
3. EPA LOGO 0168Ch(5.64K)0030Dh(0.76K)AwardBmp.bmp
4. GROUP ROM[18] 031D0h(12.45K)0225Ah(8.59K)ggroup.bin
5. YGROUP ROM 0C180h(48.38K)066E4h(25.72K)awardeyt.rom
6. GROUP ROM[ 0] 08210h(32.52K)0303Dh(12.06K)_EN_CODE.BIN
7. PCI ROM[A] 10000h(64.00K)09DBEh(39.44K)ICH9RAID.BIN
8. PCI ROM 03600h(13.50K)02553h(9.33K)ICH8AHCI.BIN
9. PCI ROM[C] 07A00h(30.50K)04479h(17.12K)JMB59.BIN
10. MINIT 08220h(32.53K)0824Fh(32.58K)DDR2_MRC.X38
11. PCI ROM[D] 0C800h(50.00K)079FDh(30.50K)rtegrom.lom
12. LOGO1 ROM 00B64h(2.85K)00520h(1.28K)dbios.bmp
13. LOGO BitMap 4B30Ch(300.76K)07EEEh(31.73K)x48dq6.bmp
14. GV3 01EFDh(7.75K)00B66h(2.85K)PPMINIT.ROM
15. OEM0 CODE 028ABh(10.17K)01E1Bh(7.53K)SBF.BIN
(SP) NCPUCODE 1D000h(116.00K)1D000h(116.00K)NCPUCODE.BIN

Total compress code space = E5000h(916.00K)
Total compressed code size = 75C8Dh(471.14K)
Remain compress code space = 6F373h(444.86K)

清單: 2.1

整個ex38dq6.f2 檔1M大小,包含了16個元件,最後的NCPUCODE.BIN元件,是虛擬的或者說物理上不存在,用awdbedit軟體分解不包括這個元件,實際上只有15個真實元件,這些元件全都是經過壓縮的。第2列是元件的名字,第3列是元件真實的大小,第4列是元件中部分壓縮的資料在ex38dq6.f2檔中的大小,最後1列是分解後元件存在磁片上的物理檔案名,以ex38dq6.bin為例,這個元件真實的大小為128K,其中85.12K是壓縮部分,其餘的以純代碼形式分佈在FE000 ~ FFFFF區域,典型地:第一條far jmp就分佈在這個區域。
ex38dq6.BIN 是BIOS的主體組件。
awardext.rom、awardeyt.rom 是BIOS的擴展部分。
ACPITBL.BIN 是供ACPI所使用的低級部件,可供作業系統使用。
PCI ROM 是 PCI 設備的一些元件。
還有一些顯示的BMP圖片
其餘組件不詳,有待瞭解
3、使用cbrom182來分解元件的方法:
Cbrom182 ex38dq6.f2 /XGROUP extract 分解出 awardext.rom
Cbrom182 ex38dq6.f2 /ACPI extract 分解出 ACPITBL.BIN
如此類推,可以逐步分解出各個元件,但是,SYSTEM BIOS元件,也即是 ex38dq6.bin 這個元件,我怎麼試也沒分解出來,所以用以下推薦的方法分解。

4、推薦分解BIOS元件的方法
使用圖形化的BIOS編輯軟體awdbedit可以很方便簡單分解全部的元件。運行awdbedit軟體,打開ex38dq6.f2,忽略掉一些警告資訊,進入後,選擇 [Actions] –> [Extract All] 就可以分解出全部的元件。

二、BIOS元件位置分析
1、ex38dq6.f2檔共1M大小,除了包含各個BIOS元件外,還充斥著大量的“填充碼”,這些“填充碼”是FF位元組以及00位元組,主要用來分隔各個元件,以及填充檔。
2、壓縮元件是以LZH形式壓縮,每個壓縮元件以“-lh5-”開頭,十六進位碼形式為 2D 6C 68 35 2D,這是壓縮組件的戳記,因此,在BIOS檔中只要尋找到這個戳記就可以區分開每個元件。
seg000:0000 24 F7 2D 6C 68 35 2D 50 54 01 00 00 00 02 00 00 $?lh5-PT ... ..
seg000:0010 00 00 50 20 01 0B 65 78 33 38 64 71 36 2E 42 49 ..P
ex38dq6.BI
seg000:0020 4E 24 D3 20 00 00 2D 20 8F 77 BF 74 89 29 BB AA N$?..- 弚縯?華
seg000:0030 7F 33 33 37 37 4D 07 73 55 45 55 78 35 91 D5 66 3377MsUEUx5懻f
seg000:0040 85 B7 54 49 34 52 21 0E 9B A5 10 91 11 BC 1D 28 叿TI4R!
洢 ??(
seg000:0050 B1 2A 66 A0 DD 5B BB BA 9C 0D 51 0C C5 17 AA F2 ?f犦[緩?Q
?
seg000:0060 FB DD BC AC AD 34 F1 55 DB 53 CC 03 DD A6 86 30 棘?馯跾?葒?
seg000:0070 2A CF 42 B5 DC 53 52 22 43 F0 75 84 66 40 00 77 *螧弟SR"C饀刦@.w
seg000:0080 7F FE 66 83 37 77 79 E7 9E BC F6 FF BD 7A FD EE �?wy鐬薦�絲
seg000:0090 BE FF 04 3E F7 76 B2 49 1B 6D C9 D1 4B 2D 15 A0 ? >鱲睮 m裳K- ?
seg000:00A0 AE 84 C4 52 58 5F FF CF ED 24 AC C1 42 64 1F F0 畡腞X_�享$Bd­?
seg000:00B0 BF 45 55 49 0A A2 CE C2 97 58 58 AF 0E 62 22 84 縀UI⑽聴XX?b"?
seg000:00C0 7E CF 94 2F E7 24 F7 E3 CE 0F 55 B8 E0 94 E0 D5 ~蠑/?縻?U膏斷?
seg000:00D0 D1 BE E0 9E FB 99 C1 F8 3B 86 C5 B8 86 6C 6B 85 丫酁麢柳;喤竼lk?
seg000:00E0 88 B3 F7 05 5A F0 BA CB C3 2E 5F 89 F8 AF ED B2 埑?Z鷙嗣._夬?
seg000:00F0 91 9C 42 50 B7 CA 60 34 B6 4A 55 8C 65 D3 8E EA 憸BP肥`4禞U宔訋?
seg000:0100 6A 5D E1 4F 7E DB 97 7F 4C A0 AE 9E 15 B7 8E 86 j]酧~蹢L牣?穾?

清單 2.2

3、以ex38dq6.f2為例,用十六制編輯軟打開,從00000000開始到000FFFFF共1M的大小,每個壓縮元件在ex38dq6.f2的物理位置如下:
0. 0 ~ 15477: System Bios (ex38dq6.bin)
1.15478 ~ 20563: XGROUP CODE(awardext.rom)
2.20564 ~ 21E9F: ACPI table(ACPITBL.BIN)
3. 21EA0 ~ 221AC: EPA LOGO(awardBmp.bmp)
4.221AD ~ 24406: GROUP ROM[18](ggroup.bin)
5.24407 ~ 2AAEA: YGROUP ROM(awardeyt.rom)
6.2AAEB ~ 2DB27: GROUP ROM[0](_EN_CODE.BIN)
7.2DB28 ~ 378E5: PCI ROM[A](ICH9RAID.BIN)
8.378E6 ~ 39E38: PCI ROM(ICH8AHCI.BIN)
9.10. 39E39 ~ 46500: PCI ROM[C]、MINIT(JMB59.BIN、DDR2_MRC.X38)
11.46501 ~ 4DEFD: PCI ROM[D](rtegrom.lom)
12.4DEFE ~ 4E41D: LOGO1 ROM(dbios.bmp)
13.4E41E ~ 5630B: LOGO BIGMAP(X48DQ6.bmp)
14.5630C ~ 56E71: GV3(PPMINIT.ROM)
15.56872 ~ 58C8C: OME0 CODE(SBF.BIN)

以上是各個壓縮元件在BIOS檔中的物理位置,從58C8D ~ FDFFF 這段區間中混合著一些資料,還在大量充斥著“填充碼”,沒有什麼實際的意義,或者說:沒看到什麼實際意義。從 FE000 ~ FFFFF 這段區間中,包含一些非壓縮的純二進位碼,其中有重要的BOOTBLOCK,以及一些初始化代碼。也包含著大量的“填充碼”。這些純代碼分散分佈在這個區間,純代碼與部分壓縮元件的混合在一起,幾乎很難區分哪些是純代碼,哪些是壓縮資料,指令:jmp far ptr 0F000:0E05B 與其它資料混合在一起,如下清單2.2所示:
seg000:FFB25 db 0C3h ; ?
seg000:FFB26 db 66h ; f
seg000:FFB27 db 0EFh ; ?
seg000:FFB28 db 8Bh ; ?
seg000:FFB29 db 0D7h ; ?
seg000:FFB2A db 8Eh ; ?
seg000:FFB2B db 0D9h ; ?
seg000:FFB2C ; ---------------------------------------------------------------------------
seg000:FFB2C jmp far ptr 0F000h:0E05Bh
seg000:FFB2C ; ---------------------------------------------------------------------------
seg000:FFB31 db 0
seg000:FFB32 db 0
seg000:FFB33 db 0
seg000:FFB34 db 0
seg000:FFB35 db 0
seg000:FFB36 db 0
seg000:FFB37 db 0
seg000:FFB38 db 0
seg000:FFB39 db 0

清單 2.3

4、BIOS的主體文件 ex38dq6.BIN的大小是128K,正好映射到系統位址空間的FFFE_0000 ~ FFFF_FFFF(E_0000 ~ F_FFFF)共128K的空間上。
當分解出BIOS主體元件ex38dq6.BIN後,這條指令就在F000:FFF0 的位置上,如下清單2.3所示:

seg000:FFFEC db 80h ; €
seg000:FFFED db 1
seg000:FFFEE db 0Ch
seg000:FFFEF db 89h ; ?
seg000:FFFF0 ; ---------------------------------------------------------------------------
seg000:FFFF0 jmp far ptr 0F000h:0E05Bh
seg000:FFFF0 ; ---------------------------------------------------------------------------
seg000:FFFF5 db 30h ; 0
seg000:FFFF6 db 32h ; 2
seg000:FFFF7 db 2Fh ; /
seg000:FFFF8 db 32h ; 2
seg000:FFFF9 db 37h ; 7
seg000:FFFFA db 2Fh ; /
seg000:FFFFB db 30h ; 0
seg000:FFFFC db 38h ; 8
seg000:FFFFD db 0
seg000:FFFFE db 0FCh ; ?
seg000:FFFFF db 0B3h ; ?
seg000:FFFFF seg000 ends

清單 2.4

在ex38dq6.BIN 檔中的FFFF0位置對應著物理FFFFFFF0這個位址上,第1條指令是far jmp,跳轉到BOOTBLOCK中,通常在這條指令的周圍是些有意議的字元描述,如:02/27/08這是BIOS日期,在far jmp 下麵處於BIOS尾端。

三、不同BIOS檔之間的異同

1、結構不同,AMI和award的BIOS是有差別的。
2、BIOS檔的大小不同,一般的BIOS檔大小為512K,以ma79xds4.f4為例,它是512K,ex38dq6.f2是1M,但結構上差什麼差異,下面是ma79xds4.f4的結構:

No. Item-Name Original-Size Compressed-Size Original-File-Name
================================================================================
0. System BIOS 20000h(128.00K)13944h(78.32K)ma79xds4.BIN
1. XGROUP CODE 0F7D0h(61.95K)0AB7Bh(42.87K)awardext.rom
2. ACPI table 06391h(24.89K)02B35h(10.80K)ACPITBL.BIN
3. EPA LOGO 0168Ch(5.64K)0030Dh(0.76K)AwardBmp.bmp
4. GROUP ROM[18] 03340h(12.81K)02339h(8.81K)ggroup.bin
5. YGROUP ROM 0B310h(44.77K)05023h(20.03K)awardeyt.rom
6. GROUP ROM[ 0] 07100h(28.25K)02C90h(11.14K)_EN_CODE.BIN
7. PCI ROM[A] 0C800h(50.00K)0AC37h(43.05K)sata22.bin
8. OEM1 CODE 0AE4Fh(43.58K)06B6Dh(26.86K)ui22.bin
9. PCI ROM[B] 0A800h(42.00K)06007h(24.01K)RTLGPXE.LOM
10. LOGO1 ROM 00B64h(2.85K)00520h(1.28K)dbios.bmp
11. OEM0 CODE 028ABh(10.17K)01E1Bh(7.53K)SBF.BIN
12. GV3 088C6h(34.19K)026FBh(9.75K)AGESACPU.ROM
13. MINIT 11B80h(70.88K)11BB3h(70.92K)MEMINIT.BIN
14. HTINIT 04BC0h(18.94K)04BF0h(18.98K)HT.DLL
15. 2 PE32 in MB 00552h(1.33K)00582h(1.38K)HT32GATE.BIN
(SP) NCPUCODE 04000h(16.00K)04000h(16.00K)NCPUCODE.BIN

Total compress code space = 63000h(396.00K)
Total compressed code size = 621F3h(392.49K)
Remain compress code space = 00E0Dh(3.51K)

清單 2.5

上面清單所示,與ex38dq6.f2的結構一樣,每個元件都有一部分是經過壓縮的。

●跟著流程走(一):far jmp後發生什麼?




跟著流程走(一):far jmp後發生什麼?
一、所需材料
1、主要BIOS :ex38dq6.f2 :技嘉主機板上Intel X38 MCH + ICH9 平臺。
備用BIOS:ma79xds4.f4 : 技嘉主機板上AMD 790X 北橋 + SB600 南橋平臺。
2、cbrom:這是一個BIOS編輯工具,這裡所用的是cbrom182版本
3、lha2.55:LHA格式的解壓工具。
4、awdbedit:award bios 的圖形化編輯工具,方便簡單。
5、hex workshop:一個十六進位編輯工具,簡單小巧。
6、IDA:一個反彙編工具,這裡使用的是IDA 5.2版本

二、所需知識
1、組合語言:這是必備的知識,彙編掌握的程度和理解能力成正比。
2、機器語言:這個不是必需的,但推薦能夠讀懂機器語言,某些場合下當組合語言也陷入窘境時,機器語言是唯一的解釋手段。
3、x86體系知識:具體可以查看相關的Intel 或 AMD 手冊
4、ISA/PCI 匯流排知識:可以查看相應的 ISA/PCI Specification
5、north/south bridge 知識:Intel 現在以MCH代稱north bridge,ICH代稱south bridge,可以查看相應的 datasheet


接下來用IDA pro打開ex38dq6.BIN觀察,這個是BIOS的主體檔,跟著流程走,看看far jmp後BIOS做什麼工作。
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的處理,以下是第二張流程圖:
下面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進行晶片組的初始化。

三、下面是本站節的重點,初始化某部分晶片組,下面是流程圖:
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。

●瞭解幾個資料結構


瞭解幾個資料結構
一、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 定義的某些資料結構(例如:鍵盤緩衝區)。
2、在 BIOS 執行初期 BIOS 將檢測 BIOS 資料區域的某些資料確定當前機器狀態。例如:BIOS 通過檢查 [0472] 的內容是否是 1234H 從而確定機器是暖開機還是冷開機。
3、BIOS 定義了鍵盤緩衝區在 01Eh 開始的 32 個位元組的。

●bios知識起點



bios知識起點:幾個重要知識
一、位址空間解析
(1) 0 ~ 9_FFFF:屬於DOS的基本記憶體,被MCH控制提交到DRAM。
(2) A_0000 ~ B_FFFF:此區域能提交到Device 2(集成顯示裝置)、Device 1(PCI Express周邊設備)以及提交到ICH進行處理。按Dev2/Dev1/ICH優先順序進行映射。
當處理器在SMM模式下,此區域供SMM 使用,此時,此區域將被MCH提交到DRAM相同的位置上。
(3) C_0000 ~ F_FFFF:共256KB的區域被稱為PAE(Programed Attribute Memory)區域,可定義4種類型屬性:Disable、Read/Write、Read-Only以及Write-Only。根據這些操作類型可以提交到DRAM和ICH處理。這些區域的初始狀態是Disable屬性,也就是說,缺省時將得交到ICH處理。可由BIOS BOOTLOCK進行對類型的屬性設置,從而達到shadow memory效果。。
(4) E_0000 ~ E_FFFF:典型地被用作BIOS使用。俗稱E_Segment。
(5) F_0000 ~ F_FFFF:被提交到LPC匯流排上的BIOS,它是High BIOS的別名,初始屬性是disable,將被MCH控制直接提交到ICH處理。俗稱F_Segment。
(6) F0_0000 ~ FF_FFFF:這1M的空間可以選擇保留給ISA使用,這段空間將不提交至DRAM,轉交ICH處理。
(7) TOLUD(Top Of Low Usable DRAM)由Host Bridge Registers設置,在位址空間最低DRAM memory區域的頂端,在TOLUD下的TSEG區域可設置為1M、2M或8M,processor在SMM模式下將被提交至DRAM相同位址上。
(8 ) IGD區域:可供MCH內部集成的Graphics Device使用,MCH內被定義為Device 2。大小由1M ~ 64M。
(9) E000_0000 ~ EFFF_FFFF:這段空間典型地被用作為PCI Express 配置空間,由Host Bridge(Dev0)的PCIEXBAR寄存器設置。缺省的Pci Express Base Address設置為0_E000_0000,共256M大小。每個Device的每個Functon大小為4K(包括PCI的256 Byte和PCI Express增強的空間共4K)
★ PCI Express Bus 定義了256條Bus:Bus0 ~ Bus255,每條Bus下可掛32個設備:Device0 ~ Device 31,每個設備下又可執行8個Function:Function 0 ~ Function 7。
★ PCI Express空間大小可得:256 × 32 × 8 × 4096 = 256M
(10) FEC0_0000 ~ FEC7_FFFF:這段區域固定分配IOAPIC中斷控制器使用,訪問這段區域將被提交到ICH處理。
(11) FEC8_0000 ~ FECF_FFFF:附加的APIC增加區域提交至 PCI Express 埠,當不設此區域時,整個APIC 配置區域(FEC0_0000 ~ FECF_FFFF)將提交至 ICH。
(12) FEDA_0000 ~ FEDB_FFFF:可選為HSEG區域以供SMM模式下使用。被重新映射至A_0000 ~ B_FFFF區域。
(13) FEE0_0000 ~ FEEF_FFFF:這段區域保留給FSB中斷信號使用。當PCI Express 或 ICH上的設備往這段區域寫資料時,將以中斷信號形式提交至FSB上,不會提交到DRAM。
(14) FFE0_0000 ~ FFFF_FFFF:此區域固定分配給BIOS 使用。共2M空間,將直接提交至ICH處理。

二、關於PCI Bus
1、PCI Bus是一種原生32位,通過Dual Address Cycle形式來實現64位元定址的資料匯流排。
2、PCI的配置寄存器集成在MCH(North Bridge)內部:CONFIG_ADDRESS寄存器和CONFING_DATA寄存器,通過埠 CF8H ~ CFBH 來實現32位元資料訪問 CONFIG_ADDRESS寄存器;通過埠 CFCH ~ CFFH 來實現32位元資料訪問 CONFIG_DATA 寄存器。
3、PCI 設備訪問位址如下:

4、訪問的形式:
mov eax,80000090h
mov dx, CF8h
out dx, eax
mov dx, CFCh
in eax,dx
… …
三、I/O 空間
1、系統提供的64K IO定址空間(0 ~ FFFF)劃分幾種情況:
★分為MCH(hostbridge) 使用和 ICH 使用,大部分供ICH外部設備使用。例如:CF8h ~ CFFh 這些埠固定分配供MCH用來訪問PCI Configuration Space。
★分為系統固定分配及按情況進行分配。如:0 ~ 1Fh 埠固定分配給ICH的DMA 控制器使用。
★一些埠被保留未用。
2、一般PCI設備都可選用memory空間和IO空間進行設備訪問。在PCI設備的configuration Register可對PCICMD寄存器的IOSE位設為IO空間訪問,根據IOBASE及IOLIMIT寄存器設置IO埠位址。

四、MCH(hostbridge)提交到ICH後的轉發處理
ICH包括PCI-to-PCI bridge 、PCI-to-ISA bridge、USB Controller、PCI Express Ports等等。ICH收到經MCH提交來的地址,相當多都分派到PCI-to-ISA bridge(LPC Controller)中的Firmware Hub及LPC bus。

●memtest86+教學 Part7

這一次我想從簡單部份說起;學code得基本原則就是debug,因此要debug就必需將我們不懂的地方把它show到螢幕的畫面上。所以我們先介紹以下這個函式:
/*
* Print characters on screen
*/
void cprint(int y, int x, const char *text)
{
register int i;
char *dptr;

dptr = (char *)(SCREEN_ADR + (160*y) + (2*x));
for (i=0; text[i]; i++) {
*dptr = text[i];
dptr += 2;
}
tty_print_line(y, x, text);//印出的UART
}
這是一個印出文字(字串)到螢幕上的Routine,相信它應該非常簡單,SCREEN_ADR0xb8000,這個位址就是bios映射到vga的彩色文字資料區;對這一區的記憶體讀寫就等同於對螢目frame buffer讀寫;相關資訊可參考這個網址。
接著我們去回想一下do_test有呼叫一個routine:init()
void init(void)
{
int i;

outb(0x8, 0x3f2); /* Kill Floppy Motor */

/* Turn on cache */
set_cache(1);

/* Setup the display */
display_init();

/* Determine the memory map */
if ((firmware == FIRMWARE_UNKNOWN) &&
(memsz_mode != SZ_MODE_PROBE)) {
if (query_linuxbios()) {
firmware = FIRMWARE_LINUXBIOS;
}
else if (query_pcbios()) {
firmware = FIRMWARE_PCBIOS;
}
}

mem_size();

/* setup pci */
pci_init();

/* setup beep mode */
beepmode = BEEP_MODE;

v->test = 0;
v->pass = 0;
v->msg_line = 0;
v->ecount = 0;
v->ecc_ecount = 0;
v->testsel = -1;
v->msg_line = LINE_SCROLL-1;
v->scroll_start = v->msg_line * 160;
v->erri.low_addr.page = 0x7fffffff;
v->erri.low_addr.offset = 0xfff;
v->erri.high_addr.page = 0;
v->erri.high_addr.offset = 0;
v->erri.min_bits = 32;
v->erri.max_bits = 0;
v->erri.min_bits = 32;
v->erri.max_bits = 0;
v->erri.maxl = 0;
v->erri.cor_err = 0;
v->erri.ebits = 0;
v->erri.hdr_flag = 0;
v->erri.tbits = 0;
for (i=0; tseq[i].msg != NULL; i++) {
tseq[i].errors = 0;
}
if (dmi_initialized) {
for (i=0; i <> 0) {
dmi_err_cnts[i] = 0;
}
}
}

cprint(LINE_CPU+1, 0, "L1 Cache: Unknown ");
cprint(LINE_CPU+2, 0, "L2 Cache: Unknown ");
cprint(LINE_CPU+3, 0, "Memory : ");
aprint(LINE_CPU+3, 10, v->test_pages);
cprint(LINE_CPU+4, 0, "Chipset : ");

cpu_type();

/* Find the memory controller (inverted from standard) */
find_controller();

if (v->rdtsc) {
cacheable();
cprint(LINE_TIME, COL_TIME+4, ": :");
}
cprint(0, COL_MID,"Pass %");
cprint(1, COL_MID,"Test %");
cprint(2, COL_MID,"Test #");
cprint(3, COL_MID,"Testing: ");
cprint(4, COL_MID,"Pattern: ");
cprint(LINE_INFO-2, 0, " WallTime Cached RsvdMem MemMap Cache ECC Test Pass Errors ECC Errs");
cprint(LINE_INFO-1, 0, " --------- ------ ------- -------- ----- --- ---- ---- ------ --------");
cprint(LINE_INFO, COL_TST, " Std");
cprint(LINE_INFO, COL_PASS, " 0");
cprint(LINE_INFO, COL_ERR, " 0");
cprint(LINE_INFO+1, 0, " -----------------------------------------------------------------------------");

for(i=0; i < style="font-weight: bold;">cprint(i, COL_MID-2, " ");
}
footer();
// Default Print Mode
// v->printmode=PRINTMODE_SUMMARY;
v->printmode=PRINTMODE_ADDRESSES;
v->numpatn=0;
find_ticks();
}
這些粗體字就是我會講解的重點;首先說明set_cache
void set_cache(int val)
{
extern struct cpu_ident cpu_id;//cpu_id這個資料結構在head.S中已初始化完成
/* 386's don't have a cache */
if ((cpu_id.cpuid lss 1) && (cpu_id.type == 3))
{
cprint(LINE_INFO, COL_CACHE, "none");
return;
}
switch(val)
{
case 0:
cache_off();
cprint(LINE_INFO, COL_CACHE, "off");
break;
case 1:
cache_on();
cprint(LINE_INFO, COL_CACHE, " on");
break;
}
}

static inline void cache_on(void)
{
asm(
"push %eax\n\t"
"movl %cr0,%eax\n\t"
"andl $0x9fffffff,%eax\n\t" /* Clear CD and NW */
"movl %eax,%cr0\n\t"
"pop %eax\n\t");
}
這個組合語言是GCC-Inline-Assembly,請自行參考語法說明。打開CACHE快取才可使CPU存取RAM的速度加快;因為匯流排,即使是跑DUAL CHENNEL,也不會比CPU快,因此快取越大,更能提升CPU效能。但要知道一點,這軟體主要是測試記憶體,萬一CPU快取本身有問題,就很難去測試記憶體真正的PASS或FAIL。

2009年3月27日 星期五

●有關smm模式及big real mode

有沒有32位真實模式,why?
在80286之後的機器上,在真實模式下已經可以部分使用32位元資料,如寄存器可以用eax等,但根本的問題是不能定址32位的位址空間,但原因不是在真實模式下不能使用32位元的定址方式,而是位址空間被“封閉”了。
如果你寫一段程式,選進入保護模式,將gs,fs等寄存器設置為可以定址全部記憶體空間,然後返回到真實模式,只要你不刷新gs /fs,則始終可以通過它們訪問全部記憶體,這表明在真實模式下是具備32位能力的,只是不太好用罷了。
Q:請問為什麼不太好用啊?A:你要在真實模式和保護模式下頻繁地切換,累啊。
因為一般來說程式中總是需要偶爾改動一下ds,es,cs這些常用的段寄存器,因此在真實模式下,在大多數時候還是受到限制,上面回復的方法只是突破了資料段的限制,使資料段可以達到4G,但程式碼片段還是不行。
intel 的文檔裡說的很清楚。SMM模式特意設計成只能讓系統的固件使用,不能被使用者程式和系統程式進入。進入SMM模式的方法是給SMI#管腳輸入一個電平信號 或者通過APIC給匯流排發一個SMI消息。你在看看smm那章,smm與其他模式切換是比較特殊,但關於big real mode 的介紹也只出現在intel手冊的這章中。smm模式使用真實模式的段式管理,但他的資料段描述符是4g的。
intel cpu smm模式與其他模式的切換只要你有晶片組的手冊就能知道如何切換(處於特權級0),cyrix的cpu 通過寄存器 0x22 0x23也可以切換。

問題越來越有趣了。flat(big) real mod is not system manager mode。SMM的進入方法我想我也沒有理解錯,intel x86進入SMM的唯一方式就是給SMI#引腳輸入信號,具體可以通過對ACPI或者fireware進行程式設計實現。進入SMM方式比進入big real mode要複雜,一方面要對APIC或者fireware程式設計,另一方面要設置好SMM的執行環境。intel文檔中也明確說明SMM方式特意設計成只能 讓fireware執行(當然,系統程式通過APIC也是可以實現的)。
在網上搜集了一些資料是說flat(big) real mode的,在486S後確實存在這種32位的真實模式,其位址是32位元的,但是指令預設是16位元的,如果執行32位元指令要在前面加首碼。一旦進入flat real mode,也沒有必要去更改CS,DS,FS,ES等段寄存器了,因為記憶體是平坦模式,基底位址是00000000,界限是4G,所以可以放心使用,關鍵問 題是16位元指令和32位元指令混合使用會產生麻煩。Windows 3.1就是使用這種模式。但很奇怪,intel的手冊裡面沒有提到過flat(big) real mode,也許正如文中所說,當Pentium引入時,big real mode再也沒有什麼吸引力了,以至於flat real mode只是曇花一現,這種模式成了intel公司的X file.

是的是的,書上說的沒錯,你理解也正確。我最開始的意 思就是big real mode在intel的手冊也只有smm這章有過介紹。big real mode有多中叫法:unreal mode ,falt real mode,其實都是一個意思,在real mode 下使用4g記憶體。不過切換到smm 模式我個人並不覺得有多困難,intel 晶片組的手冊是開放的!切換到smm只要通過晶片組允許0xA0000,然後寫io 埠0xb2產生smi中斷就切換到smm了,退出用rsm指令,smm是個有趣的模式,在這個模式裡你可以看到Descriptor Cache 的內容!

2009年3月26日 星期四

●X86開機時的狀況

開機後,CPU重置,從位址FFFFFFF0取第一個命令,這個位址正好落在ROM BIOS中。該位址內容一定是一個JMP指令,系統便跳轉到該JMP指令所指的地方。
1‧而這裡之後我開始不明白了。JMP要跳轉到的位置是在高地址(4G末端)Flash Rom BIOS中還是在低地址(1M末端)的shadow BIOS呢?
2‧位於低地址(1M處)的(BIOS shadow)是從Flash BIOS拷貝而來呢,還是沒有任何拷貝過程僅僅利用位址映射到原Flash BIOS中的呢?
3‧無論是拷貝還是映射,記憶體位址空間上ROM BIOS映射區只有 0xF0000~0x100000之間的64KB。而ROM本身有可能大於2M。那映射的應該是原BIOS程式的一部分,那麼是哪一部分呢?對這個問題的回答需要闡明機關概念:
1.機器加電時,記憶體控制器還沒有初時化,記憶體是不可用。
2.機器加電時,對CPU的指令的解碼不是北橋,CPU發出的位址被傳遞到南橋並由FHW(Firmware Hub)解碼到BIOS ROM晶片(Flash)。在加電時一直到引導進程初,BIOS的E段(0xE0000~0xEFFFF)和F段(0xF0000~0xFFFFF)和4G記憶體頂端的對應段0xFFFE0000~0xFFFEFFFF和0xFFFF0000~0xFFFFFFFF都被FWH解碼到BIOS的ROM晶片的兩個64區域。即在啟動階段訪問0xE0000~0xEFFFF和0xFFFE0000~0xFFFEFFFF是同一個BIOS區域,訪問0xF0000~0xFFFFF和0xFFFF0000~0xFFFFFFFF是同一個BIOS區域。
3.機器加電時,CS段寄存器值為0xF000,EIP值為0x0000FFF0,但CPU的取的位址是段寄存器不可見的部分(影子寄存器)加上偏移部分,此時影子寄存器的值為0xFFFFFFF0。所以CPU執行的第一條指令是0xFFFFFFF0(復位向量),通常在BIOS ROM對應的指令是一個跳轉指令JMP F000:E05B,當取出跳轉完成後,由於CS段的影子寄存器刷新並重新載入,下一條指令位址是0xFE05B。不過這條指令仍然從BIOS ROM裡取得。
4.關於shadow BIOS,BIOS程式通常是壓縮的,在系統初始化階段,BIOS會解壓BIOS Image到RAM中,然後程式設計北橋控制器對0xE0000~0xFFFFF置為write only,這樣對該區域的寫被傳遞到DRAM裡,然後把解壓的BIOS拷貝到E段和F段。最後重新程式設計北橋控制器對0xE0000~0xFFFFF置為read only。對於PCI ROM BIOS,BIOS會把每個卡上的ROM拷貝到0xC0000~0xDFFFF然後執行他們的初時化代碼。5.BIOS ROM可以很大,但不都是可執行的,如含有ACPI Table等,開始解壓到0xE0000~0xFFFFF只是其中一部分,在啟動過程中還需要從BIOS ROM解壓代碼到RAM中,並覆蓋其中不需要的代碼。這個就好像BIOS ROM是硬碟(不過可用直接訪問),真正執行的代碼在RAM中一樣。硬碟可以很大但RAM小,這也就是程式的局部性原理。
這個網址也是相關議題的論述:X86 開機流程小記BIOS 探索之旅可以比較一下是否有差異點。

BIOS的幾個模組中有部分是壓縮的,有部分是 pure binary,純代碼和壓縮代部參雜在一起。
BIOS有部分是 routine 元件,它是 pure binary,其它就包括初始化(晶片組、DRAM等)模式,解壓routine元件、還有就是CPU 的微代碼update模式等等,提供BIOS運行期間的一些函式呼叫,解壓rontine的作用就是解壓壓縮組件,將它們放入相應的memory中。還有 一件重要的事情是,建立一個 interrupter vector 及 interrupt service routine。

bios啟動ram記憶體初始化前bios是否在rom中運行,這樣的話rom的地址會不會跟ram地址衝突?
北橋晶片有 Shadow RAM 功能,ROM 和 RAM 都會映射到同一段位址,但是根據讀寫信號的不同轉到 ROM 或者 RAM去。
第一條 long jmp 指令,也就成為 x86 pc 機的固有約定或者說是規範吧。

其實主要的原因是相容!追溯到最早 808X 系列處理器,8080 是 16 位 address bus, 8086 及 8088 改進為 20 進 address bus,整個 808X 系列處理就是整個 x86 架構的始祖。定址空間 00000 ~ FFFFFh 也就是 1M 的空間。
當時 IBM 決定使用 8086 處理作為 IBM PC 機,故事就從那裡開始,BIOS 這個名詞也就是 IBM 發明出來的,IBM 搞出來的 BIOS 定位在 8086 處理器的定址高端,也就是 F0000 到 FFFFF 區域。從 386 開始,address bus 增加到 32 條,定址範圍從 0 ~ FFFFFFFFh,BIOS 的定位也在 4G 的高端FFFF0000 ~ FFFFFFFFh,但為了相容,對 F0000 ~ FFFFF 的訪問被映射到 FFFF0000 ~ FFFFFFFFh,這是從理論上定義的。
從物理上來講,F0000 ~ FFFFFh 映射到 FFFF0000 ~ FFFFFFFFh 靠硬體來保證,在位址解碼時,F0000 ~ FFFFFh 與 FFFF0000 ~ FFFFFFFFh 會被解碼到同一個區域。現在的晶片組提供的廠商有很多,如:Intel,AMD,nvidia,VIA,SIS 等,它們的解碼實現方法可能會不同,但都要保證這個所謂的“別名”機制。
Intel 實現是:MCH 將 C_0000 ~ F_FFFF 這段區域定義為 PAM(Programed attribute memory),分 disable,read-only ,wirte-only,read/write 四種屬性,初始屬性是 disable,也即是無用,因此這段區域將被送去 ICH 解碼,FFE0_0000 ~ FFFF_FFFFh 的也被 ICH 解碼,ICH 轉交 LPC bridge 處理,它們被解碼為同一區域。
AMD 實現是:無論是 C_0000 ~ F_FFFF,還是 FFFC_0000 ~ FFFF_FFFF 最終結果都將送到 LPC bus 上的 FFFC_0000 ~ FFFF_FFFF 物理位址上。其它的廠商實現也大體這樣。

因此:long jmp 後,轉到 FE05B(jmp far ptr F000:E05B)執行,它將被映射到物理位址 FFFFE05B 上,這還是 BIOS 所在的 ROM 中。第一條指令的 FFFFFFF0 與 第二條的 FE05B 都是在 BIOS 的 ROM 上。

2009年3月24日 星期二

●繼續閱讀懶人加強版

繼續閱讀懶人加強版

好用ㄉ部落格工具。

●描述符表和描述符快取記憶體




在80x86的CPU裡,描述符的概念實在是太重要了。
在真實模式下,大家都知道物理位址是由段位址和偏移位址兩部分組成,其公式如下:
物理位址 = 段位址 × 16 + 偏移位址
或者:物理位址 = 段位址 << 4 + 偏移位址
其結果都是一樣的,由於段位址和偏移位址的長度都是16位元,所以這種方式能夠表達的最大位址為:ffff:ffffH,也就是10ffefH,大致是 1088KBytes,有由於8086CPU的位址線只有20位,所以在8086上實際的定址能力僅為1024KBytes,在80286和 80386CPU上,通過A20的使用,可以實際定址到1088KBytes,這個問題在《關於A20 gate》的文章中有過介紹。
從80386開始,CPU的位址匯流排已經到了32位元,但只有在保護模式下才能真正地享受32位的高性能,實際上在保護模式下,CPU也是使用段:偏移位址 的方式來定址的,只是這裡面有兩點區別,1-偏移位址可以是16位也可以是32位;2-段寄存器中的內容和在真實模式下的含義完全不一樣,而且已經不是組成 物理位址的一部分。顯然,偏移位址的這種變化是很容易理解的,無需更多地解釋,所以,保護模式和真實模式相比,重要的區別就是段寄存器中內容的區別了。
那麼,這個段寄存器中到底存的是什麼東東呢?它存的是某個描述符在描述附表中的偏移值,這是什麼意思呢?一個描述附表中有很多描述符,每個描述符的長度都 相同,假如第一個描述符我們編號是0的話,那麼第n個描述符的編號就是n-1,這個n-1就是所謂的描述符索引,所謂“偏移”就是從描述符表起始位置起, 到我們要用的這個描述符有多少個位元組,也就是描述符索引 × 描述符長度,很顯然如果知道描述符表的起始位址,再加上段寄存器的值,我們就能找到我們要的這個描述符了。
我們先來把到此為止的問題羅列一下:
1、這個描述符是什麼東東?
2、畢竟我們是要通過段:偏移位址的形式得到物理位址,既然這個段代表一個描述符,那麼這個描述符和物理位址有什麼關係?
3、上面說到的描述符表的起始位址和描述符長度是是什麼?因為沒有這兩個東西還是找不到描述符的。
我們試著來說明這幾個問題。
首先,描述符實際上就是一個8位元組長的資料結構,它的定義如上:

每一個描述符代表一個分段(有點像真實模式下的64K分段),可以看到段基址由Byte2、Byte3、Byte4和Byte7組成,一共32bits,段 的最大長度不像在真實模式中固定為64K,而是有一個20位的段邊界(由Byte0、Byte1和Byte6的低4位組成)來設定,由於偏移位址為32位, 所以實際上段的最大長度可以達到4GBytes,段邊界的單位可以是位元組也可以是4KBytes,這取決於G=0還是G=1(Byte6的bit 7),當G=1時,段邊界的單位是4KBytes,相當於低12bits全部為1(4K剛好12bits),加上20位的段邊界,剛好為32位,最大可以 達到4GBytes;當G=0時,段邊界的單位為位元組,20位元最大為16MBytes。
在描述符的定義中還有一個訪問權位元組以及AVL、D/B等,介紹起來篇幅很長,以後有機會介紹。
說到這兒,可能大家心裡有點眉目了,段寄存器裡存放一個描述符在描述符表中的偏移,通過這個偏移可以找到相應的描述符,通過這個描述符中的段基址再加上偏移位址,就組成了物理位址,對了,基本上就是這樣,當然不會這麼簡單,其中還有很多細節,但大致原理就是這樣。
前面提到的三個問題,我們已經回答了兩個半,就是描述符是什麼,怎樣計算物理位址,還有描述符的長度是多少。最後一個問題是:描述符表的起始位址在那裡? 其實這個問題最好回答,在那篇《80386寄存器組成》的文章中提到過一個GDTR的系統表寄存器,這個描述符表的起始位址就存在這個寄存器中(這麼說不 是很完整,但不影響理解),如果你還要問,描述符表存在哪裡?如何把描述符表的起始位址放到GDTR寄存器中?這兩個問題也不難回答,描述符表可以存儲在 記憶體的任何位置;通過LGDT指令可以把描述符表的起始位址存入GDTR寄存器中。
大家有沒有感到奇怪,為什麼32位的段基址和20位的段界限在描述符中都不連續存放,這是和Intel 80x86 CPU的發展歷史分不開的,最先有保護模式的CPU是80286(現在已經不多見了),這個CPU只有24位的位址線,所以可以看到現在386的描述符中 基底位址的前24bits是連續存放的,後來在擴充時需要把基底位址變成32位元,為了和80286相容,只好分開存放了,段界限也是同樣原因造成的,所以在 80286上,一個描述符的長度雖然也是8位元組長,但它只用到了前6個位元組,到了386就全都用上了。
到這裡,應該對保護模式下記憶體的定址有了一個大致的瞭解,CPU首先通過段寄存器中的偏移值,配合GDTR寄存器中的描述符表的起始位址,找到段寄存器表示的描述符,再從描述符中獲得段基址,然後再加上偏移位址就得到了物理位址(其中許多細節省略不說)。
大家有沒有感到,CPU的定址過程很累,那麼這麼累的定址會不會導致速度變慢呢?實際上,CPU的定址過程並不像上面描述的這麼累,要解釋CPU的實際定址過程,必須要提到描述符快取記憶體(Descriptor Cache Register)。
實際上,不管是在真實模式還是在保護模式下,CPU都會把一個分段的基底位址放在一組隱藏的寄存器中,這組隱藏的寄存器,對程式師是不可見的,程式也是無法直 接存取的,但卻是實際存在的,這組隱藏的寄存器叫做描述符快取記憶體寄存器(Descriptor Cache Registers),當段寄存器的值發生變化時,段的基底位址、段的邊界以及存取屬性(存取許可權)都會被重新載入到這個段寄存器對應的快取記憶體中,為增強 性能,CPU對隨後的定址均會直接從這個快取記憶體中計算,而不會去描述符表中提取描述符,實際上,各種CPU中這個快取記憶體的內部結構是不同的,以上是幾種CPU中快取記憶體的結構圖:

不管其內部結構是怎樣的,但我們可以看到,CPU在把描述符載入進快取記憶體時並不是簡單的拷貝,而是做了一些適當的處理,比如把原來沒有連續存放的段基址 和段界限變成了連續的,把原來20bits的段界限根據G值轉換成了32bits,所以我們在快取記憶體裡看不到原來描述符裡的G標誌就是這個道理,顯然, 這些處理十分有利於快速地得出實際位址來。
前面我們提到過,CPU內部寄存器GDTR中存儲著描述符表的起始位址,細心的讀者可能會發現,《80386寄存器組成》一文中提到的GDTR寄存器有 48bits,而且分成兩部分,一部分是32bits,另一部分是16bits,這是怎麼回事呢?實際上GDTR中不僅存著描述符表的其真實位址,還存放著 描述符表的長度,其中16bits的部分就是描述符表的長度,32bits的部分就是描述符表的起始位址,按照規範,描述符表中最多可以有 8192(8K)個描述符,每個長度8個位元組,所以最大長度為64K,16bits已經足夠了,同理,段寄存仍然保持16bits長度也是足夠的。
不管是在真實模式還是在保護模式下,CPU在實際定址時都會使用這個快取記憶體。所不同的是,在真實模式下,在段寄存器的值發生變化時,僅僅把段寄存器的值 ×16(左移4位)放到快取記憶體的的基底位址位置,段界限和存取許可權總是一個固定不變的值(按照Intel的說法,PC機加電後工作在真實模式,快取記憶體中將 被置入缺省值,在真實模式下,其中的段界限和存取許可權將一直保持不變);而在保護模式下,當段寄存器發生變化時,CPU要從描述符表中載入資料到快取記憶體 中,實際上,保護模式下CPU在從描述符表中向快取記憶體中載入資料時要做大量的保護性檢查,大致如下:
1. 段寄存器的值不能是0。根據規範,描述符表中的第一個描述符必須是空描述符,所以段寄存器值為0是不合法的。如果為0,產生異常中斷13.
2. 段寄存器中的值是否大於或等於描述符表的長度(存在GDTR中),如果大於或等於描述符表的長度,產生異常中斷13.
3. 如果段寄存器是CS,檢查描述符表中的段類型是否為程式碼片段,如果不是,產生異常中斷13.
4. 如果段寄存器CS要求裝入的段是程式碼片段,檢查描述符表該段是否存在,如果不存在產生異常中斷11
5. 如果段寄存器CS通過了3、4的檢查,還要檢查IP是否超越了該段的邊界,如果越界,產生異常中斷13
6. 如果段寄存器CS通過了3、4、5的檢查,則把相應的描述符裝入快取記憶體
7. 如果段寄存器不是CS,檢查描述符表中的段類型為資料段,如果不是產生異常中斷13
8. 如果段寄存器不是CS,該段為資料段,檢查其是否存在,如果不存在產生異常中斷12
9. 如果段寄存器不是CS,且通過了7、8檢查,則把相應的描述符裝入快取記憶體
以上過程,不一定很完整,大概就是這樣,從中,大家應該可以看出,所謂保護模式的保護方式,至少有一個感性認識。

說到這裡,基本上可以結束了,但是我們所說的描述符表實際上是非常膚淺的,實際上本文說到的描述符表叫做通用描述元表(Global Descriptor Table 簡稱GDT),還有局部描述符表,中斷描述符表等,但其原理大同小異,理解了GDT,其它的也就比較容易了

2009年3月23日 星期一

●memtest86+教學 Part6



void do_test(void)
{
int i = 0, j = 0;
unsigned long chunks;
unsigned long lo, hi;

/* If we have a partial relocation finish it */
if (run_at_addr == (unsigned long)&_start) {
run_at_addr = 0xffffffff;
} else if (run_at_addr != 0xffffffff) {
__run_at(run_at_addr);
}

/* If first time, initialize test */
if (firsttime == 0) {
if ((ulong)&_start != LOW_TEST_ADR) {
restart();
}
init();
windows[0].start =
( LOW_TEST_ADR + (_end - _start) + 4095) shr 12;

/* Set relocation address at 16Mb if there is enough memory */
if (v->pmap[v->msegs-1].end gtr 0x1100) {
high_test_adr = 0x01000000;
}
windows[1].end = (high_test_adr shr 12);
firsttime = 1;
}
bail = 0;

//由於左移、右移、大於、小於的符號是html的標籤用途,因此以後程式碼若是有這些符號我便會用左移:shl 右移:shr 大於:gtr 小於:lss 的英文字符表示,否則顯示文章可能格式會有問題。
怎麼一進入c並沒有感到輕鬆自在;你就當作吃苦就是吃補。上面只是截取do_test前面一小段;或許你會每看一行code,心中就充滿一堆問題,真可說是如履薄冰,簡直就是破冰而行;對不起我喜歡有冰字的成語。 大家都已經知道do_test,我就不再多說,就直接進入這個routine:我們一小段一小段來吧! 因為windows[0]的值是(0,0x080000),因為它們是pmap所以單位是4k,所以0x80000對應到2G,但以圖PMAP-2,您會發現,經過compute_segments()routine求算出的結果:windows[0].start=0x21=132k,那是因為以下這一行: ( LOW_TEST_ADR + (_end - _start) + 4095) shr 12;你可以把它劃成下列等式8k+MT(120k)+4k=132K 這個MT為什麼會是120kㄋ,我自己編譯的memtest.bin目前是110k,所以不僅有10k差距,而且它MT只包含(_end-_start) ,並不包含boot code,因此差距就一定大於10k,所有我也不知道為什麼,反正目前就差不多就好了,而且你只要知道這 0→132k被memtest code給佔用了,所有平台都一樣,無法測得這部分。補充一點,就是640k到1mb這裡被dos系統給佔用了,所以也測不到。因此你會發現所有平台的第一個segs都一模一樣,你可參考圖PMAP-1及PMAP-2,都會是: (0X00000021,0X0000009F),即第一個SEGS【v->map】就是132K~636K的測試範圍。當然這是大約值,你可以代入mapping、emapping,就知道實際植,這裡就不囉唆了。也就是說0x00000021會隨著你的memtest86程式碼的大小來決定。若是看的相當吃力,沒關係,我們考慮從別的角度出發‧待續‧
不好意思,由於顯示問題,所以以上這段中文字都沒出現在畫面中;sorry!

2009年3月22日 星期日

●memtest86+教學 Part5


我們知道在setup.S中所設定ㄉgdt有兩ㄍ描述子,一ㄍ是資料段一ㄍ是程式段,而且它們都只能定址到128mb,我們說明一下描述子好ㄌ:
我只說明那個G位元:0則段界限之Granularity(粒度)為byte,1則段界限之Granularity(粒度)為4Kbytes。因此來看一下setup.Sㄉ描述子:
.word 0x7FFF # limit 128mb 0x7fff*4k=128mb
.word 0x0000 # base address=0
.word 0x9A00 # code read/exec
.word 0x00C0 # granularity=4096, 386
其他部分請自行參考"自己動手寫作業系統"一書。
既然它進入保護模式,但僅設為可定址到128mb,那麼當它ljmp $KERNEL_CS, $(TSTLOAD <<4)之後,它勢必再重新設定一次gdt,所以你在head.s中又看到一個gdt表。接著就是設定idt,這部分以後再來談;接下來作一些CPU相關判斷,然後才是要進入C語言的殿堂。
leal _dl_start@GOTOFF(%ebx), %eax
call *%eax
call do_test
Reloc.c中ㄉvoid _dl_start(void),另一ㄍ是main.c的void do_test(void)

"void _dl_start(void)"其實這ㄍROUTINE很難懂,若你也看不懂,也可先跳過,只知道它和ELF檔案格式有關,因為我們用C寫ㄉ這些ROUTINEㄉEntry和符號表可能尚未全部定位好,所以才呼叫這個函式。因此這ㄍ函式與記憶體測試和架構無關,若你懂ㄉ話也請你教教我。
以下這ㄍ網址有關於這方面ㄉ知識:Before main()
void do_test(void)當然就是我門要談ㄉ重點ㄌ‧‧‧待續。

2009年3月20日 星期五

●memtest86+教學 Part4


(爛豬腳‧‧續)_GLOBAL_OFFSET_TABLE_為基底位址的符號會
得到當前段的起始地址到global offset table的距離(offset),由於此
時已經是32bits的flat4G定址,因此引用這個符號相當等同於取得
.data segment及.bss區的啟始位址,因此我們比較一下為什麼setup.S
就不是用這種方式來計算offset值 ,因為那時還在真實模式,而現在
是在保護模式。
movl $(LOW_TEST_ADR + _GLOBAL_OFFSET_TABLE_), %esp
#LOW_TEST_ADR=0x2000=8k
所以這一行相當於,將堆疊指標暫存器設在.data區開始後的8k處。
/*********************************************************/
/* Load the GOT pointer */

call 0f        #為什要這樣call目的何在,目前I do'nt know.     
0: popl %ebx
addl $_GLOBAL_OFFSET_TABLE_+[.-0b], %ebx
上面這一行就是獲得標籤"0:",也就是popl %ebx這條指令的地址
,然後保存在%ebx中。
/*********************************************************/
/* Reload all of the segment registers */
leal gdt@GOTOFF(%ebx), %eax #取得gdt位址存入eax
movl %eax, 2 + gdt_descr@GOTOFF(%ebx)
#將gdtㄉ基底位址存到gdt_descr+2ㄉ位址
lgdt gdt_descr@GOTOFF(%ebx)
#看到這一行你就應該要知道上一行ㄉ目的 
leal flush@GOTOFF(%ebx), %eax
pushl $KERNEL_CS
pushl %eax
lret
#上面這三行是ㄍ很特殊ㄉ寫法,lret會pop出(cs)segment:offset
#所以相當於程式會繼續網下執行,但由於不能寫成如下:
#movl $KERNEL_CS, %eax
#movw %ax, %cs
#jmp flush 因為cs不可這樣搞

flush: movl $KERNEL_DS, %eax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss

/*
* Zero BSS 將_bss區清0
*/
cmpl $1, zerobss@GOTOFF(%ebx)
jnz zerobss_done
xorl %eax, %eax
leal _bss@GOTOFF(%ebx), %edi
leal _end@GOTOFF(%ebx), %ecx
subl %edi, %ecx
1: movl %eax, (%edi)
addl $4, %edi
subl $4, %ecx
jnz 1b
movl $0, zerobss@GOTOFF(%ebx)
zerobss_done:
此時你腦袋中應該要浮現出到目前為止程式在記
憶體中如何配置如上圖。
但這是沒有在dos環境下跑的流程:所以我們要找時間來看看這個mt86+_loader.asm檔,因此把它弄清楚就可以知道和正常的booting有何不同。

●memtest86+教學 Part3

在我們進入最佳男主角(爛豬腳)ㄉ主題之前,我希望先把makefile及三ㄍlds檔 memtest.bin.lds、memtest.lds、memtest_shared.lds稍加探討探討。若你還不知道什麼是makefile那先去溫習功課再來看這裡。makefile的寫法你去比較一下1.70版,稍有不同,但達成ㄉ效果是一樣的。一般我們很少會去寫lds檔,因為寫apㄉ人搞不好根本就不知道這是什麼東東,lds檔是給linux下gcc附的linker連結器LD所參考用的,若你不指定lds檔,LD還是會叫用預設lds檔,故名思義就是連結器專用的script(描述檔),畢竟memtest86+不是OS下ㄉapplication它等於是ㄍ小型的OS,因為從Boot到run實際應用都是要去和實際硬體溝通,因此lds檔當然要符合它自己ㄉ需求。先截取部分makefile內容:
memtest.bin: memtest_shared.bin bootsect.o setup.o memtest.bin.lds
$(LD) -T memtest.bin.lds bootsect.o setup.o -b binary \
memtest_shared.bin -o memtest.bin
以上敘述: bootsect.o setup.o連結時要參考memtest.bin.lds
以下是
memtest.bin.ldsㄉ內容:
OUTPUT_FORMAT("binary")
OUTPUT_ARCH("i386")

ENTRY(_main);
SECTIONS {
. = 0;
.bootsect : { *(.bootsect) }
.setup : { *(.setup) }
.memtest : {
_start = . ;
*(.data)
_end = . ;
}
_syssize = (_end - _start + 15) >> 4;
}
若沒有全部看懂,先懂一半也行。
其他的makefile內容依此類推,應該難不倒大家。

爛豬腳:head.S
.code32
.globl startup_32
startup_32:
cld
cli
‧‧‧
看到沒,它是32位元的code,而且它沒有.section ㄉ宣告,因為它和Cㄉ部分是整合為同一區段,如下所示:
下面ㄉOBJS除了head.o是組合語言寫ㄉ,其餘都是C寫ㄉ,但這本來就無關緊要,重要ㄉ是這些物件檔被連結成
memtest_shared(依照memtest_shared.ldsㄉ敘述)
OBJS= head.o reloc.o main.o test.o init.o lib.o patn.o screen_buffer.o \
config.o linuxbios.o memsize.o pci.o controller.o random.o extra.o \
spd.o error.o dmi.o

memtest_shared: $(OBJS) memtest_shared.lds Makefile
$(LD) --warn-constructors --warn-common -static -T memtest_shared.lds \
-o $@ $(OBJS) && \
$(LD) -shared -Bsymbolic -T memtest_shared.lds -o $@ $(OBJS)

我們還是看一下
memtest_shared.ldsㄌㄌ等(台語)
OUTPUT_FORMAT("elf32-i386");
OUTPUT_ARCH(i386);

ENTRY(startup_32);
SECTIONS {
. = 0;
.text : {
_start = .;
*(.text)
*(.text.*)
*(.plt)
_etext = . ;
} = 0x9090
.rodata : {
*(.rodata)
*(.rodata.*)
}
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.hash : { *(.hash) }
.dynamic : { *(.dynamic) }

.rel.text : { *(.rel.text .rel.text.*) }
.rel.rodata : { *(.rel.rodata .rel.rodata.*) }
.rel.data : { *(.rel.data .rel.data.*) }
.rel.got : { *(.rel.got .rel.got.*) }
.rel.plt : { *(.rel.plt .rel.plt.*) }

. = ALIGN(4);
.data : {
_data = .;
*(.data)
*(.data.*)
}
.got : {
*(.got.plt)
*(.got)
_edata = . ;
}
. = ALIGN(4);
.bss : {
_bss = .;
*(.dynbss)
*(.bss)
*(.bss.*)
*(COMMON)
/* _end must be at least 256 byte aligned */
. = ALIGN(256);
_end = .;
}
/DISCARD/ : { *(*) }
}

若是看ㄌ一頭霧水,至少你也看到startup_32,它就是head.Sㄉ頭,至於_start和 _end 請看:
memtest: memtest_shared.bin memtest.lds
$(LD) -s -T memtest.lds -b binary memtest_shared.bin -o $@
以下是 memtest.lds內容 0x10000=64k
OUTPUT_FORMAT("elf32-i386");
OUTPUT_ARCH(i386);

ENTRY(_start);
SECTIONS {
. = 0x10000;
_start = . ;
.data : {
*(.data)
}
}

所以startup_32=_startyou know!

2009年3月19日 星期四

●桌面圖示陰影(背景)








這ㄍ問題解法相當簡單:




●memtest86+教學 Part2


mt86+_loader.asm這ㄍ檔案就是我上一篇文章賣的關子,有ㄌ它你可以將memtest86+製作成在Dos底下run的執行檔:
exeh: db "MZ"
dw fullsize % 512 ; how much to load from
dw (fullsize + 511) / 512 ; .exe to RAM
dw 0 ; no relocations used
dw 2 ; header size is 2 * 16 bytes
dw stackpara ; minimum heap is 128 * 16 bytes, for stack
dw stackpara ; we do not need more heap either
dw (fullsize + 15) / 16 ; SS is after file
; segment offsets are relative to PSPseg+10h
; initial DS and ES point to PSPseg, and file
; except headers is loaded to PSPseg+10h.

dw stacksize-4 ; initial SP value
dw 0 ; no checksum
dw 100h ; initial IP
dw -10h ; initial CS relative to PSPseg+10h
dw 0 ; no relocation table, "offset 0 in file"
dw 0 ; this is not an overlay
db "MEMT" ; padding to a multiple of 16 bytes
以上這些資訊是從mt86+_loader.asm截取下來,若你看不明白可以參考這份資訊,其他的看完有問題再說。因為到目前為止都還沒進入主題,所以先進入memtest86+殿堂看看究竟‧‧‧。
註:本文使用版本是memtest86+-2.01;首先看一下上圖有哪些檔案:不好意思好像不是頂清楚。首先我們看我圈選起來的那幾個檔案,先說明main.c吧!不要被檔案名稱給誤導ㄌ,學Cㄉ時候是要你知道main這ㄍroutine而不是檔名,所以這ㄍ檔案根本就不是程式ㄉentry(進入點)所在。
另外三ㄍ副檔名為大寫"S"的檔案bootsect.S、head.S、setup.S才是主角(注意,不要在windows將memtest86+-2.01.tar.gz解壓縮,因為windows不區分檔名大小寫),我並不想針對這三ㄍ檔案涉入太深,否則一樣會廢話一堆,但玩過Linux Kernelㄉ大大們對這三個檔案一定不陌生,網路上也有粉多這方面ㄉ討論,但是這三個檔案到底還是很重要,若你完全看不懂,我想你可能要花一段不算短ㄉ時間將它搞懂,因為你若不懂,則接下來的C語言你會Trace的很吃力,也不踏實,雖然說網路上有針對這三ㄍ檔案ㄉ論述,但畢竟memtest86已經修改過因此我會稍為作說明:
這三個檔案是GAS的語法:你必須自己搞懂GAS(Assembly),但mt86+_loader.asm又是nasm的語法‧‧‧‧這些主題若是我精力過旺,或許以後再另闢文章。
1.bootsect.S:先把自己512Bytes從0x7c00搬移到0x90000,並繼續執行;繼續執行的第一ㄍ動做就是movw %cs, %ax #讓cs值也等於0x9000,接著處理一些磁碟參數問題後將setup.S從1.44mbㄉ磁片讀到0x92000,然後是# we want to load the system (at 0x10000):將整ㄍ程式主體從磁碟讀到0x10000處,這個動作就是call read_it,並從0x90200處開始執行,這ㄍ位址就是setup.Sㄉ啟始處。所以bootsect.S所完成的工作就是將程式從磁碟讀到記憶體後再接著去執行setup.S。
所以若你不作成dos版本ㄉ執行檔,原則上執行ㄉ流程是從bootsect.S開始,但相反ㄉ若作成dos版本ㄉ執行檔則bootsect.S幾乎算是ㄍ廢物,因為mt86+_loader執行完就會跳到setup.S
2.setup.S:這部份就是準備進入保護模式ㄉ一些動作,但它不搞cr0,它用lmsw這ㄍ指令進保護模式,反正道理都一樣,若你有認真去看完setup.S,你會發現它只是單純ㄉ進入保護模式,但這樣ㄉ話也可以在head.S作啊,其實說穿ㄌ它用這ㄍ檔主要是啟用a20位址線,由於這部份方法實作有三個,而它(setup.S)通通作,為什麼?請看我postㄉ文章"Fast A20 和92H,及8042到底是什麼關係";其他(改天再詳述)。
3.head.S:這ㄍ檔案才是最佳男主角‧‧‧待續。

●memtest86+教學 Part1


首先廢話一篇:我一直很渴望能從事bios相關行業,因為我確定很多你我不了解的規範specification是如何實作他們最清楚,即使從事那一行沒有在ami或award、phoenix這種外商大公司,一樣可以學習到很多東西。也因此我的學習格外牛步;但憑藉"新聞挖挖哇"的精神,從一套看似不起眼的軟體,一樣可以精進你的專業,學習memtest86+就等於是在學習如何玩弄記憶體,然而這也是跟底層很近的起點,畢竟資訊軟體業說破ㄌ就真的是在玩弄記憶體,只是玩法不同便衍生出不同的技術、演算法‧‧‧‧‧真不好意思,用"玩弄"兩ㄍ字對自己好像也不是很敬業。
言歸正傳,memtest86+及memtest86有什麼不同,memtest86+發行版本是base on memtest86,並作些修正因此release的版本較慢吧!關於作者Memtest86+ is written by Samuel Demeulemeester ,the original author of memtest86 is Chris Brady 。但"+"就是有加入一些東西ㄉ意思。加入ㄌ什麼,先賣ㄍ關子請往下看‧‧
首先你必須具備什麼技能: 1會使用linux(不用很厲害,只要你可以安裝完成linux,其他慢慢來.)2你要會看懂C及Assembly.3對X86架構要了解(這部份要時間ㄉ累積,以及你棄而不捨ㄉ精神,誰叫你我都是門外漢又找不到高高手來做朋友)。我個人是用Microsoft virtual PC或Sun xVM VirtualBox安裝linux,然後掛起samba sever,在windows用source Insight來編輯code,這樣很方便,也是我ㄍ人的習慣,畢竟windows有些好用的tools,但你若是linux高手就當我是在廢話。以上的畫面是我ㄉvirtual PC 啟動ubuntu linux的畫面。

●Fast A20 和92H,及8042到底是什麼關係

以下是從DOS编程技术這ㄍ大陸博客寫ㄉ資料放ㄌ上來,它裡面還有介紹一些好東西。您可以慢慢參考、品嘗。
早期的PC,控制鍵盤有一個單獨的單片機8042,現如今這個晶片已經給集成到了其它大片子中,但其功能和使用 方法還是一樣,當PC機剛剛出現A20 Gate的時候,估計實在找不到控制它的地方了,同時為這點小事也不值得增加晶片,於是工程師使用這個8042鍵盤控制器來控制A20 Gate,但A20 Gate真的和鍵盤一點關係也沒有,我們先從軟體的角度簡單介紹一下8042這個晶片。
8042有4個寄存器
• 1個8-bit長的Input buffer;Write-Only;
• 1個8-bit長的Output buffer; Read-Only;
• 1個8-bit長的Status Register;Read-Only;
• 1個8-bit長的Control Register;Read/Write。
有兩個埠位址:60h和64h。
• 讀60h埠,讀output buffer
• 寫60h埠,寫input buffer
• 讀64h埠,讀Status Register
對Control Register的操作相對要複雜一些,首先要向64h埠寫一個命令(20h為讀命令,60h為寫命令),然後根據命令從60h埠讀出Control Register的資料或者向60h埠寫入Control Register的資料(64h埠還可以接受許多其它的命令)。
先來看看Status Register的定義,我們後面要用bit 0和bit 1:
bit meaning
-----------------------------------------------------------------------
0 output register (60h) 中有數據
1 input register (60h/64h) 有數據
2 系統標誌(上電重定後被置為0)
3 data in input register is command (1) or data (0)
4 1=keyboard enabled, 0=keyboard disabled (via switch)
5 1=transmit timeout (data transmit not complete)
6 1=receive timeout (data transmit not complete)
7 1=even parity rec'd, 0=odd parity rec'd (should be odd)
除了這些資源外,8042還有3個內部埠:Input Port、Outport Port和Test Port,這三個埠的操作都是通過向64h發送命令,然後在60h進行讀寫的方式完成,其中本文要操作的A20 Gate被定義在Output Port的bit 1上,所以我們有必要對Outport Port的操作及埠定義做一個說明。
• 讀Output Port
向64h發送0d0h命令,然後從60h讀取Output Port的內容
• 寫Output Port
向64h發送0d1h命令,然後向60h寫入Output Port的資料
另外我們還應該介紹兩個命令:
• 禁止鍵盤操作命令
向64h發送0adh
• 打開鍵盤操作命令
向64h發送0aeh
有了這些命令和知識,我們可以考慮操作A20 Gate了,有關8042晶片更詳細的資料,請參考該晶片的Data Sheet。
如何打開和關閉A20 Gate。
理論上講,我們只要操作8042晶片的輸出埠(64h)的bit 1,就可以控制A20 Gate,但實際上,當你準備向8042的輸入緩衝區裡寫資料時,可能裡面還有其它資料沒有處理,所以,我們要首先禁止鍵盤操作,同時等待資料緩衝區中沒 有資料以後,才能真正地去操作8042打開或者關閉A20 Gate。打開A20 Gate的具體步驟大致如下:
1.關閉中斷;
2.等待8042 Input buffer為空;
3.發送禁止鍵盤操作命令;
4.等待8042 Input buffer為空;
5.發送讀取8042 Output Port命令;
6.等待8042 Output buffer有數據;
7.讀取8042 Output buffer,並保存得到的位元組;
8.等待8042 Input buffer為空;
9.發送Write 8042 Output Port命令到8042 Input buffer;
10.等待8042 Input buffer為空;
11.將從8042 Output Port得到的位元組的第2位置1(或清0),然後寫入8042 Input buffer;
12.等待,直到8042 Input buffer為空為止;
13.發送允許鍵盤操作命令到8042 Input buffer;
14. 打開中斷。
下面是完成打開A20 Gate的代碼:
A20Enable:
cli ;1.關閉中斷
call WaitInbufEmpty ;2.等待8042 Input buffer為空;
mov al, 0adh
mov dx, 64h
out dx, al ;3.發送禁止鍵盤操作命令
call WaitInbufEmpty ;4.等待8042 Input buffer為空;
mov al, 0d0h
mov dx, 64h
out dx, al ;5.發送讀取8042 Output Port命令;
call WaitOutbufFull ;6.等待8042 Output buffer有數據;
mov dx, 60h
in al, dx ;7.讀取8042 Output buffer
push ax ;保存讀取的資料
call WaitInbufEmpty ;8.等待8042 Input buffer為空;
mov al, 0d1h
mov dx, 64h
out dx, al ;9.發送寫 8042 Output Port命令
call WaitInbufEmpty ;10.等待8042 Input buffer為空
pop ax
or al, 02h ;11.將從8042 Output Port得到的位元組的bit 1置1
mov dx, 60h
out dx, al ;寫入Output Port
call WaitInbufEmpty ;12.等待8042 Input buffer為空
mov al, 0aeh
mov dx, 64h
out dx, al ;13.發送允許鍵盤操作命令
sti ;開中斷
ret

WaitInbufEmpty:
mov dx, 64h
in al, dx ;讀取Status Register
test al, 02h
jnz WaitInbufEmpty
ret

WaitOutbufFull:
mov dx, 64h
in al, dx
test al, 01 ;讀取Status Register
jz WaitOutbufFull
ret
後來,由於感覺使用8042控制A20運行太慢了(確實,那麼長的代碼,中間還要若干次的wait),所以後來又出現了所謂的Fast A20,實際上,現在的大多數機器都是Fast A20,Fast A20使用92h埠控制A20,同時BIOS裡提供了一個軟中斷來控制A20:
入口:ah=24h
al=0 關閉A20
1 打開A20
2 讀取A20狀態
int 15h
返回:如果BIOS支援此功能,CF=0,否則CF=1
CF=0時,AX返回當前A20狀態,1=打開,0=關閉
像8042中的Output Port中的定義一樣,92h埠的bit 1控制著A20,為1時打開,為0時關閉,從92h中讀一個byte可以看a20的當前狀態,所以對92h的操作如下:
• 讀A20狀態
mov dx, 92h
in al, dx
如果al的bit 1為1表示a20打開,否則為0
• 打開A20
mov dx, 92h
mov al, 02
out dx, al
• 關閉A20
mov dx, 92h
mov al, 0
out dx, al
特別要注意的是,大家從這篇文章的文字中可能也能感覺到,A20 Gate的設計本身就讓人感覺很彆扭,不是那麼流暢,所以和A20有關的事情就難免也會有相同的感覺,很奇怪的是,上面介紹的三種方法並不是在每台機器上 都適用,所以如果你要做一個商務軟體其中要操作A20,那一定要三種方式聯合使用才比較穩妥,否則會有意想不到的結果,LINUX公開的啟動代碼中就是這 麼做的
在DOS下有時我們會在config.sys中寫上一句:dos=high,這句就會把駐留的DOS放到高端記憶體區域去,怎麼放的呢?關鍵點就是打開 A20,然後把DOS從常設記憶體搬到100000h起始的區域去,並把在常設記憶體中佔用的記憶體釋放掉,當然說起來容易,實際做的時候還有很多細節要處理。
說到DOS=HIGH,就不得不提醒大家另一件事,如果在config.sys中有dos=high這一句,那麼恐怕92h的方法和BIOS的方法都會不 完全靈驗(至於操作8042的方法靈不靈我沒有試),這是DOS做了手腳,因為DOS被放到了高端記憶體中,為了保證DOS能正常運行,它不允許你把A20 給關掉,遇到這種情況不要驚慌,不是我寫得不對,確實是DOS太狡猾了。
還有最後一點要特別注意,92h的bit 0是給機器發重定信號的(8042 Output Port的bit 0也是),所以在向92h寫資料時,千萬不要讓bit 0為1,否則機器會重新啟動,如果你的應用程式需要重新開機機器,這也是方法之一,比jmp 0ffffh:0來的還要乾脆。
在其它我們介紹保護模式的文章中,我們會用到上面提到的打開A20的方法,屆時可能就不會做更多的解釋了。
另外,涉及操作A20的資料其實很少,有些資料我手裡也很缺乏,比如92h除bit 0和bit 1以外的定義是什麼我至今也不知道。

更多參考:Gate A20與保護模式

2009年3月18日 星期三

●8042 Keyboard Controller

IO Port 60h為鍵盤的輸入暫存器 、61h為控制暫存器、64h狀態暫存器
8042 - Keyboard Controller
8042 Status Register (port 64h read)
8042 Status Register
Bit0|------output register (60h) has data for system
Bit1|------input register (60h/64h) has data for 8042
Bit2|------system flag (set to 0 after power on reset)
Bit3|------data in input register is command (1) or data (0)
Bit4|------1=keyboard enabled, 0=keyboard disabled (via switch)
Bit5|------1=transmit timeout (data transmit not complete)
Bit6|------1=receive timeout (data transmit not complete)
Bit7|------1=even parity rec'd, 0=odd parity rec'd (should be odd)

Port Mode Description

64h read 8042 status register. Can be read at any time. See table above for more information.
64h write 8042 command register. Writing this port sets Bit3 of the status register to 1 and the byte is treated as a controller command. Devices attached to the 8042 should be disabled before issuing commands that return data since data in the output register will be overwritten.
60h read 8042 output register (should only be read if Bit 0 of status port is set to 1)
60h write 8042 data register. Data should only be written if Bit 1 of the status register is zero (register is empty).
When this port is written Bit 3 of the status register is set to zero and the byte is treated as a data. The 8042 uses this byte if it's expecting data for a previous command, otherwise the data is written directly to the keyboard. See ~KEYBOARD COMMANDS~ for information on
programming the actual keyboard hardware.

8042 Commands Related to PC Systems (Port 64h)

Command Description

20 Read 8042 Command Byte: current 8042 command byte is placed in port 60h.
60 Write 8042 Command Byte: next data byte written to port 60h is placed in 8042 command register. Format:

8042 Command Byte
Bit0|------1=enable output register full interrupt
Bit1|------ should be 0
Bit2|------1=set status register system, 0=clear
Bit3|------ 1=override keyboard inhibit, 0=allow inhibit
Bit4|------disable keyboard I/O by driving clock line low
Bit5|------ disable auxiliary device, drives clock line low
Bit6|------IBM scancode translation 0=AT, 1=PC/XT
Bit7|------reserved, should be 0

A4 Password Installed Test: returned data can be read from port 60h; FA=password installed, F1=no password
A5 Load Security: bytes written to port 60h will be read until a null (0) is found.
A6 Enable Security: works only if a password is already loaded
A7 Disable Auxiliary Interface: sets Bit 5 of command register stopping auxiliary I/O by driving the clock line low
A8 Enable Auxiliary Interface: clears Bit 5 of command register
A9 Auxiliary Interface Test: clock and data lines are tested; results placed at port 60h are listed below:

00 no error
01 keyboard clock line is stuck low
02 keyboard clock line is stuck high
03 keyboard data line is stuck low
04 keyboard data line is stuck high

AA Self Test: diagnostic result placed at port 60h, 55h=OK
AB Keyboard Interface Test: clock and data lines are tested; results placed at port 60h are listed above with command A9
AC Diagnostic Dump: sends 16 bytes of 8042's RAM, current input port state, current output port state and 8042 program status
word to port 60h in scan-code format.
AD Disable Keyboard Interface: sets Bit 4 of command register stopping keyboard I/O by driving the clock line low
AE Enable Keyboard Interface: clears Bit 4 of command register enabling keyboard interface.
C0 Read Input Port: data is read from its input port (which is inaccessible to the data bus) and written to output register at port 60h; output register should be empty before call.

8042 Input Port
Bit0-3|------undefined
Bit4|------ 1=enable 2nd 256K of motherboard RAM, 0=disable
Bit5|------1=manufacturing jumper not installed, 0=installed
Bit6|------ 1=primary display is MDA, 0=primary display is CGA
Bit7|------1=keyboard not inhibited, 0=keyboard inhibited

C1 Poll Input Port Low Bits: Bits 0-3 of port 1 placed in status Bits 4-7
C2 Poll Input Port High Bits: Bits 4-7 of port 1 placed in status Bits 4-7
D0 Read Output Port: data is read from 8042 output port (which is inaccessible to the data bus) and placed in output register;
the output register should be empty. (see command D1 below)
D1 Write Output Port: next byte written to port 60h is placed in the 8042 output port (which is inaccessible to the data bus)

8042 Output Port
Bit0|------system reset line
Bit1|------gate A20
Bit2-3|------undefined
Bit4|------ output buffer full
Bit5|------input buffer empty
Bit6|------keyboard clock (output)
Bit7|------keyboard data (output)

D2 Write Keyboard Output Register: on PS/2 systems the next data
byte written to port 60h input register is written to port 60h
output register as if initiated by a device; invokes interrupt
if enabled
D3 Write Auxiliary Output Register: on PS/2 systems the next data
byte written to port 60h input register is written to port 60h
output register as if initiated by a device; invokes interrupt
if enabled
D4 Write Auxiliary Device: on PS/2 systems the next data byte
written to input register a port at 60h is sent to the
auxiliary device
E0 Read Test Inputs: 8042 reads its T0 and T1 inputs; data is
placed in output register; Bit 0 is T0, Bit 1 is T1:

Test Input Port Bits
Bit0|------keyboard clock
Bit1|------keyboard data

Fx Pulse Output Port: Bits 0-3 of the 8042 output port can be pulsed low for 6 us(微秒);Bits 0-3 of command indicate which Bits should be pulsed; 0=pulse, 1=don't pulse; pulsing
Bit 0 results in CPU reset since it is connected to system reset line.