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

●讀網卡MAC Address有這麼難嗎?


或許你知道ipconfig,linux:ifconfig;但是很抱歉,我不想在有os的狀況讀出這些資訊,更不想拆下eeprom直接讀取IC。說穿了就是我現在工作遇上的瀕頸(不知這兩ㄍ字有沒有打錯,好久沒用)因為我知道bios有提供int 1ah中斷,從這部分我們可以去找出你想要的pci device的PFA,再從PFA中透過bus、device、function去找出真正你想找到的device,再從device中的BAR(Base Address Register)中找出你要的io port這樣就可以從port讀出你要的mac address。很幸運也很不幸rtl8139、rtl8168讀出來都正確無誤,不管是內建網卡還是外插螃蟹卡都可以,但是問題來ㄌ,Marvell Yukon的網卡卻讓我失去信心,原因是它是memory mappied而不是I/O mappied,還是說它是PCI-e的Device,不好意思,我還不確定,若你是高手高高手,煩請指點迷津。‧‧‧‧‧‧迷途羔羊--------Benson,也有人稱呼我老B,雖然這兩ㄍ字有點不雅。


這個問題終於搞定,感謝jajamboliao的熱心指導。現在可以放下它ㄌ,若沒有高人指點,上面那張圖我已經跑了3個loop,結果每次放下它,都是因為處理不了它;now;如釋重負。
--> 閱讀更多...

●Borland C++3.1 Compile時無法辨認內嵌組合語言的32bit register




這部分ㄉ解決辦法就是要設定兩ㄍ地方: 1.Options/Compiler/Code generation/Compile via assembler 2.Options/Compiler/Advanced code generation/80386 搞定。
--> 閱讀更多...

●learnning assembly

Iczelion's Win32 Assembly Homepage
小木偶

組合語言程式設計

還有很多‧‧‧‧但是希望我們都是一步一腳印的人。
--> 閱讀更多...

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


