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

11/09/2011

從 COM 到 Windows Runtime Component

Windows Runtime Component 是 COM 的一種延伸我想這是錯的, 但如果說 WinRT Component 是 COM 的一種演化, 我想這是正確的. 由於 COM 被賦予不少期待所以它很複雜, 從 binary-level interface (在 WinRT 稱 abstract binary interface), system-wide activation, automatic memory management , reflection and Type Library (在 WinRT 為 Windows Runtime metadata), threading model (在 WinRT 稱 concurrency model) 到 out-of-process object 都是 COM 想一併解決的問題. 我猜 Windows Runtime Component 的目標與 COM 相近, 並且想簡化 COM 的複雜度.

Abstract Binary Interface
由於 WinRT Component 可以使用 Pure C++ 搭配 Windows Runtime C++ Template Library (WRL) 來實作, 所以 binary-level interface 跟 COM 是相同的, 換句話說就是跟 Visual C++ 的 VTable 完全同. 不過 ABI 限制了可以傳遞的 type, 所有可以傳遞的 type 必須是 Windows Runtime types. 概念上來說如果不是一個 POD type, 那就必須是一個實作 IInspectable interface 的 COM, IInspectable 是用來做為 language projecting 用的, 簡單的說 IInspectable 可以傳回該 object 所有支援的 interfaces (IInspectable::GetIids) 還有 object 本身的 class fully-qualified name (IInspectable::GetRuntimeClassName). 有了這兩個資訊就足夠查詢 Windows Runtime metadata (*.winmd). WRL::Details:RuntimeClass 會去實作 IInspectable 這個介面.


Object Activation
COM Activation (COM DLL)有幾個步驟 :
  1. COM DLL 可以經由呼叫 DllRegisterServer 來註冊 COM 到系統中
  2. 根據 CLSID 從 HKEY_CLASSES_ROOT\CLSID 找出 object 對應的 binary path.
  3. 載入 binary 並且呼叫 DllGetClassObject function 來取得 Class object.
  4. 呼叫 Class object 的 IClassFactory::CreateInstance 來 create object.
WinRT DLL Component  則是:
  1. 註冊 WinRT DLL Component 的方式跟 COM 很不相同, 我猜這跟 GAC 應該會很像.
    [Update 2011/11/09]
    目前了解是放在 Activation Store (registry)
    HKEY_CURRENT_USER\Software\Classes\ActivatableClasses
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\ActivatableClassId
  2. 根據 activatable class ID 找到對應的 binary path
  3. 載入 binary 並且呼叫 DllGetActivationFactory function 來取得 factory.
  4. 呼叫 factory 的 IActivationFactory::ActivateInstance 來 create object.
目前我看到的 WinRT Component 都必須被 package 到 Metro style app 之中也就是 local side-by-side, 不知道微軟允不允許 WinRT Component 也能做為 cross application 的 share component.

Automatic Memory Management 
WinRT Component 採用的 automatic memory management 是與傳統 COM 一樣的 reference counting 而非 .NET Framework 的 mark and sweep garbage collector, 我想這是可想而知的, 因為 C++ 並沒有 runtime 的幫忙來 GC 所以必須靠每個 object 自己來管理. 由於 WinRT Component 被 JavaScript, C#, VB.NET 中被使用所以 reference cycles 這個重要的課題必須被解決, 所以微軟為 WinRT Component 引入了 weak reference 的能力, 基本上使用 WRL 實作的 component 由於繼承了 WRL::Details:RuntimeClass 所以都會實作 IWeakReferenceSource. 當然這必須是設計 component 的 developer 有意識的去使用 weak reference 才能解決 reference cycles 問題. 有興趣實作的人可以去看 WRL::Detial::WeakReference 這個 class 就能夠了解這是如何辦到的. 以下描述一個 reference cycles 如何使用 weak reference 來打斷這個 cycle, 假設有 A, B 兩個 objects:



Reflection, Projection 
IDL 是用來描述 COM interface 跟 class 的描述語言, 使用 C++ (非 C++/CX )實作 WinRT Component 還是必須使用 IDL 來描述 object 的 metadata, MIDL compiler 會根據 IDL 來產生 Type Library (TLB) 與 Windows Runtime metadata (winmd). 有趣的是你可以直接用 .NET Reflector 開啟 winmd 檔案. 以下是微軟 Metro style app 的 sample DLL server authoring sample


