My friends, my life, my style - James S.F. Hsieh

1/29/2011

Corel 尾牙 + Team building



1/28/2011

新年快樂 - 我們公司可愛的卡片

1/26/2011

Crash Dump - 要怎麼看 call stack (1)

Q: Call stack 怎麼看?
A: 使用高階語言通常不需要了解 call stack 的 layout, 但是當你需要分析 dump 的時候, 這就是必備的知識. x86 CPU 使用兩個 register 來記錄 stack frame 的位置:

  • SP/ESP/RSP: Stack pointer for top address of the stack.
  • BP/EBP/RBP: Stack base pointer/Frame pointer for holding the address of the current stack frame.
一個 stack frame 包含了 引數, context 與返回位址, 區域變數. 當程式呼叫某個函數時, 首先會根據 calling convention 來推入引數, 然後 call 指令會將相關的 context 跟返回位址推入堆疊, 然後修正 stack frame:

push ebp <= 將 Frame pointer 推入堆疊
mov ebp, esp <= 把 Frame pointer 修正到新的位指, 該位址就是舊的 EBP 所在處
sub esp, X <= 把 Stack pointer 移動來配置區域變數 X 表示區域變數需要的大小 (x86 堆疊是往小的位址成長)

所以, 我們可以預料, EBP 永遠指到上一個 EBP


以下是個簡單的例子來分析一下 stack, 堆疊的頭尾在 003e0000~003dfec4, 最後一個 EBP 是 003dff2c, 所以, 以下紅色的部分是 previous frame pointer, 藍色則是 return address. 這兩個部分是不需要知道每個 function 的 calling convention 就能夠推出來的部分. 我們能夠看出這個 stack 是從 ntdll!_RtlUserThreadStart 成長的, 所以這樣判對的 call stack 是正確的!


利用 r 拿到當下的 context

0:000> r
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=003dff08 edi=00000000
eip=7722fd21 esp=003dfec4 ebp=003dff2c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtDelayExecution+0x15:
7722fd21 83c404          add     esp,4


利用 !teb 來知道 stack 的 base 跟 limit

0:000> !teb
TEB at 7efdd000
    ExceptionList:        003dff1c
    StackBase:            003e0000
    StackLimit:           003d6000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7efdd000
    EnvironmentPointer:   00000000
    ClientId:             00000a18 . 00000734
    RpcHandle:            00000000
    Tls Storage:          7efdd02c
    PEB Address:          7efde000
    LastErrorValue:       0
    LastStatusValue:      c0000135
    Count Owned Locks:    0
    HardErrorMode:        0


我們用 dds 或是 kd 指令把 raw stack 展開


0:000> dds 003dfec4 003e0000 
003dfec4  7722fd21 ntdll!NtDelayExecution+0x15
003dfec8  76902c50 KERNELBASE!SleepEx+0x65
003dfecc  00000000
003dfed0  003dff08
003dfed4  d78faaa0
003dfed8  00dd337c CrashLab!__native_startup_lock
003dfedc  00000001
003dfee0  00000000
003dfee4  00000024
003dfee8  00000001
003dfeec  00000000
003dfef0  00000000
003dfef4  00000000
003dfef8  00000000
003dfefc  00000000
003dff00  00000000
003dff04  00000000
003dff08  c4653600
003dff0c  ffffffff
003dff10  00000000
003dff14  003dfed4
003dff18  00000000
003dff1c  003dff78
003dff20  76925eb0 KERNELBASE!_except_handler4
003dff24  a1202d44
003dff28  00000000
003dff2c  003dff3c <= Stack frame
003dff30  76903520 KERNELBASE!Sleep+0xf  
003dff34  000186a0
003dff38  00000000
003dff3c  003dff88 <= Stack frame
003dff40  00dd100b CrashLab!wmain+0xb [d:\codes\crashlab\crashlab\crashlab.cpp @ 49]
003dff44  000186a0
003dff48  00dd117d CrashLab!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
003dff4c  00000001
003dff50  00571280
003dff54  005760a8
003dff58  d780fa7f
003dff5c  00000000
003dff60  00000000
003dff64  7efde000
003dff68  00000000
003dff6c  00000000
003dff70  003dff58
003dff74  cc5814dc
003dff78  003dffc4
003dff7c  00dd16f5 CrashLab!_except_handler4
003dff80  d760246f
003dff84  00000000
003dff88  003dff94 <= Stack frame
003dff8c  75453677 kernel32!BaseThreadInitThunk+0xe
003dff90  7efde000
003dff94  003dffd4 <= Stack frame
003dff98  77249d42 ntdll!__RtlUserThreadStart+0x70
003dff9c  7efde000
003dffa0  6f7de307
003dffa4  00000000
003dffa8  00000000
003dffac  7efde000
003dffb0  00000000
003dffb4  00000000
003dffb8  00000000
003dffbc  003dffa0
003dffc0  00000000
003dffc4  ffffffff
003dffc8  772803dd ntdll!_except_handler4
003dffcc  1863df43
003dffd0  00000000
003dffd4  003dffec <= Stack frame
003dffd8  77249d15 ntdll!_RtlUserThreadStart+0x1b
003dffdc  00dd12c5 CrashLab!wmainCRTStartup [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 393]
003dffe0  7efde000
003dffe4  00000000
003dffe8  00000000
003dffec  00000000 <= Stack frame
003dfff0  00000000
003dfff4  00dd12c5 CrashLab!wmainCRTStartup [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 393]
003dfff8  7efde000
003dfffc  00000000
003e0000  ????????