以前寫AP知道有保護模式這玩意,但壓根想也想不到我也要碰這一塊;若你想往下看,我想你ㄉ功力一定深不見底,若你對這些和我以前一樣還搞的很模糊,那建議你去買本書先:"自己動手寫作業系統";于淵編著。把它的第三章搞懂你就成大功立大業ㄌ。當然若你是大內高手,每天都和這些玩意兒打混,那這裡你可以來點建議吧!
以下資料是大陸網站找到的,可以先暖暖身子:但因為資料太多,所以真正ㄉ保護模式都還沒有進入主題,先SAY SORRY,下回就會有●中斷、保護模式、分頁‧‧真是夠ㄌ___PART2,請奈心等待‧‧‧
初級接觸ASM者學習資料(1)
組合語言和CPU以及記憶體,埠等硬體知識是連在一起的. 這也是為什麼組合語言沒有通用性的原因.
下面簡單講講基本知識(針對INTEL x86及其兼容機) ============================ x86組合語言的指令,其操作物件是CPU上的寄存器,系統記憶體,或者立即數. 有些指令表面上沒有運算元, 或者看上去缺少運算元, 其實該指令有內定的操作物件, 比如push指令, 一定是對SS:ESP指定的記憶體操作, 而cdq的操作物件一定是eax / edx. 在組合語言中,寄存器用名字來訪問. CPU 寄存器有好幾類, 分別有不同的用處: 1. 通用寄存器: EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(這個雖然通用,但很少被用做除了堆疊指標外的用途) 這些32位可以被用作多種用途,但每一個都有"專長". EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器. EBX 是"基底位址"(base)寄存器, 在記憶體定址時存放基底位址. ECX 是計數器(counter), 是重複(REP)首碼指令和LOOP指令的內定計數器. EDX是...(忘了..哈哈)但它總是被用來放整數除法產生的餘數. 這4個寄存器的低16位可以被單獨訪問,分別用AX,BX,CX和DX. AX又可以單獨訪問低8位(AL)和高8位(AH), BX,CX,DX也類似. 函數的返回值經常被放在EAX中. ESI/EDI分別叫做"源/目標索引寄存器"(source/destination index),因為在很多字串操作指令中, DS:ESI指向源串,而ES:EDI指向目標串. EBP是"基址指標"(BASE POINTER), 它最經常被用作高階語言函數調用的"框架指標"(frame pointer). 在破解的時候,經常可以看見一個標準的函數起始代 碼: push ebp ;保存當前ebp mov ebp,esp ;EBP設為當前堆疊指標 sub esp, xxx ;預留xxx位元組給函數臨時變數....
這樣一來,EBP 構成了該函數的一個框架, 在EBP上方分別是原來的EBP, 返回地址和參數. EBP下方則是臨時變數. 函數 返回時作 mov esp,ebp/pop ebp/ret 即可. ESP 專門用作堆疊指標. 2. 段寄存器: CS(Code Segment,代碼段) 指定 當前執行的代碼段. EIP (Instruction pointer, 指令指標)則指向該段中一個具體的指令. CS:EIP指向哪個指令, CPU 就執行它. 一般只能用jmp, ret, jnz, call 等指令來改變程式流程,而不能直接對它們賦值. DS(DATA SEGMENT, 資料段) 指定一個資料段. 注意:在當前的電腦系統中, 代碼和資料沒有本質差別, 都是一串二進位數字, 區別只在於 你如何用它. 例如, CS 制定的段總是被用作代碼, 一般不能通過CS指定的地址去修改該段. 然而,你可以為同一個段 申請一個資料段描述符"別名"而通過DS來訪問/修改. 自修改代碼的程式常如此做. ES,FS,GS 是輔助的段寄存器, 指定 附加的資料段. SS(STACK SEGMENT)指定當前堆疊段. ESP 則指出該段中當前的堆疊頂. 所有push/pop 系列指令都只對SS:ESP指出的位址進行操作. 3. 標誌寄存器(EFLAGS): 該暫存器有32位元,組合了各個系統標誌. EFLAGS一般不作為整 體訪問, 而只對單一的標誌位元感興趣. 常用的標誌有: 進位元標誌C(CARRY), 在加法產生進位或減法有借位時置1, 否 則為0. 零標誌Z(ZERO), 若運算結果為0則置1, 否則為0 符號位元S(SIGN), 若運算結果的最高位置1, 則該位也置1. 溢 出標誌O(OVERFLOW), 若(帶符號)運算結果超出可表示範圍, 則置1. JXX 系列指令就是根據這些標誌來決定是否要跳轉,
從而實現條件分枝. 要注意,很多JXX 指令是等價的, 對應相同的機器碼. 例如, JE 和JZ 是一樣的,都是當Z=1是跳轉.只有JMP 是無條件跳轉. JXX 指令分為兩組, 分別用於無符號操作和帶符號操作. JXX 後面的"XX" 有如下字母: 無符號 操作: 帶符號操作: A = "ABOVE", 表示"高於" G = "GREATER", 表示"大於" B = ELOW", 表示"低於" L = "LESS", 表 示"小於" C = "CARRY", 表示"進位"或"借位" O = "OVERFLOW", 表示"溢出" S = "SIGN", 表示"負" 通用符號: E = "EQUAL" 表示"等於", 等價於Z (ZERO) N = "NOT" 表示"非", 即標誌沒有置位元. 如JNZ "如果Z沒有置位則跳轉" Z = "ZERO", 與E同. 如果仔細想一想,就會發現 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .... 4. 埠埠是直接和外部設備通訊的地方。外設接入系統後,系統就會把外設的資料介面映射到特定的埠位址空間,這樣,從該埠讀入資料就是 從外設讀入資料,而向外設寫入資料就是向埠寫入資料。當然這一切都必須遵循外設的工作方式。埠的位址空間與記憶體位 址空間無關,系統總共提供對64K個8位埠的訪問,編號 0-65535. 相鄰的8位埠可以組成成一個16位埠,相鄰的16位埠可以組 成一個32位埠。埠輸入輸出由指令IN,OUT,INS和OUTS實現,具體可參考組合語言書籍。
初級接觸ASM者學習資料(2)

