小華的部落格: 2007/5/6 - 2007/5/13

搜尋此網誌

星期三, 5月 09, 2007

Direct Hardware Access Under Windows 9x/NT/2000/XP/Vista

今天又多學一樣東西,就是呼叫Undocument API 去開啟I/O 存取權限,這樣子你就可以使用Ring3的應用程式直接存取硬體,例如下面的VC++範例程式中直接使用Assembly 或是IO Funcation call去存取硬體。

EX1: Read I/O Port
_asm {
pushf
mov al, ireg.h
mov dx,0x70
out dx,al
mov dx,0x71
in al, dx
mov mbid, al
popf
}

EX2: Read CMOS
_outp(0x70,ireg.h );
mbid=_inp(0x71);

使用的技巧是先進入Ring0 (也就是掛一個Driver),然後在DriverEntry 時去呼叫API去改變IO權限。
void Ke386SetIoAccessMap(int, IOPM *);
void Ke386QueryIoAccessMap(int, IOPM *);
void Ke386IoSetAccessProcess(PEPROCESS, int);

開剛始是利用修改TSS的來開啟IO存取權限方式,不過改完後DEBUG.COM會無法執行,所以才改成呼叫上面的API的方式,而直接修改TSS方式如下所示:
__asm {
cli // 遮罩中斷
sgdt gdtr // 得到 GDT 基底位址與段界限
str TSSseg // 得到 TSS 選擇子
movzx esi,TSSseg // 擴展到 ESI 中以便計算
add esi,gdtr.dwBase // 得到 TSS 在 GDT 中描述符
mov gdt,esi
}
不管是API還是修改TSS,都是在DriverEntery進去後去執行的。

因此實作部份分成三部份:
1.WDM Driver code(DriverEntery 的地方呼叫API)
2.InstallDriver code(應用程式執行時去動態載入驅動程式)
3.應用程式Application code(直接使用Assembly或是IO Function)

星期二, 5月 08, 2007

Windows 下的I/O存取

昨天我們Leader 請我幫忙寫一個Tools給對岸同胞的生產線使用,他們需要在Vista 底下去存取Panel ID.
所以自己留下個撰寫紀錄,讓自己下次修改自己的Source Code的時候有個依據。

目前Panel ID我們是使用跳線的方式去改變GPIO值來做辨別,所以只要去存取GPIO就可以了。

1.南橋ICH8會把GPIO 值 mapped 到某個IO Range...所以只要利用組合語言的IN/OUT 去存取他映對過去的IO 位址就可以知道GPIO的值。
2.查看主機板電路圖,看實際上被當成選擇Panel ID的GPIO是哪幾支接腳。
3.開發一個DOS版的Tools直接使用IN/OUT指令去存取,例如下面範例:
byte ReadGpioStatus(word OEM_GPIO_ADDR)
{
byte mbid;
...
_asm {
mov dx, OEM_GPIO_ADDR
in ax, dx
mov mbid, al
}
...
return mbid;
}
4.開發一個Windows 版的Tools也是使用IN/OUT指令,不過是由自己撰寫的I/O Driver所提供
byte ReadGpioStatus(word OEM_GPIO_ADDR)
{
byte mbid;
mbid=IN_B(OEM_GPIO_ADDR); //IN_B 會去呼叫Windows API然後送IRP給我自己寫的Driver
return mbid;
}
5.在Source code內做一個介面IN_B() 然後呼叫API去使用自己寫的I/O Driver所提供的服務
byte IN_B (WORD port)
{
ireg.x.DX = port;
...
bRc = DeviceIoControl (...); //送IRP給我自己寫的I/O Driver...
...
return (ireg.h.AL);
}
6.整個Windows版本的Source code架構與DOS版本差異不大,只是Windows版本需要去動態載入驅動程式,而這部分寫在if (!InitializeWinIo()) return 1 ;

上述這幾個部份是比較重要的地方,而整個DOS版與Windows版的程式架構如下:
int main(int argc,char *argv[])
{
modelString[]={
"000"," Reserved. ",
"001"," Vendor1. ",
"002"," Vendor2. ",
....
}
...
if (!InitializeWinIo()) return 1 ; // For Windows version only.
...
pStatus1=ReadGpioStatus(OEM_GPIO_DEFAULT_IO1); // Read Gpio
pStatus2=ReadGpioStatus(OEM_GPIO_DEFAULT_IO2);
...
panelid=(GetBit(pStatus1,2)<<2)+(GetBit(pStatus1,6)<<1)+GetBit(pStatus2,6);
...
if (strcmp(argv[2], modelString[panelid*2]))
{
...// if match...return passed!
}
return 0;
}

從上面範例可以知道其實DOS版與Windows版的Source code 寫法幾乎一樣,只是Windows版因為無法直接存取I/O,所以我自己多加了一個I/O Driver,然後做了兩個Function (IN_B()跟InitializeWinIo())來載入與使用這個Driver 。

星期一, 5月 07, 2007

SEGMENT與Public/Extern 用法

在BIOS Source code之中常常會看到一大堆的Segment,而有時候相同的Segment內去使用同一個Segment內的程序時,會在Segment 內去把他Extern進來,如下範例所示:

Test1.asm
AAA SEGMENT <--AAA Segment 裡面有一個BBB程序被共用(Public)
....
BBB PROC NEAR PUBLIC
...
BBB ENDP
....
AAA ENDS