如果是使用 C++/CX 則它會自動產生 winmd 而不用去撰寫 IDL. Language Projection 完全仰賴 IInspectable 與 Windows Runtime metadata

Threading model
根據 WRL 的 source code 上來看 reference counter 已經確保是 thread-safety, 這有別於 ATL 的設計, 由於 WinRT component 沒有 registry 或是其他 metadata 來描述 COM 的 threading model, 所以我猜測 WinRT  已經不使用 apartment 的概念來管理 WinRT component, 換句話說, 可能所有的 thread 都是 MTA 所有的 component 都是 Free, 但是 , 令我訝異的是 RoInitialize 還是必須傳入, RO_INIT_SINGLETHREADED 或是 RO_INIT_MULTITHREADED, 來描述 thread 的 concurrency model. 目前我還不知道原因.

更讓我疑惑的是 old-style COM 該怎麼辦呢? 微軟文件上是說 old-style COM 在 Metro style app 下還是能夠有限度的被使用 (PS. 可以被使用的 COM CLSID 被記錄在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime\AllowedCOMCLSIDs), 在使用 COM 之前還是必須呼叫 CoInitializeEx 且傳遞 COINIT_APARTMENTTHREADED 或 COINIT_MULTITHREADED 描述 thread 的 threading model, 根據我測試的結果 C++/CX and XAML 的 UI thread (不是 main thread 喔) 是 STA 並且內建了 message loop. 但如果有兩個 STA 要存取對方所擁有的 COM 時, Workder thread 所 host 的 COM 可能就無法正常運作, 因為我不知道怎麼在一個 Worker thread 實作一個 message loop 來推動 STA 下 Apartment 或是 Both 的 COM object, 因為 GetMessage/PeekMessage API 在 WinRT 已經不能用了.

[Update 2011/11/09]
看了這篇 Windows Runtime internals: understanding "Hello World" article 我才恍然大悟, Threading model 是存在的, 只不過 WinRT 隱藏了這些細節, WinRT 有一個預設的 Win32 exe 也就是 WWAHost.exe ,它被用來 host Metro style application. WWAHost.exe 也就是 application 的 Server, 以我所了解, host 可以是別的 exe, 這些資訊會被記錄在HKEY_CURRENT_USER\Software\Classes\ActivatableClasses\Server\XXX\ExePath. 根據我對這篇 article 的了解, WWAHost.exe 的第一個 thread 會是 MTA, 這個 thread 被用來做為 activation 之用. 所以 WWAHost!Host::Run 會去執行twinapi!Windows::ApplicationModel::Core::CoreApplicationFactory::RunWithBackgroundFactory 並且等待新的 activation request. 當新的 request 進來時, 它會 fork 一個新的 STA thread 並且執行 request 的需求. 這個需求可能任何一種 Contract ex: Launch, Search, BackgroundTasks ....


[Update 2011/11/25]
Introduction to background tasks - Guidelines for developers 對於 threading model 有特別的解釋
For non-JavaScript apps, the background tasks are hosted in an in-proc DLL which is loaded in a multi-threaded apartment (MTA) within the app. For JavaScript apps, background tasks are launched in a new single-threaded apartment (STA) within the WWA host process. The actual background task class can be STA or MTA. Because background tasks can run when an app is in a Suspended or Terminated state, they need to be decoupled from the foreground app. Loading the background task DLL in a separate apartment enforces separation of the background task from the app while allowing the background task to be controlled independently of the app.


Out-of-Process WinRT Component
依照 WRL 的 source code 的設計來看, WinRT Component 是允許 Out-of-Process 的形式, 但是我卻找不到實際的 sample, 如同上述的問題 GetMessage/PeekMessage API 已經不存在了, 這樣如何實作一個 Out-of-Process COM 呢?

[Update 2011/11/10]
我猜想 WinRT 可能提供類似 DLL Surrogates 的方式來實作 DLL Server, 也就是說我們不用寫一個 exe 來 host COM 而是使用 WinRT 提供既有的 host exe. 另外, WinRT 提供 Broker. 

還有許多問題需要釐清.... 如果我知道答案在分享出來 :D

11/08/2011

Stack of WinRT