ASM指令的運算元可以是記憶體中的資料, 如何讓程式從記憶體中正確取得所需要的資料就是對記憶體的定址.
INTEL 的CPU 可以工作在兩種定址模式:實模式和保護模式. 前者已經過時,就不講了, WINDOWS 現在是32位元保護模式的系統, PE 檔就基本是運行在一個32位元線性位址空間, 所以這裏就只介紹32位元線性空間的定址方式.
其實線性位址的概念是很直觀的, 就想像一系列位元組排成一長隊,第一個位元組編號為0, 第二個編號位1, .... 一直到4294967295(十六進位FFFFFFFF,這是32位二進位數字所能表達的最大值了). 這已經有4GB的容量! 足夠容納一個程式所有的代碼和資料. 當然, 這並不表示你的機器有那麼多記憶體. 實體記憶體的管理和分配是很複雜的內容, 初學者不必在意, 總之, 從程式本身的角度看, 就好象是在那麼大的記憶體中.
在INTEL系統中, 記憶體位址總是由"段選擇符:有效位址"的方式給出.段選擇符(SELECTOR)存放在某一個段寄存器中, 有效位址則可由不同的方式給出. 段選擇符通過檢索段描述符確定段的起始位址, 長度(又稱段限制), 粒度, 存取許可權, 訪問性質等. 先不用深究這些, 只要知道段選擇符可以確定段的性質就行了. 一旦由選擇符確定了段, 有效位址相對于段的基底位址開始算. 比如由選擇符1A7選擇的資料段, 其基底位址是400000, 把1A7 裝入DS中, 就確定使用該資料段. DS:0 就指向線性位址400000. DS:1F5278 就指向線性位址5E5278. 我們在一般情況下, 看不到也不需要看到段的起始位址, 只需要關心在該段中的有效位址就行了. 在32位元系統中, 有效位址也是由32位元數字表示, 就是說, 只要有一個段就足以涵蓋4GB線性位址空間, 為什麼還要有不同的段選擇符呢? 正如前面所說的, 這是為了對資料進行不同性質的訪問. 非法的訪問將產生異常中斷, 而這正是保護模式的核心內容, 是構造優先順序和多工系統的基礎. 這裏有涉及到很多深層的東西, 初學者先可不必理會.
有效位址的計算方式是: 基址+間址*比例因數+偏移量. 這些量都是指段內的相對于段起始位址的量度, 和段的起始位址沒有關係. 比如, 基址=100000, 間址=400, 比例因數=4, 偏移量=20000, 則有效位址為:
100000+400*4+20000=100000+1000+20000=121000. 對應的線性位址是400000+121000=521000. (注意, 都是十六進位數).
基址可以放在任何32位通用寄存器中, 間址也可以放在除ESP外的任何一個通用寄存器中. 比例因數可以是1, 2, 4 或8. 偏移量是立即數. 如: [EBP+EDX*8+200]就是一個有效的有效位址運算式. 當然, 多數情況下用不著這麼複雜, 間址,比例因數和偏移量不一定要出現.
記憶體的基本單位是位元組(BYTE). 每個位元組是8個二進位位元, 所以每個位元組能表示的最大的數是11111111, 即十進位的255. 一般來說, 用十六進位比較方便, 因為每4個二進位位元剛好等於1個十六進位位, 11111111b = 0xFF. 記憶體中的位元組是連續存放的, 兩個位元組構成一個字(WORD), 兩個字構成一個雙字(DWORD). 在INTEL架構中, 採用small endian格式, 即在記憶體中,高位位元組在低位元位元組後面. 舉例說明:十六進位數803E7D0C, 每兩位元是一個位元組, 在記憶體中的形式是: 0C 7D 3E 80. 在32位寄存器中則是正常形式,如在EAX就是803E7D0C. 當我們的形式位址指向這個數的時候,實際上是指向第一個位元組,即0C. 我們可以指定訪問長度是位元組, 字或者雙字. 假設DS:[EDX]指向第一個位元組0C:
mov AL, byte ptr DS:[EDX] ;把位元組0C存入AL
mov AX, word ptr DS:[EDX] ;把字7D0C存入AX
mov EAX, dword ptr DS:[EDX] ;把雙字803E7D0C存入EAX
在段的屬性中,有一個就是缺省訪問寬度.如果缺省訪問寬度為雙字(在32位元系統中經常如此),那麼要進行位元組或字的訪問,就必須用byte/word ptr顯式地指明.
缺省段選擇:如果指令中只有作為段內偏移的有效位址,而沒有指明在哪一個段裏的時候,有如下規則:
如果用ebp和esp作為基址或間址,則認為是在SS確定的段中;
其他情況,都認為是在DS確定的段中。
如果想打破這個規則,就必須使用段超越首碼。舉例如下:
mov eax, dword ptr [edx] ;缺省使用DS,把DS:[EDX]指向的雙字送入eax
mov ebx, dword ptr ES:[EDX] ;使用ES:段超越首碼,把ES:[EDX]指向的雙字送入ebx
堆疊:
堆疊是一種資料結構,嚴格地應該叫做“棧”。“堆”是另一種類似但不同的結構。SS 和 ESP 是INTEL對棧這種資料結構的硬體支援。push/pop指令是專門針對棧結構的特定操作。SS指定一個段為棧段,ESP則指出當前的棧頂。push xxx 指令作如下操作:
把ESP的值減去4;
把xxx存入SS:[ESP]指向的記憶體單元。
這樣,esp的值減小了4,並且 SS:[ESP]指向新壓入的xxx. 所以棧是“倒著長”的,從高位址向低位址方向擴展。pop yyy 指令做相反的操作,把SS:[ESP]指向的雙字送到yyy指定的寄存器或記憶體單元,然後把esp的值加上4。這時,認為該值已被彈出,不再在棧上了,因為它雖然還暫時存在在原來的棧頂位置,但下一個push操作就會把它覆蓋。因此,在棧段中位址低於esp的記憶體單元中的資料均被認為是未定義的。
最後,有一個要注意的事實是,組合語言是面向機器的,指令和機器碼基本上是一一對應的,所以它們的實現取決於硬體.有些看似合理的指令實際上是不存在的,比如:
mov DS:[edx], ds:[ecx] ;記憶體單元之間不能直接傳送
mov DS, 1A7 ;段寄存器不能直接由立即數賦值
mov EIP, 3D4E7 ;不能對指令指標直接操作.
初級接觸ASM者學習資料(3)