有時候 context 中的 EBP 可能會是錯的, 那就得猜一下了! 如果幸運的找到正確的 EBP, 你將可以使用 "k L = BasePtr [FrameCount]" 來忽略錯誤的 stack frame. 假如正確的 EBP 是 003dff44.

0:000> k L = 003dff44 10
ChildEBP RetAddr  
003dfec4 76902c50 ntdll!NtDelayExecution+0x15
003dff44 00dd117d KERNELBASE!SleepEx+0x65
003dff88 75453677 CrashLab!__tmainCRTStartup+0x10f
003dff94 77249d42 kernel32!BaseThreadInitThunk+0xe
003dffd4 77249d15 ntdll!__RtlUserThreadStart+0x70
003dffec 00000000 ntdll!_RtlUserThreadStart+0x1b

有幾種最佳化的技巧會讓這個技巧無法使用:
  1. Inline:  inline 利用行內拓展來避免函數呼叫, 所以根本沒有 stack frame. 
  2. Frame pointer omission (FPO): 當 FPO enable 時, function 的呼叫可能就不在維護 EBP (有些條件還是需要), 在這種狀況, 你就沒有 EBP 可以參考. 詳細可以參考這篇文章 http://www.nynaeve.net/?p=91

Crash Dump - 為什麼 dump 的 call stack 總是會不準或很詭異

Q: 為什麼 dump 的 call stack 總是會不準或怪怪的
A: 有些比較成熟的軟體會在自己 crash 的時候產生 dump, 大部分有兩種做法 
  1. 程式內部使用 SetUnhandledExceptionFilter API 來設定 UnhandledExceptionFilter callback, 然後, 在 callback 利用 MiniDumpWriteDump API 來產生 dump
  2. 一樣使用 UnhandledExceptionFilter callback, 不過由外部的程式來產生 dump
在第一種做法中, 似乎是因為呼叫  MiniDumpWriteDump 的關係, 直接使用 windbg 指令 k 來傾印 call stack 會得到錯誤的結果. 例如下面這種不合理又附帶警告的 stack

0:000> k
0035eb9c 768fb75d ntdll!ZwGetContextThread+0x12
0035ebac 754513f8 KERNELBASE!CloseHandle+0x2d
0035ebbc 7483d4c3 kernel32!CloseHandleImplementation+0x3f
0035ed54 00210000 dbghelp!Win32LiveSystemProvider::OpenMapping+0x1fe
WARNING: Frame IP not in any known module. Following frames may be wrong.
0035ed6c be05061e 0x210000
0035ed70 02810048 0xbe05061e
0035ed74 0035f3d8 0x2810048
0035ed78 00000000 0x35f3d8

最簡單的方法就是將 dump 中 ExceptionStream 的 exception context record 讀出來解釋 crash stack, 最直接就是使用 .ecxr 指令, 很明顯 KERNELBASE!RaiseException 比較是合理的呼叫函數, 所以下面的 call stack 是正確的:

0:000> .ecxr
eax=0035fa0c ebx=00000001 ecx=00000003 edx=00000000 esi=ffffffff edi=00d23864
eip=768fb727 esp=0035fa0c ebp=0035fa5c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
KERNELBASE!RaiseException+0x58:
768fb727 c9              leave

0:000> k
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  
0035fa5c 749edbf9 KERNELBASE!RaiseException+0x58
0035fa94 00ca88cd msvcr90!_CxxThrowException+0x48 
0035fbc8 00ca05e6 Sample!wWinMain+0xad <= crash 所在之處
0035fc5c 75453677 Sample!__tmainCRTStartup+0x150 
0035fc68 77249d42 kernel32!BaseThreadInitThunk+0xe
0035fca8 77249d15 ntdll!__RtlUserThreadStart+0x70
0035fcc0 00000000 ntdll!_RtlUserThreadStart+0x1b

如果沒有 ExceptionStream 那就比較麻煩了


0:000> .ecxr
Minidump doesn't have an exception context
Unable to get exception context, HRESULT 0x80004002


我們必須仰賴 Thread Environment Block 中的 StackBase 跟 StackLimit, 不過有時候 !teb 指令無法讀到正確的 TEB, 似乎 ThreadListStream 所記載的資料就是錯的, 後來我發現是因為建立 dump 的時候少了 MiniDumpWithProcessThreadData, 所以會有以下的狀況

0:000> !teb
TEB at fffdd000
error InitTypeRead( TEB )...

Stream 0: type ThreadListStream (3), size 00000034, RVA 0000026C
  1 threads
  RVA 00000270, ID 8FC, Teb:FFFFFFFFFFFDD000

Crash Dump - 如何知道 dump 包含了哪些資訊?

Q: 如何知道 dump 包含了哪些資訊? 
A: 指令 .dumpdebug

----- User Mini Dump Analysis
MINIDUMP_HEADER:
Version         A793 (61B0)
NumberOfStreams 11
Flags        1050
                0010 MiniDumpScanMemory
                0040 MiniDumpWithIndirectlyReferencedMemory
                1000 MiniDumpWithThreadInfo

可以由 Flags 得知 dump 包含了哪些資訊, 對應的 MINIDUMP_TYPE 請參閱 MSDN 或是 http://www.debuginfo.com/articles/effminidumps.html. 如果是自己軟體產生的 dump 甚至可以包含 ExceptionStream ! 只要在建立 dump 的 API MiniDumpWriteDump 傳入 MINIDUMP_EXCEPTION_INFORMATION, 如果是利用 UnhandledExceptionFilter 來產生 dump 就非常的方便, 因為 UnhandledExceptionFilter 就提供了 EXCEPTION_POINTERS 來傳給 MINIDUMP_EXCEPTION_INFORMATION, 程式碼如下:

MINIDUMP_EXCEPTION_INFORMATION mdei = {0};
mdei.ThreadId  = GetCurrentThreadId();
mdei.ExceptionPointers  = pExceptionPointers;
mdei.ClientPointers     = FALSE;

MINIDUMP_CALLBACK_INFORMATION mci = {0};
mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
mci.CallbackParam       = 0;

MINIDUMP_TYPE mdt = (MINIDUMP_TYPE) MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory | MiniDumpWithThreadInfo | MiniDumpWithHandleData);

BOOL bRet = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, mdei, 0, &mci ); 
Stream 3: type ExceptionStream (6), size 000000A8, RVA 000001A0
  ThreadID 2720
  ExceptionCode C0000005
  ExceptionRecord 0
  ExceptionAddress 6a6b0998
  Context record RVA 1010, size 2cc

個人建議的 mini dump 至少資訊包含 MiniDumpWithIndirectlyReferencedMemory, MiniDumpScanMemory, MiniDumpWithThreadInfo, MiniDumpWithHandleData, MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules

1/25/2011

什麼是 C++ Runtime Error!

每當程式跳出了以下的視窗, 大概除了按下 "確定" 什麼也不能做, 什麼是 "C++ Runtime Error!" ?


