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產業的最大宗,然而我相信搞好這一塊,要跳到其它嵌入式系統應該熟悉度會加快。雖說技術一日千里,但只要你夠耐心及細心,我想你也可以日進萬里(內心ㄉ成長)。
--> 閱讀更多...

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。
--> 閱讀更多...

●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_ADR :0xb8000,這個位址就是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!
--> 閱讀更多...