“ 組合語言”作為一門語言,對應於高階語言的編譯器,我們需要一個“ASM器”來把組合語言原文件ASM成機器可執行的代碼。高級的ASM器如MASM, TASM等等為我們寫組合語言程式提供了很多類似於高階語言的特徵,比如結構化、抽象等。在這樣的環境中編寫的組合語言程式,有很大一部分是面向ASM器的虛擬指令,已經類同於高階語言。現在的ASM環境已經如此高級,即使全部用組合語言來編寫windows的應用程式也是可行的,但這不是組合語言的長處。組合語言的長處在於編寫高效且需要對機器硬體精確控制的程式。而且我想這裏的人學習ASM的目的多半是為了在破解時看懂反編譯代碼,很少有人真的要拿組合語言編程式吧?(汗......)

好了,言歸正傳。大多數組合語言書都是面向組合語言編程的,我的帖是面向機器和反編譯的,希望能起到相輔相成的作用。有了前面兩篇的基礎,組合語言書上對大多數指令的介紹應該能夠看懂、理解了。這裏再講一講一些常見而操作比較複雜的指令。我這裏講的都是機器的硬指令,不針對任何ASM器。

無條件轉移指令jmp:

這種跳轉指令有三種方式:短(short),近(near)和遠(far)。短是指要跳至的目標位址與當前位址前後相差不超過128位元組。近是指跳轉的目標位址與當前位址在用一個段內,即CS的值不變,只改變EIP的值。遠指跳到另一個代碼段去執行,CS/EIP都要改變。短和近在編碼上有所不同,在ASM指令中一般很少顯式指定,只要寫 jmp 目標位址,幾乎任何ASM器都會根據目標位址的距離採用適當的編碼。遠轉移在32位元系統中很少見到,原因前面已經講過,由於有足夠的線性空間,一個程式很少需要兩個代碼段,就連用到的系統模組也被映射到同一個位址空間。

jmp的運算元自然是目標位址,這個指令支援直接定址和間接定址。間接定址又可分為寄存器間接定址和記憶體間接定址。舉例如下(32位元系統):