來分享一下 WinRT 下 JavaScript 呼叫 C++ WinRT object 的 stack 你發現什麼嗎? :D


.  0  Id: f70.7f0 Suspend: 0 Teb: 7f3dd000 Unfrozen
      Start: WWAHost!mainCRTStartup (001c77a2) 
      Priority: 0  Priority class: 32  Affinity: f
ChildEBP RetAddr  
00eafb44 76e619f8 ntdll_77220000!ZwWaitForSingleObject+0xc
00eafbb0 76e61983 KERNELBASE!WaitForSingleObjectEx+0x8f
00eafbc4 6341b17b KERNELBASE!WaitForSingleObject+0x12
00eafbec 6341b21d twinapi!Windows::ApplicationModel::Core::CoreApplicationFactory::RunWithBackgroundFactoryAndThreadingModel+0x1af
00eafc04 001c92ed twinapi!Windows::ApplicationModel::Core::CoreApplicationFactory::RunWithBackgroundFactory+0x19
00eafc54 001c7750 WWAHost!Host::Run+0xec
00eafc70 001c78a9 WWAHost!RunHost+0x80
00eafc94 74d69391 WWAHost!mainCRTStartup+0x107
00eafca0 772831b0 kernel32!BaseThreadInitThunk+0xe
00eafce0 77283183 ntdll_77220000!__RtlUserThreadStart+0x6f
00eafcf8 00000000 ntdll_77220000!_RtlUserThreadStart+0x1b

10/27/2011

ChopChop 可以讓影片發揮擴大社交圈功能!


ChopChop 是免費的 Facebook 應用程式,可以讓影片發揮擴大社交圈功能! 先搜尋 YouTube™ 影片,將影片全部組合到 ChopChop 腳本中,找出您最喜歡的片刻,然後將您完成的混搭影片分享給 FaceBook 上的朋友。 取個有趣的片名和標題說明,然後標示您的朋友,再邀請他們加入您的 ChopChop 影片作品。 ChopChop 讓您的影片樂趣無窮!

ChopChop 是我的第一個 Web application, 他結合了影片編輯與社群互動等有趣的功能, 在這個產品中, 我負責 Video render engine, RESTful Web API 跟 Database 等規劃與設計.

DEMO: 美女接龍, 經典台詞2011 06 26-28 Palau + Phoenix



ChopChop is a free Facebook app that makes video social! Search YouTube™ videos, assemble them together in the ChopChop storyboard, find your favorite moments and share your mash-ups with your FaceBook friends. Add funny titles and captions, then tag your friends and invite them to add to your ChopChop video production. ChopChop is video fun for all!

ChopChop is based on Google App Engine. I am responsible for the design and development of  Web API, noSQL database (BigTable), server cache line, browser extensions (IE, Chrome, Firefox, and Safari), Facebook integration, and small part of front-end UI.

10/09/2011

2011 09 18 西藏轉山軌跡



在較大的地圖上查看西藏轉山軌跡
在較大的地圖上查看西藏轉山軌跡

8/24/2011

Word Cloud


7/06/2011

2011 06 25-28 Palau

6/13/2011

2011 06 11-12 水漾森林




在較大的地圖上查看2011 06 12 水漾森林

5/24/2011

2011 05 21 松羅湖國家步道

4/14/2011

James and Stella - *





4/07/2011

Wedding Invitation

4/01/2011

HTML5 Video Render Engine


It is a web video render engine based on HTML5 and WebGL. It provides timeline, tracks, clips, programmable animation (key-frame) and video effects for user to compose a video project. A video project of output is a small JSON file (10~50KB). Engine can compose videos, images, audios and texts according to the project file in time. The media source can be a YouTube online video, an image in Picasa web, or a MP3 audio in the cloud etc. JVRE provides APIs to edit a video project based on online resources such as YouTube videos or Picasa images. The editing result likes a “playlist”, and it can be persisted to a JSON structure. JVRE can accord to the editing result to composite visual and auditory output on-the-fly with online resources. I designed and implemented whole engine.