簡單說, 大部分的 C++ Runtime Error 是眾多例外狀況 exception 的其中一種, 這裡指的 exception 是指 Windows 的 Structured Exception Handling (SEH). C++ Runtime Error 這種 exception 可以說是由 C++ Runtime 發出的 SEH exception, 當沒有人處理這個 exception 時, SEH 的 KiUserExceptionDispatcher 就會呼叫 C++ Runtime 內部預設的 unhandled exception filter  __CxxUnhandledExceptionFilter 來 "處理" 這個例外, 處理的方法則十分的簡單, 就是顯是以上的畫面然後結束程式! 所以我們可以看到堆疊大概會是以下的形式, 藍色部分是產生 exception 的部分, 黑色是 SEH, 而紅色則是 C++ Runtime 的部分. 這個例子是一個用 VC2008 建立的 MFC 程式, 所以會顯示 Message Box, 如果是 console 的程式, 輸出 message 到 stderror 串流.


以上略
MSVCR90!__crtMessageBoxA+0x160
MSVCR90!_NMSG_WRITE+0x16f
MSVCR90!abort+0x26
MSVCR90!terminate+0x33
CrashLab2!__CxxUnhandledExceptionFilter+0x3c 
KERNEL32!UnhandledExceptionFilter+0x127
ntdll_77360000!__RtlUserThreadStart+0x62
ntdll_77360000!_EH4_CallFilterFunc+0x12
ntdll_77360000!_except_handler4+0x8e
ntdll_77360000!ExecuteHandler2+0x26
ntdll_77360000!ExecuteHandler+0x24
ntdll_77360000!KiUserExceptionDispatcher+0xf
KERNELBASE!RaiseException+0x58
MSVCR90!_CxxThrowException+0x48 
CrashLab2!CCrashLab2App::CCrashLab2App+0x9c 
以下略

你可以用 WinDbg 把 exception record 印出來, 關鍵在於 __CxxUnhandledExceptionFilter 這個函數, 因為每個 unhandled exception filter 都有 EXCEPTION_POINTERS 引數,  該結構包含了 EXCEPTION_RECORD 跟 CONTEXT. 你可以看到相當多的資訊, 包含了 exception 物件的指標跟型別, 所以這個例子只是很簡單的丟出一個 int 並且 "int" 的值是 "1", 有趣的是只要是 MSVC 丟出來的 exception, 他的 ExceptionCode 都會是 0xe06d7363, 直翻成 Ascii code 就剛好是 ".msc" 順帶一提, 如果是 .NET Runtime 的 exception 則 ExceptionCode 剛好是 0xe0434f4d ".COM".


0:000>  .exr 001af4f0
ExceptionAddress: 0000000076a1b727 (KERNELBASE!RaiseException+0x0000000000000058)
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 3
   Parameter[0]: 0000000019930520
   Parameter[1]: 00000000001af8c8
   Parameter[2]: 0000000001093dc0
  pExceptionObject: 00000000001af8c8
  _s_ThrowInfo    : 0000000001093dc0
  Type            : int


0:000>  dt int 00000000001af8c8
1

因為這個 exception 已經被 "處理" 所以如果你用 SetUnhandledExceptionFilter API 將無法攔到這個 exception.  我判斷是錯誤的, 正確的說  SetUnhandledExceptionFilter API 可以攔到部分的 C++ Runtime Error, 只要這個 error 是建立在 SEH Exception 之上. 但某些錯誤 (Pure virtual function call) 則必須註冊 signal 來攔截, 因為他不會產生 exception 卻會直接 terminate process. 當然你也可以用 signal 來攔截 C++ Runtime 產生的 SEH exception. 大多數的 Windows programmer 對 signal 都不是非常的熟悉, 也許是因為 signal 是 UNIX 的產物, signal 是 ANSI C 定義的一種 callback 方式, 專門接收特殊的信號以便傳遞給 program, 舉個例子 CTRL+C 可以關閉命令列程式就是用這種方式來傳遞 SIGINT.


以下就是個簡單的例子, 程式將會在 __CxxUnhandledExceptionFilter 執行 terminate.


以下是呼叫 "terminate"  callback function 的堆疊