jmp 8E347D60 ;直接定址段內跳轉
jmp EBX ;寄存器間接定址:只能段內跳轉
jmp dword ptr [EBX] ;記憶體間接定址,段內跳轉
jmp dword ptr [00903DEC] ;同上
jmp fward ptr [00903DF0] ;記憶體間接定址,段間跳轉

解釋:
在32位元系統中,完整目標位址由16位元段選擇子和32位偏移量組成。因為寄存器的寬度是32位,因此寄存器間接定址只能給出32位偏移量,所以只能是段內近轉移。在記憶體間接定址時,指令後面是方括號內的有效位址,在這個位址上存放跳轉的目標位址。比如,在[00903DEC]處有如下資料:7C 82 59 00 A7 01 85 65 9F 01

記憶體位元組是連續存放的,如何確定取多少作為目標位址呢?dword ptr 指明該有效位址指明的是雙字,所以取
0059827C作段內跳轉。反之,fward ptr 指明後面的有效位址是指向48位完全位址,所以取19F:658501A7 做遠跳轉。

注意:在保護模式下,如果段間轉移涉及優先順序的變化,則有一系列複雜的保護檢查,現在可不加理會。將來等各位功力提升以後可以自己去學習。

條件轉移指令jxx:只能作段內轉移,且只支援直接定址。

=========================================
調用指令CALL:

Call的定址方式與jmp基本相同,但為了從副程式返回,該指令在跳轉以前會把緊接著它的下一條指令的位址壓進堆疊。如果是段內調用(目標位址是32位元偏移量),則壓入的也只是一個偏移量。如果是段間調用(目標位址是48位元全位址),則也壓入下一條指令的完全位址。同樣,如果段間轉移涉及優先順序的變化,則有一系列複雜的保護檢查。

與之對應retn/retf指令則從副程式返回。它從堆疊上取得返回位址(是call指令壓進去的)並跳到該位址執行。retn取32位偏移量作段內返回,retf取48位全地址作段間返回。retn/f 還可以跟一個立即數作為運算元,該數實際上是從堆疊上傳給副程式的參數的個數(以字計)返回後自動把堆疊指標esp加上指定的數*2,從而丟棄堆疊中的參數。這裏具體的細節留待下一篇講述。

雖然call和ret設計為一起工作,但它們之間沒有必然的聯繫。就是說,如果你直接用push指令向堆疊中壓入一個數,然後執行ret,他同樣會把你壓入的數作為返回位址,而跳到那裏去執行。這種非正常的流程轉移可以被用作反跟蹤手段。
==========================================
中斷指令INT n

在保護模式下,這個指令必定會被作業系統截獲。在一般的PE程式中,這個指令已經不太見到了,而在DOS時代,中斷是調用作業系統和BIOS的重要途徑。現在的程式可以文質彬彬地用名字來調用windows功能,如 call user32!getwindowtexta。從程式角度看,INT指令把當前的標誌寄存器先壓入堆疊,然後把下一條指令的完全位址也壓入堆疊,最後根據運算元n來檢索“中斷描述符表”,試圖轉移到相應的中斷服務程式去執行。通常,中斷服務程式都是作業系統的核心代碼,必然會涉及到優先順序轉換和保護性檢查、堆疊切換等等,細節可以看一些高級的教程。

與之相應的中斷返回指令IRET做相反的操作。它從堆疊上取得返回位址,並用來設定CS:EIP,然後從堆疊中彈出標誌寄存器。注意,堆疊上的標誌寄存器值可能已經被中斷服務程式所改變,通常是進位元標誌C, 用來表示功能是否正常完成。同樣的,IRET也不一定非要和INT指令對應,你可以自己在堆疊上壓入標誌和位址,然後執行IRET來實現流程轉移。實際上,多工作業系統常用此伎倆來實現任務轉換。

廣義的中斷是一個很大的話題,有興趣可以去查閱系統設計的書籍。

============================================
裝入全指標指令LDS,LES,LFS,LGS,LSS

這些指令有兩個運算元。第一個是一個通用寄存器,第二個運算元是一個有效位址。指令從該位址取得48位全指標,將選擇符裝入相應的段寄存器,而將32位偏移量裝入指定的通用寄存器。注意在記憶體中,指標的存放形式總是32位偏移量在前面,16位選擇符在後面。裝入指標以後,就可以用DS:[ESI]這樣的形式來訪問指標指向的資料了。