Test2.asm內容如下
AAA SEGMENT <--AAA Segment內有一個CCC程序要去呼叫BBB程序,但是要在AAA Segment把BBB程式用Extern方式拉進來
EXTERN BBB:NEAR
....
CCC PROC
...
CALL BBB
...
CCC ENDP
AAA ENDS

Public 是把程序共用,而Extern 可看作要使用一個共用的程序
一般來說如果BBB程序與CCC程序都在同一個檔案內,那麼CCC程序可以直接呼叫BBB程序而不需要Extern
而不同"檔案"時就要這樣做,如上面範例是Test1.asm 跟Test2.asm

Strong/Weak 語法與"::"標籤表示法

組合語言中的Strong/Weak 語法簡單說就是如果A存在就跳躍過去A執行,如果A不存在就跳到B去執行
假如我有一段程式如下面所示:

PUBLIC BBB&Return
EXTERN AAA(BBB&Return):NEAR <--Strong/weak寫法

Test1 Proc
...
Jmp AAA
...
BBB&Return:: <--如果AAA不存在會跳到這邊執行
ret
Test1 EndP

AAA Proc <--如果AAA存在會跳到這邊執行
...
ret
AAA EndP

除了上面的Strong/Weak之外,還可以看到標籤是BBB&Return:: 而"::"代表的意思有兩種:
1.PUBLIC 出來的標籤,要把它標示成"::",簡單說就是不同程序間也可以跳躍
2."::"不同程序間的跳躍,如下面範例所示:
[註]功能還是跟 ":"一樣,只是表示方式不同而已。

Public Label2
Test1 proc near

Label1:

Label2::

ret
Test1 endp

Test2 proc near

jmp Label1 ; compiler 會錯誤, 因為該 label 找不到

jmp Label2 ; 正確, 該 label2 是可以找到的,因為有把他Public

ret
Test2 endp

星期日, 5月 06, 2007

BIOS Code Entry point

前一篇說明了CPU如何去讀取第一條存在於BIOS ROM裡面的程式碼,現在就說明一下實際上BIOS Code是怎樣跑的。

這邊要重新說明一下觀念:
FFFF_FFF0 是第一條指令所存放的地方,而他是在4G頂端的地方,CPU在被重置(Reset)之後會去這邊讀取指令,而對CPU來說並不會在意Address FFFF_FFF0 是給BIOS ROM使用還是記憶體DRAM使用,而80386之後,這個位址是都給BIOS ROM使用。

000F_FFF0 (或表示成F000:FFF0),這個位址是在1M 頂端的地方,而這個位址是給BIOS ROM還是DRAM?
給誰使用則是去設定北橋,一般來說在POST 前(Power On-->BootBlock),這個地方應該是給BIOS ROM使用,而POST中途會去做Shadow動作,這個動作做完後,這個位址會給DRAM使用。

以前8088/8086 在1M頂端是給BIOS ROM使用,後來才變成BIOS ROM/DRAM 混用,所以以前CPU被重置的時候是去F_FFF0(F000:FFF0)讀取第一條指令,後來的CPU就不這樣做了。

上面觀念說明完之後我們就實際追蹤一下BIOS code的流程;在進入到OS之後,如果你去看000F_FFF0的資料,其實是看到DRAM裡面的資料,而這個資料是BIOS放進去的;而進入OS之前,這個位址的資料因為預設是給BIOS ROM使用,所以會跟FFFF_FFF0看到的資料一模一樣。

下面是實際上我們去追蹤P廠商BIOS code的範例:
1.我們先利用一些工具去在OS中去存取4G頂端的位址FFFF_FFF0 看到的資料如下所示:
FFFF_FFF0 E9 55 B2 ........... -->實際在BIOS ROM裡面的資料,因為這個位址是給BIOS ROM使用

上面看到的E9 55 B2 就是CPU讀取的第一條指令,而這個指令是一個Jmp 指令,他會跳到BootBlock Entry Point去執行,執行完畢後才會跳到POST Entry point.

2.下面也是在OS中利用工具去讀取1M頂端的資料,由於已經進入OS了,所以這些資料是BIOS填進去DRAM的資料(Shadow),也就是說這個000F_FFF0是被指向DRAM之中。

F000:FFF0 EA 5B E0 00 F0 ..... -->跳到F000:E05B
F000:E05B E9 FB A8 ........... -->跳到實際的POST Entry point....
A8FB+E05E=8959 (進位1 去掉)
F000:8959 FA ................. -->FA=CLI

這邊可以看到為了相容性問題,所以000F_FFF0是放著Jmp F000:E05B 位址,而這個位址裡面會放著POST Entry point指標(E9 FB A8),所以繼續追蹤過去後可以發現POST的第一條指令是CLI...

3.其實CPU第一條指令E9 55 B2 會跳到BootBlock去執行;而執行完畢後,BootBlock也會跳到跟F000:8959內一樣的BIOS code去執行所謂的POST。

所以簡單的描述上面所說的流程:
1.CPU去FFFF_FFF0 提取第一條指令執行(Jmp BootBlock)
2.執行BootBlock
3.執行完bootBlock後,跳到POST
4.執行POST (POST第一條指令一定是CLI)
5.POST過程中Shadow ,Shadow後000F_FFF0的資料就是Jmp F000:E05B

這樣子說應該很容易瞭解了吧! ^^Y