CrashLab2!terminate
msvcr90d.dll!raise
msvcr90d.dll!abort
msvcr90d.dll!terminate
CrashLab2.exe!__CxxUnhandledExceptionFilter
KERNEL32!UnhandledExceptionFilter+0x127
ntdll_77360000!__RtlUserThreadStart+0x62
ntdll_77360000!_EH4_CallFilterFunc+0x12
ntdll_77360000!_except_handler4+0x8e
ntdll_77360000!ExecuteHandler2+0x26
ntdll_77360000!ExecuteHandler+0x24
ntdll_77360000!KiUserExceptionDispatcher+0xf
KERNELBASE!RaiseException+0x58
MSVCR90!_CxxThrowException+0x48 
CrashLab2!CCrashLab2App::CCrashLab2App+0x9c 
以下略

有哪些狀況會產生 C++ Runtime Error 呢? 我所知道的有以下條件
  1. 未處理的 C++ exception
    a. 產生 exception 而外部又沒有 try-catch
    b. 在 catch statement 中又產生 exception 而外部又沒有 try-catch
  2. 無法處理的 exception
    a. printf, scanf 引數匹配錯誤
    b. Pure virtual function call 
以下是個 Pure virtual function call 的例子, 當你建構 class B 就會發生.


其他例子的詳細描述可以參考 C Run-Time Errors R6002 through R6025

1/23/2011

2011 01 22 加羅湖



軌跡記錄到紮營點而已

在較大的地圖上查看2011 01 22 加羅湖


在較大的地圖上查看2011 01 22 加羅湖

1/17/2011

Neutral Apartments

終於找到關於 Neutral Apartments 的描述了, 記得之前都查不到呢, 根據 MSDN 上寫的日期, 這是 9/2/2010 的文件, 真是 "新" 啊!


Neutral Apartments http://msdn.microsoft.com/en-us/library/ms681813(v=VS.85).aspx
Threading Model Attribute http://msdn.microsoft.com/en-us/library/ms681753(v=VS.85).aspx
有張挺有價值的表格 :)
COM+ Contexts and Threading Models http://msdn.microsoft.com/en-us/library/ms681289(v=VS.85).aspx

而外還找到一個不錯解釋, 解釋的比 MSDN 還好, 真不知道他怎麼知道的 ..... 呵呵
摘錄於 http://gsraj.tripod.com/com/com_threading.html

Components that use the Thread Neutral Apartment model (TNA), mark themselves as Free Threaded or Both. Here the component instances run on the same thread type as the caller's thread. Each instance of a COM class can run on a different thread each time a method is called. When a thread is executing a method in a COM object, and that method creates a new object, MTS will suspend the current thread and create a new thread to handle the new object. Like the MTA, TNAs allow more than one thread to enter an apartment. However, once a thread has entered an apartment, it obtains an apartment-wide lock and no other thread can enter that apartment until it exits. This model was introduced into MTS and COM+ to ensure that context switches are faster.

1/16/2011

MySQL licensing

大家都知道 MySQL 是以 GPL 的方式授權使用, 而他也提供付費授權的版本, 那麼, 如果一個網站使用 MySQL 做為營利用途, 那是否得公開源始碼呢? 否則就違反 GPL 授權了. 根據 GPL 的規範, GPL 軟體與軟體的衍生物都必須以 GPL 的方式公開源碼, 而衍生物比較廣泛的定義應該包含了 dynamic linking 跟 database access 這類的行為吧. 所以從這個角度來看, 營利網站似乎不能使用 GPL 的 MySQL .... 


不過我又查到有此一說: 以下摘錄於 http://stackoverflow.com/questions/620696/mysql-licensing-and-gpl

Provided that you keep it server-only(and therefore "private"), you don't have to release it as GPL. But as soon as it reaches public desktops, it can no longer be considered an internal build, and therefore source code is needed.
So you can:
  • Release your code as GPL
  • Buy the commercial version of MySQL
  • Keep it server-only.
從這樣的說法來看 Server 使用 MySQL 又好像不用付費 ... 我更好奇 Amazon 推出的 
Relational Database Service 服務又該怎麼算呢? 它可是建立在 MySQL 之上呢.

1/07/2011

神秘的 COINIT Enum 到底做啥用的啊?

http://msdn.microsoft.com/en-us/library/ms678505(v=vs.85).aspx

COINIT_DISABLE_OLE1DDE
   Disables DDE for OLE1 support.
COINIT_SPEED_OVER_MEMORY
   Trade memory for speed.

到底是啥鬼?