============================================
字串操作指令

這裏包括CMPS,SCAS,LODS,STOS,MOVS,INS和OUTS等。這些指令有一個共同的特點,就是沒有顯式的運算元,而由硬體規定使用DS:[ESI]指向源字串,用ES:[EDI]指向目的字串,用AL/AX/EAX做暫存。這是硬體規定的,所以在使用這些指令之前一定要設好相應的指針。
這裏每一個指令都有3種寬度形式,如CMPSB(位元組比較)、CMPSW(字比較)、CMPSD(雙字比較)等。
CMPSB:比較源字串和目標字串的第一個字元。若相等則Z標誌置1。若不等則Z標誌置0。指令執行完後,ESI 和EDI都自動加1,指向源/目標串的下一個字元。如果用CMPSW,則比較一個字,ESI/EDI自動加2以指向下一個字。
如果用CMPSD,則比較一個雙字,ESI/EDI自動加4以指向下一個雙字。(在這一點上這些指令都一樣,不再贅述)
SCAB/W/D 把AL/AX/EAX中的數值與目標串中的一個字元/字/雙字比較。
LODSB/W/D 把源字串中的一個字元/字/雙字送入AL/AX/EAX
STOSB/W/D 把AL/AX/EAX中的直送入目標字串中
MOVSB/W/D 把源字串中的字元/字/雙字複製到目標字串
INSB/W/D 從指定的埠讀入字元/字/雙字到目標字串中,埠號碼由DX寄存器指定。
OUTSB/W/D 把源字串中的字元/字/雙字送到指定的埠,埠號碼由DX寄存器指定。

串操作指令經常和重複首碼REP和迴圈指令LOOP結合使用以完成對整個字串的操作。而REP首碼和LOOP指令都有硬體規定用ECX做迴圈計數器。舉例:

LDS ESI,SRC_STR_PTR
LES EDI,DST_STR_PTR
MOV ECX,200
REP MOVSD

上面的代碼從SRC_STR拷貝200個雙字到DST_STR. 細節是:REP首碼先檢查ECX是否為0,若否則執行一次MOVSD,ECX自動減1,然後執行第二輪檢查、執行......直到發現ECX=0便不再執行MOVSD,結束重複而執行下麵的指令。


LDS ESI,SRC_STR_PTR
MOV ECX,100
LOOP1:
LODSW
.... (deal with value in AX)

LOOP LOOP1
.....

從SRC_STR處理100個字。同樣,LOOP指令先判斷ECX是否為零,來決定是否迴圈。每迴圈一輪ECX自動減1。

REP和LOOP 都可以加上條件,變成REPZ/REPNZ 和 LOOPZ/LOOPNZ. 這是除了ECX外,還用檢查零標誌Z. REPZ 和LOOPZ在Z為1時繼續迴圈,否則退出迴圈,即使ECX不為0。REPNZ/LOOPNZ則相反。

初級接觸ASM者學習資料(4)

高階語言程式的ASM解析

在高階語言中,如C和PASCAL等等,我們不再直接對硬體資源進行操作,而是面向於問題的解決,這主要體現在資料抽象化和程式的結構化。例如我們用變數名來存取資料,而不再關心這個資料究竟在記憶體的什麼地方。這樣,對硬體資源的使用方式完全交給了編譯器去處理。不過,一些基本的規則還是存在的,而且大多數編譯器都遵循一些規範,這使得我們在閱讀反編譯代碼的時候日子好過一點。這裏主要講講ASM代碼中一些和高階語言對應的地方。

1. 普通變數。通常聲明的變數是存放在記憶體中的。編譯器把變數名和一個記憶體位址聯繫起來(這裏要注意的是,所謂的“確定的位址”是對編譯器而言在編譯階段算出的一個臨時的位址。在連接成可執行檔並載入到記憶體中執行的時候要進行重定位等一系列調整,才生成一個即時的記憶體位址,不過這並不影響程式的邏輯,所以先不必太在意這些細節,只要知道所有的函數名字和變數名字都對應一個記憶體的位址就行了),所以變數名在ASM代碼中就表現為一個有效位址,就是放在方括號中的運算元。例如,在C檔中聲明:

int my_age;

這個整型的變數就存在一個特定的記憶體位置。語句 my_age= 32; 在反ASM代碼中可能表現為:

mov word ptr [007E85DA], 20

所以在方括號中的有效位址對應的是變數名。又如:

char my_name[11] = "lianzi2000";

這樣的說明也確定了一個位址,對應於my_name. 假設位址是007E85DC,則記憶體中[007E85DC]='l',[007E85DD]='i', etc. 對my_name的訪問也就是對這位址處的資料訪問。

指標變數其本身也同樣對應一個位址,因為它本身也是一個變數。如:

char *your_name;

這時也確定變數"your_name"對應一個記憶體位址,假設為007E85F0. 語句your_name=my_name;很可能表現為:

mov [007E85F0], 007E85DC ;your_name的內容是my_name的位址。

2. 寄存器變數

在C和C++中允許說明寄存器變數。register int i; 指明i是寄存器存放的整型變數。通常,編譯器都把寄存器變數放在esi和edi中。寄存器是在cpu內部的結構,對它的訪問要比記憶體快得多,所以把頻繁使用的變數放在寄存器中可以提高程式執行速度。

3. 陣列

不管是多少維的陣列,在記憶體中總是把所有的元素都連續存放,所以在記憶體中總是一維的。例如,int i_array[2][3]; 在記憶體確定了一個位址,從該位址開始的12個位元組用來存貯該陣列的元素。所以變數名i_array對應著該陣列的起始位址,也即是指向陣列的第一個元素。存放的順序一般是i_array[0][0],[0][1],[0][2],[1][0],[1][1],[1][2] 即最右邊的下標變化最快。當需要訪問某個元素時,程式就會從多維索引值換算成一維索引,如訪問i_array[1][1],換算成記憶體中的一維索引值就是1*3+1=4.這種換算可能在編譯的時候就可以確定,也可能要到執行時才可以確定。無論如何,如果我們把i_array對應的位址裝入一個通用寄存器作為基址,則對陣列元素的訪問就是一個計算有效位址的問題:

; i_array[1][1]=0x16

lea ebx,xxxxxxxx ;i_array 對應的位址裝入ebx
mov edx,04 ;訪問i_array[1][1],編譯時就已經確定
mov word ptr [ebx+edx*2], 16 ;

當然,取決於不同的編譯器和程式上下文,具體實現可能不同,但這種基本的形式是確定的。從這裏也可以看到比例因數的作用(還記得比例因數的取值為1,2,4或8嗎?),因為在目前的系統中簡單變數總是佔據1,2,4或者8個位元組的長度,所以比例因數的存在為在記憶體中的查表操作提供了極大方便。

4. 結構和物件

結構和物件的成員在記憶體中也都連續存放,但有時為了在字邊界或雙字邊界對齊,可能有些微調整,所以要確定物件的大小應該用sizeof操作符而不應該把成員的大小相加來計算。當我們聲明一個結構變數或初始化一個物件時,這個結構變數和物件的名字也對應一個記憶體位址。舉例說明:
struct tag_info_struct
{
int age;
int sex;
float height;
float weight;
} marry;

變數marry就對應一個記憶體位址。在這個位址開始,有足夠多的位元組(sizeof(marry))容納所有的成員。每一個成員則對應一個相對於這個位址的偏移量。這裏假設此結構中所有的成員都連續存放,則age的相對位址為0,sex為2, height 為4,weight為8。

; marry.sex=0;

lea ebx,xxxxxxxx ;marry 對應的記憶體位址
mov word ptr [ebx+2], 0
......

物件的情況基本相同。注意成員函數具體的實現在代碼段中,在物件中存放的是一個指向該函數的指標。


5. 函數調用

一個函數在被定義時,也確定一個記憶體位址對應於函數名字。如:

long comb(int m, int n)
{
long temp;
.....

return temp;
}

這樣,函數comb就對應一個記憶體位址。對它的調用表現為:

CALL xxxxxxxx ;comb對應的位址。這個函數需要兩個整型參數,就通過堆疊來傳遞:

;lresult=comb(2,3);

push 3
push 2
call xxxxxxxx
mov dword ptr [yyyyyyyy], eax ;yyyyyyyy是長整型變數lresult的位址

