2004年3月31日 星期三

Who Call Me?

誰叫我? 有用過 gdb 對 core 檔作 bt (backtrace) 嗎? 所謂 backtrace 是用來回溯檢查函式呼叫的關係, 以便了解是由那一個函式呼叫出問題的函式. 尢其是在許多錯綜複雜的龐大程式碼中, backtrace 是相當有用的 debug 技巧. 而這個題目則是用來討論如何在程式執行中作 backtrace.

在實作這個技術前有兩個關鍵點要先解決:

1. 如何取得此 function 返回位址.
2. 如何依據返回位址查知函式名稱.
關於第一點, 必須先了解堆疊(Stack) 和函式呼叫的處理關係. 堆疊是一個後進先出(Last-In-First-Out)的資料結構. 當呼叫某個函式時, 相關的暫存器(Register)就會被存入堆疊. 而當函式返回時便會從堆疊裡取回返回位址以便回到原來呼叫的下一個指令繼續執行. 至於暫存器(Register), 其中 EIP 是 Instruction Pointer, 用來指出 CPU 將要執行指令的位址. ESP 暫存器則是用來指向目前堆壘的位址.

我們先寫個小程式來觀察可行性.

----------- test.c -----------
void test()
{

}

int main()
{

test();
}
------------------------------

[tim@localhost whocallme]$ gcc -o test test.c
[tim@localhost whocallme]$ gdb ./test
GNU gdb 5.3-25mdk (Mandrake Linux)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-mandrake-linux-gnu"...
(gdb) b test
Breakpoint 1 at 0x804832f
(gdb) r
Starting program: /home/tim/research/whocallme/test

Breakpoint 1, 0x0804832f in test ()
(gdb) info reg
eax 0x0 0
ecx 0x1 1
edx 0x4014fe50 1075117648
ebx 0x4014e9a0 1075112352
esp 0xbffff698 0xbffff698
ebp 0xbffff698 0xbffff698
esi 0x40013880 1073821824
edi 0xbffff6f4 -1073744140
eip 0x804832f 0x804832f

(gdb) disas test
Dump of assembler code for function test:
0x804832c : push %ebp
0x804832d : mov %esp,%ebp
0x804832f : pop %ebp
0x8048330 : ret
End of assembler dump.

ebp 暫存器值是 0xbffff698, 也就是原來的堆疊位址. 我們知道在呼叫函式時(call) CPU 會將返回位址存入堆疊, 因此可以從 ebp 暫存器的位址裡面找到我們需要的返回位址:

(gdb) p/x *0xbffff698
$1 = 0xbffff6a8

別忘了, 一進入此函式時 push $ebp 已被執行, 因此堆疊位址已被減 4, 所以要取得正確的值還得把 4 加回去才行:

(gdb) p/x *(0xbffff698+4)
$2 = 0x8048346

這個值應該就是 test() 正確的返回位址, 來檢查看看:

(gdb) disas main
Dump of assembler code for function main:
0x8048331
: push %ebp
0x8048332 : mov %esp,%ebp
0x8048334 : sub $0x8,%esp
0x8048337 : and $0xfffffff0,%esp
0x804833a : mov $0x0,%eax
0x804833f : sub %eax,%esp
0x8048341 : call 0x804832c
0x8048346 : leave
0x8048347 : ret
0x8048348 : nop
0x8048349 : nop
0x804834a : nop
0x804834b : nop
0x804834c : nop
0x804834d : nop
0x804834e : nop
0x804834f : nop
End of assembler dump.

果然在 call 完後的下個指令是位於 0x8048346, 也就是 test() 返回位址.
接下來我們就用 C 和一些 assembly 配合來實作.