Give a general application, a web-site provides video editing service based on JVRE. User can create/edit/playback his video project in the browser. The project file (a JSON structure) can be store in server for next playback or editing. JVRE also supports primary features such as:
 Support multiple layers called tracks in timeline
 Track has multiple clips; it supports Transformation and Effects.
 Clip is a playlist of one resource; it supports Transformation and Effect.
 Source of clip can be a video, an audio, an image, or a title.
 Title supports multiple texts and Transformation.
 Effects implement with WebGL pixel-shader, it can leverage GPU.
 User can edit timeline without pause playback.
 Engine supports delay-loading and pre-loading videos to reduce latency.
 Implement key-frame architecture for animations; the interpolation algorithm of key-frame is extendable.
 Support hit-test for editing and interaction.


3/04/2011

2011 03 02 Springfield Team 出團北海岸


2/27/2011

2011 02 26 死黨文子結婚




2/25/2011

常常搞混的 Structural pattern

以下幾種 structural pattern 常常被搞混或是誤用, 甚至都以 wrapper 這個濫用的名詞來稱呼, 大多都有 aggregation 的結構關係, 其 aggregate 的目有所不同, 以下以 caller, aggregator 與 target 來說明三者的關係

  1. Shortcut (一種退化的 Facade)
    aggregator 的目的是讓 caller 對 target 使用起來更簡單方便, aggregator 大多隱藏了很多操作 target 的細節, 所以具有減化複雜度的效果, 但是其目的是以 caller 方便為出發點, 所以 aggregator 多半參雜過多 caller 的假設與邏輯而變的不容易 maintain, 另外, 如果 target 提供一個新的功能, aggregator 往往都要對映的 "開一條路" 讓 caller 使用, 如果開路的代價比方便的好處高, 這就成為一種不良的設計 middle men.

    C# 有一種 syntax sugar Extension methods 可以避面這種 middle men 的問題, 又可以將操作的邏輯封裝在 caller 的部分而非獨立建立出一組 aggregator class.

  2. Proxy
    aggregator 的目的表留一種間接性來達到某種 target 沒有的功能, 這些功能跟 target 本身的功能通常比較沒有直接相關 (但不是絕對), 多半 aggregator 都是 transparent, 並且它會針對特定 target 來設計, 所以擁有相同的介面, 以下是一些比較著名的應用: 

    virtual proxy: 用於 lazy instantiation (visualization) 或是 lazy processing
    remote proxy: 用於 IPC/RPC proxy/stub, 用以支援遠端呼叫 
    access proxy: 用於 access control. COM 的 Apartment proxy/stub 就是一種 access proxy 用來保證對 target 的操作是 thread-safety

  3. Decorator
    當 target 有自己的繼承體系並且該體系擁有 composite pattern 結構, aggregator 的目的是擴充一些 target 沒有的功能 (sub-classing), 並且 aggregator 必須保有 composite 的特性, 所以它必須繼承 component 而非 target. 多半 aggregator 都是 transparent.

    proxy 跟 decorator 的目的嚴格說都是 sub-classing, 但是成本有時候比繼承還要高, 而 strategy 是一種比較低成本的做法, 但是這樣的機制必須被設計在 target 當中.

    Boost Iostreams library 與 WIC 提供 filter 的設計思維都是種 decorator pattern

  4. Adapter
    aggregator 的目的是讓 target 加入到 caller 的繼承體系中, 用以解決不相容的問題, 這種做法非常直覺, 因為幾乎沒有人想要在 caller 的繼承體系中實做一個跟 target 功能近似的 class. 而多半會 reuse target.

  5. Bridge
    aggregator 的目的是隔離抽象性實做, 所以 aggregator 必須定義一般性的抽像操作, 而 target 來實做細節, 一般來說 
    aggregator 會有自己的繼承體系, 並且 target 會有其對應關係, 不過, 繼承體系不一定是必要的 COM 的 interface 就是種例子, 繼承體系帶來的限制往往是弊多於利, 不如根據 aspect 分離 interface 是比較恰當的做法.

    如果一個 aggregator 會有多個可能的 target 實做對應, 那根據 target 來定義 
    aggregator (interface) 是種錯誤的思維, 這違反 DIP 與 bridge 的目的, 保持抽像性與分離不相依的面像才是介面設計的原則.

2/22/2011

Justin.tv


Watch live video from nomadlibra on Justin.tv

2/09/2011

.NET and COM interop

以下是收集有用的連結
Type Library to Assembly Conversion Summary

2/08/2011

2011 02 06 五專同學聚會


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.

到底是啥鬼?