這裏請注意兩點。第一,在C語言中,參數的壓棧順序是和參數順序相反的,即後面的參數先壓棧,所以先執行push 3. 第二,在我們討論的32位元系統中,如果不指明參數類型,缺省的情況就是壓入32位元雙字。因此,兩個push指令總共壓入了兩個雙字,即8個位元組的資料。然後執行call指令。call 指令又把返回位址,即下一條指令(mov dword ptr....)的32位位址壓入,然後跳轉到xxxxxxxx去執行。

在comb副程式入口處(xxxxxxxx),堆疊的狀態是這樣的:

03000000 (請回憶small endian 格式)
02000000
yyyyyyyy <--ESP 指向返回地址 前面講過,副程式的標準起始代碼是這樣的: push ebp ;保存原先的ebp mov ebp, esp;建立框架指針 sub esp, XXX;給臨時變數預留空間 ..... 執行push ebp之後,堆疊如下: 03000000 02000000 yyyyyyyy old ebp <---- esp 指向原來的ebp 執行mov ebp,esp之後,ebp 和esp 都指向原來的ebp. 然後sub esp, xxx 給臨時變數留空間。這裏,只有一個臨時變數temp,是一個長整數,需要4個位元組,所以xxx=4。這樣就建立了這個子程式的框架: 03000000 02000000 yyyyyyyy old ebp <---- 當前ebp指向這裏 temp 所以副程式可以用[ebp+8]取得第一參數(m),用[ebp+C]來取得第二參數(n),以此類推。臨時變數則都在ebp下面,如這裏的temp就對應於[ebp-4]. 副程式執行到最後,要返回temp的值: mov eax,[ebp-04] 然後執行相反的操作以撤銷框架: mov esp,ebp ;這時esp 和ebp都指向old ebp,臨時變數已經被撤銷 pop ebp ;撤銷框架指針,恢復原ebp. 這是esp指向返回位址。緊接的retn指令返回主程序: retn 4 該指令從堆疊彈出返回位址裝入EIP,從而返回到主程序去執行call後面的指令。同時調整 esp(esp=esp+4*2),從而撤銷參數,使堆疊恢復到調用副程式以前的狀態,這就是堆疊的平衡。調用副程式前後總是應該維持堆疊的平衡。從這裏也可以看到,臨時變數temp已經隨著副程式的返回而消失,所以試圖返回一個指向臨時變數的指標是非法的。 為了更好地支援高階語言,INTEL還提供了指令Enter 和Leave 來自動完成框架的建立和撤銷。Enter 接受兩個運算元,第一個指明給臨時變數預留的位元組數,第二個是副程式嵌套調用層數,一般都為0。enter xxx,0 相當於: push ebp mov ebp,esp sub esp,xxx leave 則相當於: mov esp,ebp pop ebp ============================================================= 好啦,我的學習心得講完了,謝謝各位的抬舉。教程是不敢當的,因為我也是個大菜鳥。如果這些東東能使你們的學習輕鬆一些,進步快一些,本菜鳥就很開心了。

--> 閱讀更多...

2009年3月17日 星期二

●我的新NB:ASUS N80VC 新居落成

到貨馬上二話不說,先安裝bbxp再說,安裝過程還稱的上順利。拜網路上有太多solution 所以2~3HR就完成ㄌ,當然我最重要的virtual pc、source insight、masm、nasm、ubuntu linux、也都要一一給它setup完成,所以我想未來會從網誌上開始加入一些個人所學的資料,一方面幫助記憶,另一方面檢視自己哪方面能力不足須加強。順便把一些舊資訊整理整理;最近用了Notepad++,覺得實在不錯,感覺效能上比pspad好很多,在此也建議有在用notepad記事本ㄉ大大們download來用用,免費open source喔;另外再介紹一ㄍ好用ㄉtools:檔案比對工具Araxis Merge ,雖然目前很多人多會使用ultra compare或beyond compare但我個人已經習慣這個ㄌ;這些都不是copyleft,所以大大們請各憑本事囉。上圖是 "新居落成"的desktop。
P.S.在linux也有個kdiff3的比對軟體。
--> 閱讀更多...