------------- test-1.c ------------------
void test()
{
unsigned long *stack;
asm ("movl %%ebp, %0\n"
printf("ret address = 0x%x\n", *(stack+1));

}

int main()
{

test();
}
-----------------------------------------

[tim@localhost whocallme]$ ./test-1
ret address = 0x8048394
[tim@localhost whocallme]$ gdb ./test-1
(gdb) disas main
Dump of assembler code for function main:
0x804837f
: push %ebp
0x8048380 : mov %esp,%ebp
0x8048382 : sub $0x8,%esp
0x8048385 : and $0xfffffff0,%esp
0x8048388 : mov $0x0,%eax
0x804838d : sub %eax,%esp
0x804838f : call 0x804835c
0x8048394 : leave
0x8048395 : ret
0x8048396 : nop

第一個關鍵點目前已解決, 再來要想想怎麼要能夠依記憶體位址查知所處的函式名稱呢?

更多詳細的內容請看 Who Call Me? 一文.

2004年3月26日 星期五

DOSBOX 下玩 CCH

這兩天在 Sayya BBS 上有朋友討論圍棋、象棋,
後來更有人在 Linux 下直接玩一個古考的 DOS 遊戲 - 將族.
將族應該是我高中時候的遊戲, 當時的我對象棋非常著迷, 而將族的棋力又非常不錯, 我記得我曾經打敗過所有一星和二星的棋士, 但三星就很困難了.
今天我也去下載了 DOSBOX, Mandrake 的使用者可以至 cooker 下載 dosbox-0.61-1mdk, 原來 Mandrake 9.2 的 dosbox 0.58 版本太舊無法正常執行 CCH.
cch.png
Ok, 從上面這張 screenshot 來看效果不錯, 而且音樂音效也有出來, 美中不足的是偶爾 dosbox 會突然爆炸....

2004年3月25日 星期四

悼 Maxtor 硬碟

去年(2003)六月時買的硬碟, 突然一聲不吭的就出問題了, 裡頭的資料啊.....

由於平時沒有勤備份的習慣, 目前已服役數年的兩顆 Maxtor 也都還十分認真,
實在料想不到好好的 Maxtor 80G 用不到一年就出問題, 以致許多重要資料就
此遺失.

底下的 messages 為已陣亡的 Maxtor 6Y080L0(/dev/hde) 記錄:

Jan 13 10:58:39 debian kernel: Partition check:
Jan 13 10:58:39 debian kernel: hda: hda1 hda2 hda3 < hda5 hda6 >
Jan 13 10:58:39 debian kernel: hde:hde: dma_intr: status=0x51 { DriveReady See
Jan 13 10:58:39 debian kernel: hde: dma_intr: error=0x40 { UncorrectableError }
Jan 13 10:58:39 debian kernel: end_request: I/O error, dev 21:00 (hde), sector
Jan 13 10:58:39 debian kernel: hde: dma_intr: status=0x51 { DriveReady SeekComp
Jan 13 10:58:39 debian kernel: hde: dma_intr: error=0x01 { AddrMarkNotFound },
Jan 13 10:58:39 debian kernel: hde: dma_intr: status=0x51 { DriveReady SeekComp
Jan 13 10:58:39 debian kernel: hde: dma_intr: error=0x40 { UncorrectableError }
Jan 13 10:58:39 debian kernel: end_request: I/O error, dev 21:00 (hde), sector
Jan 13 10:58:39 debian kernel: hde: dma_intr: status=0x51 { DriveReady SeekComp
Jan 13 10:58:39 debian kernel: hde: dma_intr: error=0x40 { UncorrectableError }
Jan 13 10:58:39 debian kernel: end_request: I/O error, dev 21:00 (hde), sector
Jan 13 10:58:39 debian kernel: hde: dma_intr: status=0x51 { DriveReady SeekComp
Jan 13 10:58:39 debian kernel: hde: dma_intr: error=0x40 { UncorrectableError }
Jan 13 10:58:39 debian kernel: end_request: I/O error, dev 21:00 (hde), sector
Jan 13 10:58:39 debian kernel: unable to read partition table

當然, 遇到問題總不能馬上投降. 因此上網找尋了 smartmontools 來檢測
使用 smartctl -H /dev/hde 確認了硬碟無法使用後才放棄.

第二天出發至 NOVA 找店家, 並在店家的指示下到地下一樓的 Maxtor 維修中心,
填妥資料即馬上領了新的硬碟, 一樣還是 Maxtor 6Y080L0. 說到這, 我在 google
時注意到有許多人的 Maxtor 6Y080L0 在用不到一年即有類似的情況.

此外, 我也特意買了新的 ATX Power 和硬碟散熱風扇, 希望這次的 Maxtor 6Y080L0
能服役更久些...

底下是良好的 Maxtor 6Y080L0 狀況
[root@www src]# smartctl -H /dev/hde
smartctl version 5.30 Copyright (C) 2002-4 Bruce Allen
Home page is http://smartmontools.sourceforge.net/

=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED

最後,
希望大家的硬碟都身體健康, 並養成常洗手常備份的習慣, 謝謝.