LLVM for Windows Visual Studio 2010/2012

Chandler Caruth 在 GoingNative 2013 上宣布了一個好消息,LLVM 3.4 (開發中)正在如火如荼地移植到 Windows 上,目前初步的成果已經可以使用 Visual Studio 2010/2012 作為 IDE 了,對於習慣 Visual Studio 的人來講應該是個好消息。

alpha 版本可以在這邊下載到:

  • SVN Snapshot: http://llvm.org/builds/
  • 設定上也非常簡單,在 Project > Properties > General > Platform Toolset 改成 LLVM-vs2010 即可。
    llvm_on_windows

試玩了一下發現:

新 project 有感

C# + .NET framework 2.0 + Visual Studio 2010 + remote debugging = 殘廢

C# + .NET framework 2.0 + dump + WinDbg = 悲劇

NetBSD Kernel 要支援 Lua

讓我想起了以前微軟研究院利用 C# 改良版 Spec# 開發的 Singularity OS ,不過這次換成了 Lua 。

2010 年便開始開始的 GSoC project,一度變成沒人維護的項目,不過 NetBSD 7 可能即將包含它。還提到了 Python 和 Java 也是有可能的取代方案,不過考慮到這兩個語言的 object mapping 和 memory 需求量,還是沒有成真。

目前看來外國鄉民是一面倒地不看好,不過技術嘛,或許會有令人驚豔的應用或影響出現也說不定!不過 script language 大都帶有 garbage collection ,要讓這大傢伙和 kernel 裡其他的 subsystem 互動順利、尤其是記憶體管理,加上處理 interpreter 內,自帶的 synchronization mechanism 搭配 kernel scheduler ,花的功夫應該是不少,令人好奇!

News http://bsd.slashdot.org/story/13/02/16/2329259/netbsd-to-support-kernel-development-in-lua-scripting

Slides: https://fosdem.org/2013/schedule/event/lua_in_the_netbsd_kernel/attachments/slides/278/export/events/attachments/lua_in_the_netbsd_kernel/slides/278/kernel_mode_lua.pdf

BoostPro 要關門了?!

在 Boost mail list 上看到的消息:

http://lists.boost.org/Archives/boost/2013/01/200634.php


Subject: [boost] Future of Boost Windows Installers
From: Dave Abrahams (dave_at_[hidden])
Date: 2013-01-31 14:42:33

Hi,

As many of you already know, I'm going to work at Apple in late
February, and BoostPro is closing its doors. For many years, the Boost
Getting Started Guide has suggested that Windows users pop over to
BoostPro's website and use the latest installer to get Windows binaries.
We'd like to donate to the community the technology used to build those
installers. If anyone would like to pick up the job of creating them,
we'd be happy to offer guidance.

Right now I'm on a plane where an uplink to GitHub is creeping along at
4KB/s, so I've been pushing the stuff up in bits and pieces and the
latest stuff isn't up there, but it's going to
http://github.com/boostpro/installer and will be complete within 24 hours.

Whatever happens with this installer code, someone is going to need to
update the Getting Started Guide, either to point at the installer's new
home, or to remove references to it.

-- 
Dave Abrahams
BoostPro Computing                  Software Development        Training
http://www.boostpro.com             Clang/LLVM/EDG Compilers  C++  Boost

BoostPro 是幾位 C++ 大師組成的教學顧問團隊,BoostPro 上有他們的黃金陣容的介紹:

  • Dave Abrahams
    多年來致力於 C++ 標準制定和 Boost 推廣維護,貢獻了很多 Boost libraries、2001 年創辦 BoostPro 、
  • Joel de Guzman
    Boost.Spirit 、 Boost.Fusion 和 Boost.Phoneix 的作者。看過 Spirit 精妙的 C++ expression templates 魔術後,很難不對作者留下深刻印象。
  • Jeremy Siek
    六個 Boost libraries 的作者,很早就在 Boost 裡頭提供一套 concept check 的人。

嗯, Apple 這幾年應該算是最力挺 native code 的大型軟體公司(硬體公司?!),旗下的 LLVM、Clang 等 open source projects 不說,還雇用了 Doug Gregor (Modules for C++)、Howard Hinnant(libcxx 作者) 等神人在為更底層的基礎扎根,曾幾何時,竟然要喊聲: Apple 加油啊~

C++ Grandmaster Certification ( 一代宗師認證)

在 Ptt C_and_CPP 版看到網友的分享:http://www.cppgm.org/index.html

是個免費的線上課程,但結業時要求妳/你寫出一套 C++ compiler (x86_64)、standard library、toolchain ,並且要符合 C++11 標準,過程中不可以使用任何的 3rd party code 。

Participants in this free online course will develop their own C++ compiler, standard library, and toolchain with the following features:

  • Compliant with the latest 2011 standard (C++11)
  • Written entirely in C++ with no third-party dependencies
  • Code generator targeting Linux x86_64
  • Full toolchain including preprocessor, assembler, and linker
  • Will build with itself (self-hosting) and pass provided conformance test suite

真是非常的硬!!!那過了,有什麼好處?目前網頁列出了三項:

  • the title Certified C++ Grandmaster [CPPGM]

CPPGM 認證過後的一代宗師!

  • a letter of recommendation detailing their achievement

CPPGM 會幫你寫一封介紹信!

  • access to a course graduate exclusive professional networking group

一代宗師網路俱樂部!

報名

2013/2/15: 報名截止

2013/3/1: 下一期開課

有興趣的趕快衝啊~不過認真一下,網路上 google 不太到 CPPGM 的詳細資訊,網頁上的自我介紹也很簡單幾句:

ABOUT CPPGM:

The CPPGM Foundation was formed by a software company that
recognized the value to programmer productivity that a good
knowledge of language mechanics had to new developers to
their team.  The C++ Grandmaster Certification began
development as an internal training program, and the
foundation was founded to offer it publicly.

 

或許是某個詐騙集團要騙 code 或是神奇企業的徵才方式?!不過若是能按部就班、完整提供這樣訓練內容的組織,大概也非等閒之輩,精通 C++11 標準、library 還有底層實務。衝著這些,報名去了~

WinDbg Tips: Automatically Break into the Target Computer

適用時機

  • 進行非常早期的 kernel debugging ,希望在 kernel 完成初始化後,立即中斷。
  • 追蹤 boot process 、kernel loader 、休眠啟動(resume from hibernation)。透過適當 BCDEdit 設定,也可用來 debug BootMgr, Winload.exe, WinResume.exe 等程式

說明

在 WinDbg 裡頭又稱作:Change Post-Reboot Break State 。目前共有三種模式可供切換:

No break
除非使用者按下 CTRL+Break 手動觸發一個 break event ,否則不會中斷 target 的運作。預設模式。
Break on reboot
當 target 上的 kernel 完成初始化後,立即中斷。 Break on first module load
當 target 上的 kernel 完成第一個 module 的載入,立即中斷。

語法

CTRL+ALT+K

範例

automatic break

應用

透過 BCDEdit 開啟 boot manager 的 debug :

bcdedit /set {bootmgr} bootdebug on

接著,在 WinDbg 中設定 Break on reboot ,重開機後,我們就可以看到 WinDbg 停在 bootmgr 上了。

bootmgr

感謝

會有這篇文章,是因為上了張銀奎老師的課,張銀奎老師不用多做介紹,他是软件调试[2]的作者,也常在高端调试[3]出沒(應該也是該網站的建立者?!),三天的課程非常精實,把 Windows Internals 用 WinDbg 方式再講一次,受益匪淺 :)

Reference

  1. CTRL+K (Change Post-Reboot Break State) (Windows Debuggers): http://msdn.microsoft.com/zh-tw/library/windows/hardware/ff540326%28v=vs.85%29.aspx
  2. 软件调试:http://advdbg.org/books/swdbg/
  3. 高端调试:http://advdbg.org/

Visual C++ Directories/Inherited Properties Changes

前言

在 Visual C++ 裡頭,C++ Directories 定義了 Visual C++ 這個 IDE 要去哪些路徑下找尋 header files/library files/sources files ,在 2005/2008 裡頭,全域的設定可以在 Tools > Options > Projects and Solutions > VC++ Directories 下找到。

image

圖一、VC++ 2005 下的 VC++ Directories Dialog

這個 dialog 在 VC++ 2010 不復存在了,取而代之的是請使用者使用 property sheet 以及 per-project 的設定。

image

如果好奇為什麼做了這樣的更動,可以參考 [2] 的文章,主要是因為:

  1. 這樣全域的設定太過強大,當使用者有許多 projects 在同一台機器上 build 時,容易混淆出錯。
  2. 因為這個全域的設定檔,是存放在 %LOCALAPPDATA%\Microsoft\VisualStudio\8.0\VCComponents.dat (8.0 為 VC++ 2005,9.0 則是 2008) 這個檔案裡頭,而不是跟著每個專案檔。因此當使用者使用 source control system 時,不會把設定跟著同步到 server 上,check-out 的人也因此無法正確建置專案了。
  3. VCComponents.dat 是給 VCBuild.exe 讀的 INI 檔,而 VC++ 2010 開始改用 MSBuild.exe 來作為統一的 build utility [3] ,因此竟然要改,就連同格式一起換掉吧。

按編:這三個理由實在都很說不過去,不過既然這是 Visual C++ team 的決定,我們也只能接受了 :~

UI 變動

從 2010 之後,變動後的 VC++ Directories 不再放在 Tools > Options > Projects and Solutions > VC++ Directories ,而是以 per-project 方式存在,使用者可以:

  • 對 project 按右鍵,選擇 properties > Configuration Properties > VC++ Directories
  • 或是,從工具列:Project > <your-project-name> properties > Configuration Properties > VC++ Directories

選取 VC++ Directories 。

image

很快地,妳/你會發現,新的 dialog 跟原有的沒什麼差別,而實際上也是,只是現在的變更都是 per-project 的,不再對所有的專案有效,因此若是妳/你想將 Boost library 作為所有 VC++ project 都會使用的函式庫,就沒辦法從這裡完成;也或是,妳/你更新了 Boost 版本,每個已經使用 Boost 又想升級的 projects ,就得手動來更改。

檔案位置與格式變化

說完了使用上的變動,接著是檔案位置的更動,存放的位置從:

%LOCALAPPDATA%\Microsoft\VisualStudio\8.0\VCComponents.dat

換到了:

%LOCALAPPDATA%\Microsoft\MSBuild\v4.0\

下,並且以 Microsoft.Cpp.<Platform>.user 命名:

  • Microsoft.Cpp.ARM.user
  • Microsoft.Cpp.Itanium.user
  • Microsoft.Cpp.Win32.user
  • Microsoft.Cpp.x64.user

image

新的檔案格式稱作:Property sheet ,附檔名 props 。有了這個檔案,我們還是有機會可以做到 2005 & 2008 時的全域設定。先前提到了:為了迎接 MSBuild 的來臨,VC++ 將 INI 格式變成了 XML 格式,所以客製化上還是很簡單的,任選一個 platform 的檔案打開:

image

所以一種修改方式,就是直接用編輯器修改 XML ,看想修改哪些就可以改,值得一提的是,以往在 2005 & 2008 裡頭,路徑名不可以是 Visual C++ 裡頭的 Macro ,現在反倒是可以了,因為 IDE 是將檔案中的值讀入後才做解析。另外一種方式則是透過 Visual C++ 內建的 Property Manager 修改,我們放到下一節介紹。

Inherited Values

wdk803

當 Visual C++ 將 property sheet 讀入後,會當做 Inherited values ,對於當前 project 來說,是不可編輯的,我們必須繞到 props 檔去,或是使用 property manager 編輯。property manager 可以從工具列 > View > Property Manager 找到:

image

叫起 Property Manager 後,可以看到目前 project configuration 所涵蓋的 platform 的設定。 Double click 後,可以看到跳出一個視窗,

image

跳出來的視窗長得很像 project properties ,不過仔細看會發現 Configuration 和 Platform 已經被鎖死成對應的 property sheet 檔所代表的設定,接著,直接編輯,然後存檔,這樣就完成了修改全域 property sheet 了。

VC++ 2010 & VC++2012

值得注意的是,property sheet 放在 MSBuild 的 local app data 下,而目前 2010 和 2012 所使用的 MSBuild 都是 4.0 [4],因此一旦我們修改了 property sheet ,可是會同時影響到 2010 跟 2012 的 ... Orz ...

Reference

  1. Inherited Properties and Property Sheets: http://blogs.msdn.com/b/vsproject/archive/2009/06/23/inherited-properties-and-property-sheets.aspx
  2. VC++ Directories: http://blogs.msdn.com/b/vsproject/archive/2009/07/07/vc-directories.aspx
  3. VCBuild vs. C++ MSBuild on the Command Line: http://blogs.msdn.com/b/vcblog/archive/2010/01/11/vcbuild-vs-c-msbuild-on-the-command-line.aspx
  4. MSBuild Toolset: http://msdn.microsoft.com/en-us/library/bb383796.aspx

VC++ 2012 Can't Build Driver Project

這幾天終於趁著聽課的空檔,把電腦裝了 VC++ 2012 ,這版的一大特色:整合了 device driver 的開發。很快地試玩了一下它的 project wizard :

wdk801

結果,很不幸地失敗 ...

1>C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h(10920): fatal error C1003: error count exceeds 100; stopping compilation

再看一下 Error list:

Error    1    error C2220: warning treated as error - no 'object' file generated    C:\Program Files (x86)\Windows Kits\8.0\Include\shared\sal.h    2884
Error    4    error C2054: expected '(' to follow '_IRQL_requires_same_'    C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h    1897
Error    5    error C2085: 'EXCEPTION_ROUTINE' : not in formal parameter list    C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h    1903
Error    6    error C2085: 'EXCEPTION_ROUTINE' : not in formal parameter list    C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h    1905
Error    7    error C2143: syntax error : missing ';' before '*'    C:\Program Files (x86)\Windows Kits\8.0\Include\shared\ntdef.h    1905
Error    8    error C2143: syntax error : missing ')' before 'constant'    C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h    10384
Error    9    error C2143: syntax error : missing '{' before 'constant'    C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h    10384
Error    10    error C2059: syntax error : '<Unknown>'    C:\Program Files (x86)\Windows Kits\8.0\Include\KM\wdm.h    10384

真的是很 ooxx 。問題都是發生在 sal.h, ntdef.h wdm.h 等等基本的 header files 裡。

這類的問題通常是發生在 include 了錯誤的 header files ,所以回頭檢視 solution property : Project >  Properties > Configuration Properties > VC++ Directories 。

image

include directories 多了很多路徑,其他像是 Executable Directories 、Library Directories 也有一樣的問題。嗯嗯,VC++ 2012 匯入了電腦上其他版本 VC++ 的設定,可能的原因之一:安裝完 2012 、第一次啟動時,我允許了 2012 去找尋舊版本的設定並匯入所導致。再點開 Include Directories 看一下:

image

發現這些多餘的路徑都是來自 Inherited values 。這裡,我試了兩種解決方式:

  1. 解法一
    取消 Inherit from parent or project defaults ,然後重新把需要的路徑一個一個加回去,也就是手動把
    $(ProjectDir)、$(CRT_INC_PATH)、$(ddk_INC_PATH)、$(KIT_SHARED_INC_PATH) 加上去。手動作這件事除了麻煩外,還只能用在這個 project 上,也就是一旦我開了新的 driver solution 或是在當下 solution 下新增新的 driver project 都得重做一次。
  2. 解法二
    比較一勞永逸, VC++ 把這個 Inherited Values 定義在 C:\Users\<user-name>\AppData\Local\Microsoft\MSBuild\v4.0 下的檔案裡頭,檔案是根據不同 configuration 而命名:
    Microsoft.Cpp.ARM.user.props
    Microsoft.Cpp.Itanium.user.props
    Microsoft.Cpp.Win32.user.props
    Microsoft.Cpp.x64.user.props
    *.props 是 XML 格式,把不用的 path 都清除乾淨,只留下 default value 就可以了。
    <?xml version="1.0" encoding="utf-8"?>
    <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <ExecutablePath>$(ExecutablePath)</ExecutablePath>
        <IncludePath>$(IncludePath)</IncludePath>
        <ReferencePath>$(ReferencePath)</ReferencePath>
        <LibraryPath>$(LibraryPath)</LibraryPath>
        <SourcePath>$(SourcePath)</SourcePath>
        <ExcludePath>$(ExcludePath)</ExcludePath>
      </PropertyGroup>
    </Project>

    這個方法是比較一勞永逸,不過有個大缺點:不同版本的 Visual C++ 會共用這個檔案。像我的電腦修改了後, VC++ 2010 的 VC++ project 的預設值也會受到影響,但已經產生的、既有 project 則不受影響,算是不幸中的大幸。

Facebook App -- Getting Started with Apps on Facebook + Heroku

Facebook App 時下分成三類:

  1. Facebook for Websites
    這類的 apps ,可以讓妳/你使用 Facebook 提供的 social plugins 來幫自己的網站加分,像是在你的網頁中加入"讚"按鈕、讓使用者使用 Facebook 帳號登入、存取使用者在 Facebook 的資料。
  2. Mobile Apps
    Facebook 分別在 iOS 和 Android 上提供 Object-C 和 Java 版本的 SDK ,來讓開發者可以提供 native apps。其他還有 PHP 和 JavaScript 可以選擇。
  3. Apps on Facebook
    把我們的 apps 整合在 Facebook 上,使用者必須透過 Facebook 來存取我們的 apps , Facebook 提供適當的頁面,並把我們的 apps 導入其中的 iframe 裡頭。

特別要注意的是, Facebook 只提供我們使用 Facebook 的 social platform ,但它並不提供 code hosting ,因此開發 Apps on Facebook 需要自己準備 web server 或是第三方的 web hosting ,運氣不錯的是,Facebook 和 Heroku 有正式的合作,因此若是找不到代管服務或是懶得自己架站的開發者,可以先使用 Heroku 來試試看。

Apps on Facebook

步驟一

登入 https://developers.facebook.com/apps 。第一次登入時, Facebook 會詢問您相關的 permission ,選 allow 就可以。之後就會看到下面的畫面,點選 Create New App

FB_App_1

步驟二

image

  • App Name: 輸入妳/你的 app name 。
  • App Namespace: 其實就是我們 app 的 URL 。它會附加在 http://apps.facebook.com 之後,所以必須是個 unique name ,另外還有一些名規則。 輸入完後 Dialog 會幫你 check ,若是失敗也會報錯。
  • Web Hosting: 目前 Facebook 只跟 Heroku 官方合作,所以要是我們沒有自己的 hosting server ,也可以考慮一下 Heroku ,它支援 PHP 、 Python 、 JavaScript 、 Ruby 。

步驟三

跟 Facebook 說一下,妳/你不是機器人吧~

步驟四

image

選擇一下妳/你想用的開發語言。

image

若是事先沒有 Heroku 帳號也不用擔心,透過這個步驟填入 email 就會幫妳/你申請。

步驟五

image

看到這個 dialog 出現,就恭喜妳/你成功囉。

步驟六

當妳/你點下 Go To App 後,瀏覽頁會被導到 Heroku 代管的 app page 去,以這邊的例子會是:http://evening-plateau-9255.herokuapp.com

FB_App_3

開發

步驟一、找到對應的 Git repository

若是之前沒有 Heroku 的帳號,當完成上面的步驟後,Heroku 會寄一封啟動信到步驟四的信箱去。或是之前已經有 Heroku 帳號,可以直接到 https://dashboard.heroku.com/apps? 去看一下,會發現多了一個 evening-plateau-9255 ,點進去後,請找 Settings > Info > Git URL ,以本文的例子, Heroku 配給我們的 git url 是 git@heroku.com:evening-plateau-9255.git

步驟二、下載 Herolu toolbelt

toolbelt 是 cmd line 工具包,主要包含了 Heroku 環境初始化工具、app 的 local 模擬和 Git 。

步驟三、初始化環境

安裝完 toolbelt 後,它應該已經被加入到環境變數裡頭。這時,請準備一個新資料夾來當做 working folder ,並使用 cmd.exe 切到該 folder 下,執行:

heroku login

image

步驟四、Clone 一份來改吧

還記得自己的 app git URL 嗎?

git clone git@heroku.com:your_app_name_in_heroku.git -o heroku

到這裡,妳/你已經可以開始做些了,若是不熟悉 Git ,可以參考下面指令把你的修改上傳到 server 上去:

git commit -am "commit message"
git push heroku master

Reference

  1. Canvas Tutorial: http://developers.facebook.com/docs/appsonfacebook/tutorial/
  2. Getting Started with Your Facebook App on Heroku: https://devcenter.heroku.com/articles/facebook

Windows Debugging 2 – Kernel Debugging with WinDbg and VMware

Windows Vista 之後已經不再將開機選項儲存於 boot.ini 中,而是改以 BCD (Boot Configuration Data) 儲存,因此以前透過編輯 boot.ini 啟動 Windows kernel debugging 的方法也得更新一下。這裡我們透過 bcdedit.exe 來編輯 BCD ,讓 Windows 啟動後可以進入 debug mode 。

VMware 設定

Debuggee 部分,和以前一樣使用 named piped 來替 guest OS 模擬 serial port (COM port)。

  1. 進入 guest OS 設定。 com port
  2. 新增硬體。
    com port 2
  3. 選擇 Serial Port 裝置。
    com port 3
  4. 選擇 named pipe 來模擬我們的 serial port。
    com port 4
  5. 替 named pipe 取一個名字,這個名字等會會被 WinDbg 使用。
    com port 5
  6. 最後別忘了把 Yield CPU on Poll 選項勾起。
    com port 6
  7. 完成新增後,請留意 VMware 替我們新增的 Serial Port 的編號,以上圖為例是:Serial Port 2 

Windows Boot 設定

  1. 使用 administrator 權限開啟一個 cmd。
    start cmd
  2. 透過 BCDEdit 編輯開機選項,使用 COM port (serial port)來做為 debugger 和 debuggee 間的溝通,debugport 的編號請根據 VMware 的 Serial Port 設定而定:
    bcdedit /dbgsettings serial debugport:2
  3. 複製現有的開機選項到 DebugEntr:
    bcdedit /copy {current} /d DebugEntry
    執行完該指令後,會出現一個 ID ,代表我們要複製出來的開機選項,該 ID 在每台電腦上都不太一樣。
  4. 調整開機選樣的順序:
    bcdedit /displayorder {current} {ID}
  5. 打開 debug 選項:
    bcdedit /debug {ID} ON
  6. 設定成預設開機選項:
    bcdedit /default {ID}
    bcdedit edit

 

Enable Debug Print

Vista之後,DbgPrintEx()、vDbgPrintEx()、vDbgPrintExWithPrefix()、KdPrintEx() 等選擇性的輸出函式預設是關閉的,要啟動的話,需要到 registry 上設定對應的 log level [3]:

到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter 下新增 DEFAULT=dword:0000ffff
實際數值,依照需求而定,0000ffff 足以應付大多數用途。

Debug Print Filter

使用

在 bcdedit 設定完成後,重開機,便會看到新的開機選項 DebugEntry:

boot 1

選擇 DebugEntry 後,我們便可以在 debugger 的機器上的 cmd 執行下面指令來進行 kernel debugging,com_1 就是我們當初在 VMware guest OS 裡頭設定的名字:

windbg -b -k com:pipe,port=\\.\pipe\com_1,resets=0

停用驅動程式簽章增強(Disable Driver Signature Enforcement)

Vista 之後的 64-bit OS ,Microsoft 都會要求 drivers 必須有 WHQL 的認證才能啟動。這對於一般使用者來說可能沒有影響,但對於有在開發、測試 drivers 的人就會是個問題。

最簡單的方式是,開機時,使用 F8 進入進階模式:
boot 1
並選擇(停用驅動程式簽章增強)Disable Driver Signature Enforcement。
boot 2

Test Signing

若是 drivers 有 test sign ,那麼我們還可以使用 BCDEdit 來允許 testing sign driver 的載入[4]:
bcdedit /set TESTSIGNING ON
設定完成後,需要重開機。

要驗證 test sign 是否成功,可以使用 bcdedit 檢查,或是觀察桌面右下角的文字說明:

test signing

Reference

  1. Boot Parameters to Enable Debugging
    http://msdn.microsoft.com/en-us/library/windows/hardware/ff542279%28v=vs.85%29.aspx
  2. Kernel Debugging in Windows Vista
    http://msdn.microsoft.com/en-us/windows/hardware/gg487520
  3. Reading and Filtering Debugging Messages
    http://msdn.microsoft.com/en-us/library/windows/hardware/ff551519%28v=vs.85%29.aspx
  4. The TESTSIGNING Boot Configuration Option
    http://msdn.microsoft.com/en-us/library/windows/hardware/ff553484%28v=vs.85%29.aspx

See Also

  1. Windows Debugging – Kernel Debugging with WinDbg and VMware
    http://keikoblog.blogspot.com/2009/03/windows-debugging-kernel-debugging-with.html

VMware 64-bit Guest OS Support

去年聖誕節買的 x201 ,下單前還特別注意 CPU 是否支援 Virtualization ,直到上個月拿來跑 64 bit guest OS 時,才發現 Windows 會進入開機、當機的無窮迴圈。後來才發現一些小眉角,尤其是在筆電上:

  1. 不是 64-bit OS 就可以直接支援 64-bit guest OS ,必須 CPU 有支援 VT 才行。
  2. 大多數筆電的 VT 預設值都是關閉,必須進入 BIOS 啟動。
  3. 在 BIOS 中,若有 Hardware data execution 之類的 H/W security 功能最好也關閉。
  4. 部分筆電,像是 Lenovo x201 ,要讓上述設定生效,還必須將電池、電源移除,按下電源開關,進行放電, BIOS 的改動才會生效。

測試方式

如果不確定 CPU 是否支援 VT ,可以到 Intel/AMS 的網站查詢,像是 Intel Processor Spec. Finder 或 AMD Desktop Processor。

Processor Check for 64-Bit Compatibility

[感謝 Allen Lai 提醒]

VMware 提供了一個快速的檢測工具——Processor Check for 64-Bit Compatibility,雖然已經很久沒更新了,不過它的結果明確易懂,像是下圖就說明:Processor Check for 64-Bit Compatibility

不過根據說明文件,它目前只支援特定的 processors ,而且還有幾項是 experimental support:

  • AMD™ Athlon™ 64, revision D or later
  • AMD Opteron™, revision E or later
  • AMD Turion™ 64, revision E or later
  • AMD Sempron™, 64-bit-capable revision D or later (experimental support)
  • Intel™ EM64T VT-capable processors (experimental support)

VMware CPU Identification Utility

或是利用 VMware 的 CPU Identification Utility ,這是個 可用來開機的 ISO 檔,使用時只需要掛載進 VMware 即可 。

未啟動 VT 時的輸出:

CPU Identification Utility (Before)

啟動 VT 後:

CPU Identification Utility

上面的圖是 x201 開啟 VT 後的結果,根據說明文件和實驗,似乎紅框裡有一個是 Yes ,就可以支援 64-bit guest OS 。根據 Allen Lai 的測試,即使 64-bit LongMode 顯示 YES ,也並非能支援 64-bit guest OS。所以目前還沒有比較好的解讀方式 … 大概只是方便你把結果丟到 support 或 community 去發問 …

最後要注意的是,當 CPU features 設定改變後,都可能讓已經建立好的 snapshots 出問題。

Reference

  1. VMware Shared Utility: http://www.vmware.com/download/shared_utilities.html
  2. Ensuring your hardware is functioning correctly: http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003690
  3. Ensuring Virtualization Technology is enabled on your VMware host: http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003944
  4. Hardware and firmware requirements for 64-bit Guest Operating Systems: http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1003945
  5. Intel Processor Spec. Finder: http://ark.intel.com/Default.aspx
  6. AMD Desktop Processor: http://products.amd.com/en-us/desktopcpuresult.aspx

更新

2011/4/26: 補上 x201 未啟動 VT 的 VMware CPU Identification 結果截圖。

2011/4/27: 補充 Allen Lai 的連結和測試工具 Processor Check for 64-Bit Compatibility 。

Re: GCC Bug

原文:GCC Bug

哈,這次老師給我的作業可是難題,有好一陣子沒有碰觸 gdb ,更何況這次還要跟 FPU 打交道,下面是我的測試程式,和原文只有小部份差異,但重點部份是一樣的:

#include <cstdio>
using namespace std;

double f(double z)
{
    return z;
}

void foo(double fraction)
{
    double z = fraction * 20.0;
    int t1 = z;
    int t2 = f(z);
    printf("t1=%d, t2=%d\n", t1, t2);
}

int main()
{
    foo(6.0/20.0);
    return 0;
}

問題

輸出:

6, 6

是我們預期的結果。但在某些情況下卻會產生

5, 6

這樣的輸出。但若是在 compile 時加上 -ffloat-store 則又能得到正確的結果。根據 gcc optimization options 的說明:

Do not store floating point variables in registers, and inhibit other options that might change whether a floating point value is taken from a register or memory.

我們懷疑是從 register 讀出時發生意想不到的進位,導致結果有所出入。

實驗

一開始在嘗試 reproduce 時就遇上問題,在和 aaa 討論後才發現,我們使用的 gcc 版本和 compile options 都不盡相同。最後確定了發生問題的環境:

  1. cygwin + gcc 3.4.4
  2. gcc –O text.c

下面是我嘗試過的平台和結果:

  -O -O3
gcc 4.4.1 No No
gcc 3.4.4 (cygwin) Yes No

有趣的開始!

O3 版本

g++ -Wall -O3 -S -masm=intel main.cpp

這個版本是最好理解的, compiler 已經最佳化到:

  • 不呼叫 foo()、f(),直接以常數取代。(沒記錯,這算是 compiler const folding 和 const propagation 的範疇)

看一下 assembly 就很清楚:

LC2:
	.ascii "t1=%d, t2=%d\12\0"
	.align 4
...
_main:
    push   ebp
    mov    eax, 16
    mov    ebp, esp
    sub    esp, 24
    and    esp, -16
    call   __alloca
    call   ___main
    mov    DWORD PTR [esp], OFFSET FLAT:LC2 ; 看一眼 LC2 ,一個取址動作 :)
    mov    edx, 6
    mov    eax, 6
    mov    DWORD PTR [esp+8], edx
    mov    DWORD PTR [esp+4], eax
    call   _printf
    leave
    xor    eax, eax
    ret

O 版本

g++ -Wall -O -S -masm=intel main.cpp

O 版本就沒那麼聰明,而是真正的去呼叫 foo() :

_main:
    push   ebp
    mov    ebp, esp
    sub    esp, 8
    and    esp, -16
    mov    eax, 16
    call   __alloca
    call   ___main
    fld    QWORD PTR LC4
    fstp   QWORD PTR [esp]
    call   __Z3food
    mov    eax, 0
    leave
    ret

而且可以發現,LC4 是 foo() 的參數:

LC4:
	.long	858993459
	.long	1070805811
	.text
	.align 2


0.3 的 IEEE 754 Double precision (64 bits) 表示式:
0 01111111101 00110011001100110011  110011001100110011001100110011
                        1070805811                       858993459

嗯嗯,O 版本先做了簡單的運算展開。不過在進入 foo() 的 floating point 計算前,得先對 x87 FPU 有簡單的概念 :

image節錄自: Intel® 64 and IA-32 Architectures Software Developer’s Manual ─ Volume 1: Basic Architecture

x87 上用來儲存資料的 registers 被組織成一塊 stack 的,並且搭配了 control, status 等 registers 可供控制、查詢之用。 Stack 以下圖的方式運做:

image節錄自: Intel® 64 and IA-32 Architectures Software Developer’s Manual ─ Volume 1: Basic Architecture

Stack 的 top 又稱為 ST(0),是許多 FPU 指令的隱藏 operand 之一,像是待會在 foo() 看見的 fld、fstp、fmul 指令都會 implicitly 操作 ST(0) 。

__Z3food:
    push    ebp
    mov     ebp, esp
    push    ebx
    sub     esp, 20
    fld     DWORD PTR LC1
    fmul    QWORD PTR [ebp+8]
    fnstcw  WORD PTR [ebp-6]
    movzx   eax, WORD PTR [ebp-6]
    or      ax, 3072
    mov     WORD PTR [ebp-8], ax
    fldcw   WORD PTR [ebp-8]
    fist    DWORD PTR [ebp-12]      ; store integer
    fldcw   WORD PTR [ebp-6]
    mov     ebx, DWORD PTR [ebp-12]
    fstp    QWORD PTR [esp]
    call    __Z1fd
    fnstcw  WORD PTR [ebp-6]
    movzx   eax, WORD PTR [ebp-6]
    or      ax, 3072
    mov     WORD PTR [ebp-8], ax
    fldcw   WORD PTR [ebp-8]
    fistp   DWORD PTR [esp+8]
    fldcw   WORD PTR [ebp-6]
    mov     DWORD PTR [esp+4], ebx
    mov     DWORD PTR [esp], OFFSET FLAT:LC2
    call    _printf
    add     esp, 20
    pop     ebx
    pop     ebp
    ret

這邊使用倒推法,來找到 t1 及其在 assembly 的表示,尋著上頭的 highlight 部份,可以發現:

  1. printf 從 ebx 拿到 t1 的值,也就是 ebx 、
  2. ebx 由 ebp-12 得到、
  3. ebp-12 則是 fist 的結果,也就是 FPU 的 ST(0)、
  4. 往上回溯,只有 L6 處有個 fld ,因此,ST(0) 指的是 fmul 的結果、

反推的過程也符合程式的邏輯,所以下一步就是用 debugger 去追蹤:

Breakpoint 1, 0x0040104f in foo ()
1: x/i $pc
0x40104f <_Z3food+7>:   flds   0x402030
(gdb) info float
  R7: Empty   0x3ffd9999999999999800
  R6: Empty   0x00e07c93022200240000
  R5: Empty   0x51c27c92fe956123a74c
  R4: Empty   0x79d37c9a06007c930460
  R3: Empty   0x0a887c9479870022bf80
  R2: Empty   0xbf9800010a886123debc
  R1: Empty   0x00006123a74c7c93043e
=>R0: Empty   0xdee46123de0c6123d8cc

Status Word:         0xffff0000
                       TOP: 0
Control Word:        0xffff037f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round to nearest
Tag Word:            0xffffffff
Instruction Pointer: 0x1b:0x004010c8
Operand Pointer:     0xffff0023:0x0022cd10
Opcode:              0xdd1c

我們將中斷點設在 foo() 函式,接著使用 info float 去 dump FPU 相關的 register (FPU 相關 register 似乎無法加到 gdb 的 Automatic Display!?所以每次都需手打 ),比較特別的幾個要點都被 highlight 了:

  1. R0 ~ R7 即剛剛提到的 FPU data register stack : stack 目前是空的,雖然指向 R0 ,但顯示為 Empty
  2. Control Word 裡頭的 RC (rounding-control)決定了 floating-point 的進位(round)方式。
    image 
    節錄自: Intel® 64 and IA-32 Architectures Software Developer’s Manual ─ Volume 1: Basic Architecture

認識了 gdb 對於 FPU 的輸出後,可以開始一步步逼近問題核心 ── ebp-12 了。

foo() 的一開始,是讀入 0x402030 上的值到 FPU stack 上:

Breakpoint 1, 0x0040104f in foo ()
1: x/i $pc
0x40104f <_Z3food+7>:   flds   0x402030

好奇這 magic number 是何來的,可以透過之前的 disassembly 結果:

LC1:
	.long	1101004800    ; = 0x41a00000
	.text
	.align 2

或是動態的用 debugger 觀看:

(gdb) x 0x402030
0x402030 <_data_start__+48>:    0x41a00000 ; = 1101004800

而 0x41a00000 便是 20.0 的 IEEE 754 0.3 的 IEEE 754 Double precision (64 bits) 表示式。來用 debugger 確認一下:

Breakpoint 1, 0x0040104f in foo ()
1: x/i $pc
0x40104f <_Z3food+7>:   flds   0x402030
(gdb) info float
  R7: Empty   0x3ffd9999999999999800
  R6: Empty   0x00e07c93022200240000
  R5: Empty   0x51c27c92fe956123a74c
  R4: Empty   0x79d37c9a06007c930460
  R3: Empty   0x0a887c9479870022bf80
  R2: Empty   0xbf9800010a886123debc
  R1: Empty   0x00006123a74c7c93043e
=>R0: Empty   0xdee46123de0c6123d8cc

(gdb) si
0x00401055 in foo ()
1: x/i $pc
0x401055 <_Z3food+13>:  fmull  0x8(%ebp)

(gdb) info float
=>R7: Valid   0x4003a000000000000000 +20
  R6: Empty   0x00e07c93022200240000

執行完上面的指令後, FPU 的 data stack 現在是 20 ,接著進行 x 0.3 的乘法(ebp+0x8 是 foo() 的參數,即 0.3):

0x401055 <_Z3food+13>:  fmull  0x8(%ebp)

(gdb) si
0x00401058 in foo ()
1: x/i $pc
0x401058 <_Z3food+16>:  fnstcw -0x6(%ebp)
(gdb) info float
=>R7: Valid   0x4001bffffffffffffe00 +6
  R6: Empty   0x00e07c93022200240000

得到了結果:+6 ,無誤。不過接下來的 FPU control word 卻令人意外:

(gdb) info float
...
Control Word:        0xffff037f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round to nearest

0x40105f <_Z3food+23>:  or     $0xc00,%ax
0x401063 <_Z3food+27>:  mov    %ax,-0x8(%ebp)
0x401067 <_Z3food+31>:  fldcw  -0x8(%ebp)

(gdb) info float
...
Control Word:        0xffff0f7f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round toward zero

O 版將 RC 從 Round to nearest 改變成 Round toward zero 。這會影響到 fist 的運作。在執行 fist 前,我們把預期的結果: 6.0 的 IEEE 754 Double precision 求出來:0x4018000000000000 ;而目前的 R7 值是 0x4001bffffffffffffe00 ,小於該值。表示 R7 現在是不足 6.0 ,而此時又改以 Round toward zero 來進行取整數,那結果會是 5 則是預期了。

(gdb) si
0x0040106a in foo ()
1: x/i $pc
0x40106a <_Z3food+34>:  fistl  -0xc(%ebp)
(gdb) info float
=>R7: Valid   0x4001bffffffffffffe00 +6
  R6: Empty   0x4bc000010101e259acd0
...
Control Word:        0xffff0f7f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round toward zero

(gdb) x 0x22ccfc              ; ebp - 0xc = 0x22ccfc
0x22ccfc:       0x61100049

(gdb) si
0x0040106d in foo ()
1: x/i $pc
0x40106d <_Z3food+37>:  fldcw  -0x6(%ebp)
(gdb) x 0x22ccfc
0x22ccfc:       0x00000005

執行結果印證了我們的預期。

-ffloat-store 版

模仿取得 O 版 disassembly 的方式來取得 -ffloat-store 的結果:

g++ -Wall -O -ffloat-store -S -masm=intel main.cpp

-ffloat-store 版和 O 版相同,會去呼叫 foo() ,不過比較兩者 foo() 可以發現: -ffloat-store 版只多了兩行,在進行 x 0.3 後,會馬上將值寫回 call stack 上,並再讀入。

image也就是,當 6.0 被寫入記憶體時,是以 Round to nearest 的方式寫進:

(gdb) si
0x00401058 in foo ()
1: x/i $pc
0x401058 <_Z3food+16>:  fstpl  -0x10(%ebp)
(gdb) info float
=>R7: Valid   0x4001bffffffffffffe00 +6
  R6: Empty   0xdbc000010101e4ada9d8
...
Control Word:        0xffff037f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round to nearest

而讀回 FPU stack 時,已經是個精確的 6.0 :

(gdb) si
0x0040105e in foo ()
1: x/i $pc
0x40105e <_Z3food+22>:  fnstcw -0x12(%ebp)
(gdb) info float
=>R7: Valid   0x4001c000000000000000 +6
  R6: Empty   0x10000000000400010000
...
Control Word:        0xffff037f   IM DM ZM OM UM PM
                       PC: Extended Precision (64-bits)
                       RC: Round to nearest

WinDbg 實驗: Command Line Arguments 的傳遞

一個 C/C++ 程式可以透過 main() 的 argument list 取得 client 端輸入的 command line arguments:

int main( int argc, char* argv[] ) { ... }

如果好奇這是如何地從無到有,可以寫一段程式碼來 trace 。

先準備好 sample code:

#include <iostream>
using namespace std;

int main( int argc, char* argv[] )
{   // 程式的內容不是重點
    cout << "hello world";
    cin.get();
    return 0;
}

透過 VC++ 或是 WinDbg 在 main() 設定 breakpoint 來追蹤:

  1. Compiler: VC++ 2005
  2. OS: Windows XP
  3. 在 cmd 中執行 CrtDemo05 test

image

0:000> bp CrtDemo05!main
*** WARNING: Unable to verify checksum for CrtDemo05.exe

0:000> bl
 0 e 004377d0     0001 (0001)  0:**** CrtDemo05!main

0:000> g
Breakpoint 0 hit
eax=00383088 ebx=7ffda000 ecx=00383028 edx=00000001 esi=010df742 edi=010df6f2
eip=004377d0 esp=0012ff58 ebp=0012ffb8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
CrtDemo05!main:
004377d0 55              push    ebp

0:000> k
ChildEBP RetAddr  
0012ff54 0044a203 CrtDemo05!main [f:\src\_experiment\crtdemo05\crtdemo05\main.cpp @ 6]
0012ffb8 00449fbd CrtDemo05!__tmainCRTStartup+0x233 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 327]
0012ffc0 7c816d4f CrtDemo05!mainCRTStartup+0xd [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 196]
0012fff0 00000000 kernel32!BaseProcessStart+0x23

進入 main() 之前的兩個函式都是 CRT 的一部分,負責完成基本但必要的初始化,舉凡 Global variables、Heap、I/O 等等都屬於這個範疇。這樣一來,身處 main() 後頭的我們才能順利工作。所以照這情勢看來,想知道 argument list 怎麼來,就得去 trace 這兩個 functions ,幸運的, VC++ CRT 的 source code 是隨著安裝程式散發的,通常就在:

C:\Program Files\Microsoft Visual Studio 8\VC\crt

視安裝路徑而定。也可以 double click VC++ 的 Call Stack 進入程式碼裡頭。

如果想利用 debugger 追蹤或試驗 CRT ,不妨把 CRT link 改成 debug mode 並且是 static link 。這可以省下一些時間、增加 tracing 時的可讀性和便利性,因為像是 debug 下的 dynamic link 會使用 ILT ,它所引入的間接性,會讓定位 functions 或 symbols 徒增額外的時間,造成實驗的不便。

VC++ 有所謂的 Microsoft C++ ,其程式進入點有自己的一套方式來定義,至少在名稱上,就可以找到四種:

  1. main
  2. wmain
  3. WinMain
  4. wWinMain

不過它們使用的 CRT 是同一份程式碼,並且使用了跟 TCHAR.h 相同的手法來區別,但確實由 WPRFLAG 這個 macro 所控制凡;凡是見到 t、_t、__t 開頭的名稱,都有機會透過它來替換掉,不過 __tmainCRTStartup() 可說是例外。下表節錄與 command line 相關的 t、_t、_t 開頭 symbols:

 

ansi/console

ansi/GUI

wide/console

wide/GUI

_tmainCRTStartup

mainCRTStartup

WinMainCRTStartup

wmainCRTStartup

wWinMainCRTStartup

_tcmdln

_acmdln

_acmdln

_wcmdln

_wcmdln

_targv

__argv

__argv

__wargv

__wargv

GetCommandLineT()

GetCommandLineA

GetCommandLineA

__crtGetCommandLineW

__crtGetCommandLineW

_tsetargv()

_setargv

_setargv

_wsetargv

_wsetargv

我們先從 ansi 版的 main() ,也是 C/C++ Standard 所描述的 main() 開始:

mainCRTStartup()

mainCRTStartup() 基本上是一個 forward function :

int _tmainCRTStartup( void )
{
    __security_init_cookie();
    return __tmainCRTStartup();
}

__tmainCRTStartup()

就像前面提到的:__tmainCRTStartup() 就是正港的 __tmainCRTStartup() 沒有 ansi, wide 的替換,很快地就可以梳理出跟 command line 相關的程式:

int __tmainCRTStartup( void )
{
    // ...

    __try {
        // ...
    
        /* get wide cmd line info */
        _tcmdln = (_TSCHAR *)GetCommandLineT();

        /* get wide environ info */
        _tenvptr = (_TSCHAR *)GetEnvironmentStringsT();

        if ( _tsetargv() < 0 )
            _amsg_exit(_RT_SPACEARG);
        if ( _tsetenvp() < 0 )
            _amsg_exit(_RT_SPACEENV);

        // ...

    #ifdef _WINMAIN_
        lpszCommandLine = _twincmdln();
        mainret = _tWinMain( (HINSTANCE)&__ImageBase,
                             NULL,
                             lpszCommandLine,
                             StartupInfo.dwFlags & STARTF_USESHOWWINDOW
                                  ? StartupInfo.wShowWindow
                                  : SW_SHOWDEFAULT
                            );
    #else  /* _WINMAIN_ */
        _tinitenv = _tenviron;
        mainret = _tmain(__argc, _targv, _tenviron);
    #endif  /* _WINMAIN_ */

        // ...
    }
    __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) {
        // ...
    }

    return mainret;
}

從上面的程式碼可以發現,重點就在於兩個 functions 上:

  1. GetCommandLineT()
  2. _tsetargv()

但是當我們將 breakpoint 設在 GetCommandLineT() 時,卻無法 step in 。這時可以切換到 assembly 模式,透過 WinDbg diassembly window 或是 WinDbg 的 uf 幫助來持續追蹤:

0:000> uf __tmainCRTStartup
...
CrtDemo05!__tmainCRTStartup+0x1b4 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 300]:
  300 0044a184 ff1564924b00    call    dword ptr [CrtDemo05!_imp__GetCommandLineA (004b9264)]
  300 0044a18a a3a4814b00      mov     dword ptr [CrtDemo05!_acmdln (004b81a4)],eax
  303 0044a18f e8a89efeff      call    CrtDemo05!ILT+55(___crtGetEnvironmentStringsA) (0043403c)
  303 0044a194 a3d45e4b00      mov     dword ptr [CrtDemo05!_aenvptr (004b5ed4)],eax
  305 0044a199 e87a9ffeff      call    CrtDemo05!ILT+275(__setargv) (00434118)
  305 0044a19e 85c0            test    eax,eax
  305 0044a1a0 7d0a            jge     CrtDemo05!__tmainCRTStartup+0x1dc (0044a1ac)
...

在 Windows 裡頭, _imp、__imp 開頭的 name mangling 代表: symbol 代表是從 DLL export 出的 ,可見 CrtDemo05!_imp__GetCommandLineA 存在與另外一個 DLL 之中。 dereference 該位址便可以找到真正的 symbol 。

0:000> dd 004b9264
004b9264  7c812c8d 7c93043d 7c812851 7c9305d4
004b9274  7c80aa49 7c80a0c7 7c809cad 7c832e2b

由於 CrtDemo05 裡頭使用 call 來呼叫這個位址,想必這個位址會是一段可以執行的指令,使用 uf 來查找:

0:000> uf 7c812c8d 
kernel32!GetCommandLineA:
7c812c8d a1f435887c      mov     eax,dword ptr [kernel32!BaseAnsiCommandLine+0x4 (7c8835f4)]
7c812c92 c3              ret

原來是落在 kernel32.dll 這個基礎函式庫裡頭,而且發現它的實做相當簡單:僅僅是從 kernel32!BaseAnsiCommandLine+0x4 位址讀取而已。為了驗證,我們以 dereference 該位址看是否真的儲存了要傳遞給 CrtDemo05 的 arguments 。

0:000> dd 7c8835f4
7c8835f4  00151ee0 000a0008 7c80e300 ffff02ff
7c883604  00000001 000a0008 7c831874 000a0008

接著

0:000> da 00151ee0 
00151ee0  "C:\test\CrtDemo05.exe test"

若是覺得 dd 兩層的 dereference 有點麻煩,針對字串可以使用 dp* ,

kd> dpa 7c8835f4 L1
7c8835f4  001522f8 "CrtDemo05.exe"

無論是哪種方式,都驗證了我們的想法── kernel32!GetCommandLineA() 是取得 command line 的函式,並且有著極簡單的實做──讀取一個預先填好值的 variable。

wmain() 版本也是相同的:

0:000> uf CrtDemo05!__tmainCRTStartup
...
CrtDemo05!__tmainCRTStartup+0x1b4 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 300]:
  300 0044a194 e8ad9efeff      call    CrtDemo05!ILT+65(___crtGetCommandLineW) (00434046)
  300 0044a199 a3e4824b00      mov     dword ptr [CrtDemo05!_wcmdln (004b82e4)],eax
  303 0044a19e e8439ffeff      call    CrtDemo05!ILT+225(___crtGetEnvironmentStringsW) (004340e6)
  303 0044a1a3 a3d85e4b00      mov     dword ptr [CrtDemo05!_wenvptr (004b5ed8)],eax
  305 0044a1a8 e845b1feff      call    CrtDemo05!ILT+4845(__wsetargv) (004352f2)
  305 0044a1ad 85c0            test    eax,eax
  305 0044a1af 7d0a            jge     CrtDemo05!__tmainCRTStartup+0x1db (0044a1bb)
...

函式,你可能會好奇為什麼 wide 版本並不是呼叫 _imp__GetCommandLineW() ,其實它被包裝在 ___crtGetCommandLineW() 裡頭。

0:000> uf 00434046
...
CrtDemo05!__crtGetCommandLineW+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\aw_com.c @ 52]:
   52 0046336f ff1530934b00    call    dword ptr [CrtDemo05!_imp__GetCommandLineW (004b9330)]
   52 00463375 85c0            test    eax,eax
   52 00463377 740c            je      CrtDemo05!__crtGetCommandLineW+0x25 (00463385)
...

並且同樣地,可以使用如同 ansi 版本的方式,去 trace :

0:000> dd 004b9330
004b9330  7c816cfb 7c80c6cf 7c801eee 7c873d83
004b9340  7c81abe4 7c80180e 7c810da6 7c80cd58

0:000> uf 7c816cfb 
kernel32!GetCommandLineW:
7c816cfb a10430887c      mov     eax,dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)]
7c816d00 c3              ret

0:000> dd 7c883004
7c883004  0002062c 00000000 7c809784 7c80979d
7c883014  7c8097d2 7c8097e7 7c8097b7 00000000

0:000> du 0002062c 
0002062c  "C:\test\CrtDemo05.exe test"

使用 Kernel Debugging

或許你會好奇為什麼 trace command 如何填入一個 process 需要動要到 kernel debug ?其實原因很簡單,因為當 user-mode debug 無法滿足時,以這次的參數傳遞來看,當我們利用 windbg 載入一個 program 時,program 需要執行到某個狀態後, user-mode debugger 才能介入,此時 arguments 已經設置完成。

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: C:\test\CrtDemo05.exe
Symbol search path is: srv*C:\sym_cache*http://msdl.microsoft.com/download/symbols;C:\test
Executable search path is: 
ModLoad: 00400000 004bb000   CrtDemo05.exe
ModLoad: 7c920000 7c9b5000   ntdll.dll
ModLoad: 7c800000 7c91d000   C:\WINDOWS\system32\kernel32.dll
(7d8.568): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffd8000 ecx=00000001 edx=00000002 esi=00251f48 edi=00251eb4
eip=7c921230 esp=0012fb20 ebp=0012fc94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
7c921230 cc              int     3

0:000> kvn
 # ChildEBP RetAddr  Args to Child              
00 0012fb1c 7c95edc0 7ffdf000 7ffd8000 00000000 ntdll!DbgBreakPoint (FPO: [0,0,0])
01 0012fc94 7c941639 0012fd30 7c920000 0012fce0 ntdll!LdrpInitializeProcess+0xffa (FPO: [5,89,4])
02 0012fd1c 7c92eac7 0012fd30 7c920000 00000000 ntdll!_LdrpInitialize+0x183 (FPO: [Non-Fpo])
03 00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7

0:000> da 00151ee0 
00151ee0  "C:\test\CrtDemo05.exe test"

注意到沒?當 CrtDemo05 整支程式還在被 OS 的 loader 所初始化時,在 __tmainCRTStartup 還未執行到之前,arguments 已經填入了。因此這個例子需要透過 kernel debugging 來進行更早期的追蹤。 kernel debugging 的方式有許多種,使用 VMWare 算是便利的方法之一,可以參考 Windows Debugging – Kernel Debugging with WinDbg and VMWare 設定環境。

Catch Me If You Can

為了捕捉 kernel32!BaseUnicodeCommanLine 以及 kernel32!BaseAnsiCommandLine 是如何被填寫的?我們可以透過 WinDbg 的 break on access ,不過很快就會遇到第一個問題:

kd> x kernel32!BaseUnicodeCommandLine
7c885730 kernel32!BaseUnicodeCommandLine = <no type information>
7c883000 kernel32!BaseUnicodeCommandLine = <no type information>

遇到兩個同名的 symbols ,理論上這是件會造成 symbol resolve 上 ambiguous 的事。雖然暫時無解,但幸運的是,根據之前追蹤結果:

7c816cfb a10430887c mov eax,dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)]

我們可以直接選擇 7c885730 :

kd> ba r4 7c883004
kd> g
Breakpoint 0 hit
nt!MiCopyOnWrite+0x148:
0008:805222bc f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
kd> k
ChildEBP RetAddr  
b22bad00 8051d019 nt!MiCopyOnWrite+0x148
b22bad4c 805406ec nt!MmAccessFault+0x9f9
b22bad4c 7c93a100 nt!KiTrap0E+0xcc
0012f938 7c93d8a0 ntdll!__security_init_cookie_ex+0x31
0012f944 7c93d83a ntdll!LdrpInitSecurityCookie+0x2f
0012fa40 7c939b78 ntdll!LdrpRunInitializeRoutines+0x124
0012faf0 7c939ba0 ntdll!LdrpGetProcedureAddress+0x1c6
0012fb0c 7c942334 ntdll!LdrGetProcedureAddress+0x18
0012fc94 7c941639 ntdll!LdrpInitializeProcess+0xd7a
0012fd1c 7c92eac7 ntdll!_LdrpInitialize+0x183
00000000 00000000 ntdll!KiUserApcDispatcher+0x7

這一次還不是我們想要的 stack:

kd> g
Breakpoint 0 hit
kernel32!_BaseDllInitialize+0x20f:
7c817ea8 ff157c10807c    call    dword ptr [kernel32!_imp__RtlUnicodeStringToAnsiString (7c80107c)]
kd> bl
 0 e 7c883004 r 4 0001 (0001) kernel32!BaseUnicodeCommandLine+0x4
kd> k
ChildEBP RetAddr  
0012f918 7c9211a7 kernel32!_BaseDllInitialize+0x20f
0012f938 7c93cbab ntdll!LdrpCallInitRoutine+0x14
0012fa40 7c939b78 ntdll!LdrpRunInitializeRoutines+0x344
0012faf0 7c939ba0 ntdll!LdrpGetProcedureAddress+0x1c6
0012fb0c 7c942334 ntdll!LdrGetProcedureAddress+0x18
0012fc94 7c941639 ntdll!LdrpInitializeProcess+0xd7a
0012fd1c 7c92eac7 ntdll!_LdrpInitialize+0x183
00000000 00000000 ntdll!KiUserApcDispatcher+0x7

這次 stack 看起來成功率比較高。 disassembly 一段 breakpoint 前的程式碼,使用 WinDbg 的 disassembly 視窗或是指令都可以做到:

kd> ub 7c817ea8 L10
kernel32!_BaseDllInitialize+0x1cc:
...
7c817e7f 64a118000000    mov     eax,dword ptr fs:[00000018h]
7c817e85 8b4030          mov     eax,dword ptr [eax+30h]
7c817e88 8b4010          mov     eax,dword ptr [eax+10h]
7c817e8b 8b4840          mov     ecx,dword ptr [eax+40h]
7c817e8e 6a01            push    1
7c817e90 890d0030887c    mov     dword ptr [kernel32!BaseUnicodeCommandLine (7c883000)],ecx
7c817e96 8b4044          mov     eax,dword ptr [eax+44h]
7c817e99 680030887c      push    offset kernel32!BaseUnicodeCommandLine (7c883000)
7c817e9e 68f035887c      push    offset kernel32!BaseAnsiCommandLine (7c8835f0)
7c817ea3 a30430887c      mov     dword ptr [kernel32!BaseUnicodeCommandLine+0x4 (7c883004)],eax

解讀 fs:[00000018h]

可以發現,kernel32!BaseUnicodeCommandLine 是由 ecx 決定,而 ecx 則是由 fs:[00000018h] 所決定,這部份的解讀,需要一些的隱藏知識:在 x86 上Windows user mode 將每個 thread 的 TEB (Thread Environment Block),存放在 fs:[0];而在 kernel mode 中, fs:[0] 則是存放 KPCR (Process Control Region) 。

kd> dt _TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   ...

整個 ntdll!_TEB 有點龐大,不過根據 ub 的結果,fs:[00000018h] 落在第一個欄位 ── _NT_TIB (Thread Information Block)裡頭,再進行一次 dt :

kd> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

那為何不直接使用 fs:[0] 而要選擇 fs:[00000018h] ?因為 fs:[0] 並非 process 的 virtual address :

kd> dd fs:0
003b:00000000  0012fa30 00130000 0012f000 00000000
003b:00000010  00001e00 00000000 7ffdd000 00000000
003b:00000020  00000778 00000128 00000000 00000000

因此使用上,往往透過 fs:[00000018h] 來指向正確 TEB 或說 TIB 的位址。

重新解讀 ub

7c817e7f 64a118000000    mov     eax,dword ptr fs:[00000018h]
7c817e85 8b4030          mov     eax,dword ptr [eax+30h]
7c817e88 8b4010          mov     eax,dword ptr [eax+10h]
7c817e8b 8b4840          mov     ecx,dword ptr [eax+40h]

上述組語在知道 fs:[0] 的意義後,便可尋著指令進行 dt 的解碼,並寫出對應的虛擬碼:

_TEB            teb = fs:[00000018h];
UNICODE_STRING  kernel32!BaseUnicodeCommandLine = 
    teb.ProcessEnvironmentBlock->ProcessParameters.CommandLine;

到了這個步驟,我們已經知道即使是 kernel32!_BaseDllInitialize() 這般低階的初始化,也僅僅只是將 PEB 中的某個變數值讀出,那麼 PEB 中的值又是給負責填寫呢?還記得 Win32 API 中的 CreateProcess() 嗎?它或許是有嫌疑的一份子。

CreateProcess()

kd> dt _RTL_USER_PROCESS_PARAMETERS
ntdll!_RTL_USER_PROCESS_PARAMETERS
   +0x000 MaximumLength    : Uint4B
   +0x004 Length           : Uint4B
   +0x008 Flags            : Uint4B
   +0x00c DebugFlags       : Uint4B
   +0x010 ConsoleHandle    : Ptr32 Void
   +0x014 ConsoleFlags     : Uint4B
   +0x018 StandardInput    : Ptr32 Void
   +0x01c StandardOutput   : Ptr32 Void
   +0x020 StandardError    : Ptr32 Void
   +0x024 CurrentDirectory : _CURDIR
   +0x030 DllPath          : _UNICODE_STRING
   +0x038 ImagePathName    : _UNICODE_STRING
   +0x040 CommandLine      : _UNICODE_STRING
   +0x048 Environment      : Ptr32 Void
   +0x04c StartingX        : Uint4B
   +0x050 StartingY        : Uint4B
   +0x054 CountX           : Uint4B
   +0x058 CountY           : Uint4B
   +0x05c CountCharsX      : Uint4B
   +0x060 CountCharsY      : Uint4B
   +0x064 FillAttribute    : Uint4B
   +0x068 WindowFlags      : Uint4B
   +0x06c ShowWindowFlags  : Uint4B
   +0x070 WindowTitle      : _UNICODE_STRING
   +0x078 DesktopInfo      : _UNICODE_STRING
   +0x080 ShellInfo        : _UNICODE_STRING
   +0x088 RuntimeData      : _UNICODE_STRING
   +0x090 CurrentDirectores : [32] _RTL_DRIVE_LETTER_CURDIR

目前我們已經知道,一個程式的 command line 儲存在 _RTL_USER_PROCESS_PARAMETERS 這個 strcut 裡頭,所以要做的是,找到何時會去填寫,方法有兩種:

  1. Trace CreateProcess
  2. 尋找有關 ProcessParameter 的 function 。

我們使用方法 2. 來加速:

可以使用 WinDbg 的 x 指令:

kd> x *!*processparameter*
7c819f9e kernel32!BasePushProcessParameters = <no type information>
7c801488 kernel32!_imp__RtlCreateProcessParameters = <no type information>
7c801484 kernel32!_imp__RtlDestroyProcessParameters = <no type information>
7c9432ec ntdll!RtlDestroyProcessParameters = <no type information>
7c950ea3 ntdll!RtlCheckProcessParameters = <no type information>
7c9433c1 ntdll!RtlCreateProcessParameters = <no type information>

這幾個 functions 看起來都有機會,其中以 BasePushProcessParameters() 的名稱看來最有可能是 creater 建立 createe process parameter 的函式,為了驗證這想法,將 WinDbg 目前的 context 切換到 cmd.exe 下,並設定 breakpoint 來驗證:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
...
PROCESS 8200d7e8  SessionId: 0  Cid: 0194    Peb: 7ffdf000  ParentCid: 0608
    DirBase: 08940240  ObjectTable: e1533828  HandleCount:  33.
    Image: cmd.exe

kd> .process /r /P /p 8200d7e8

kd> bp 7c819f9e

kd> g
Breakpoint 1 hit
kernel32!BasePushProcessParameters:
7c819f9e 68c0020000      push    2C0h

kd> k
ChildEBP RetAddr  
0013f028 7c8199bc kernel32!BasePushProcessParameters
0013fa88 7c80235e kernel32!CreateProcessInternalW+0x184e
0013fac0 4ad031dd kernel32!CreateProcessW+0x2c
0013fc20 4ad02db0 cmd!ExecPgm+0x22b
0013fc54 4ad02e0e cmd!ECWork+0x84
0013fc6c 4ad05f9f cmd!ExtCom+0x40
0013fe9c 4ad013eb cmd!FindFixAndRun+0xcf
0013fee0 4ad0bbba cmd!Dispatch+0x137
0013ff44 4ad05164 cmd!main+0x216
0013ffc0 7c816d4f cmd!mainCRTStartup+0x125
0013fff0 00000000 kernel32!BaseProcessStart+0x23

stack 目前看來是支持我們的猜測, cmd.exe 透過 CreateProcess()  來建立 CrtDemo05.exe ,並且呼叫 BasePushProcessParameters() 來初始化 process parameter。在這邊,我們也可以切換到 CrtDemo05.exe 下,確定一下它目前的建立狀態,以瞭解它仍是否仍在建立中而非建立完成:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
...

PROCESS 8200d7e8  SessionId: 0  Cid: 0194    Peb: 7ffdf000  ParentCid: 0608
    DirBase: 08940240  ObjectTable: e1533828  HandleCount:  35.
    Image: cmd.exe

PROCESS 82476d00  SessionId: 0  Cid: 00c4    Peb: 7ffd4000  ParentCid: 0194
    DirBase: 089402c0  ObjectTable: e1d3bf68  HandleCount:  36.
    Image: conime.exe

PROCESS 8244e980  SessionId: 0  Cid: 05b4    Peb: 7ffdc000  ParentCid: 0400
    DirBase: 08940300  ObjectTable: e16e0700  HandleCount: 123.
    Image: wuauclt.exe

PROCESS 8244a460  SessionId: 0  Cid: 0224    Peb: 7ffd5000  ParentCid: 0194
    DirBase: 08940380  ObjectTable: e17871a0  HandleCount:   1.
    Image: CrtDemo0

看到 Image name 被截斷,有點訝異,不過可能是因為 kernel 在填寫時被我們中斷了。現在來看看 PEB 裡頭的 command line 是否已經準備好了:

kd> !peb 7ffd5000
PEB at 7ffd5000
error 1 InitTypeRead( nt!_PEB at 7ffd5000)...

kd> .process /r /P /p 8244a460 
Implicit process is now 8244a460
.cache forcedecodeuser done
Loading User Symbols
PEB is paged out (Peb.Ldr = 7ffd500c).  Type ".hh dbgerr001" for details
kd> dt _PEB 7ffd5000
nt!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0 ''
   +0x003 SpareBool        : 0 ''
   +0x004 Mutant           : 0xffffffff Void
   +0x008 ImageBaseAddress : 0x00400000 Void
   +0x00c Ldr              : (null) 
   +0x010 ProcessParameters : (null) 
   +0x014 SubSystemData    : (null) 
   +0x018 ProcessHeap      : (null) 
   +0x01c FastPebLock      : (null) 
   ...

試了兩種方法,都無法正確讀取 PEB ,這正和我們的預期:表示 CrtDemo05.exe 正在被建立中,其 PEB 似乎也在混沌之中。緊接著回到 cmd.exe 的 stack 上,接下來的事就需要點耐心,由於目前只知道 BasePushProcessParameters() 跟 _RTL_USER_PROCESS_PARAMETERS 有關,但是找不到可以設立 breakpoint 的地方,根據 assembly 一步步追蹤是個方法,不過直接閱讀 assembly 來找尋跟 ProcessParameters 相關的指令或許會更快,一個很快速的想法是使用關鍵字來縮小範圍,這裡若是使用 uf 來 diassembly ,可能會遇上一些麻煩,因為 CreateProcessInternalW() 被 OMAP 最佳化過,無法依照 mapping 到記憶體上的 layout 顯示:

kd> uf CreateProcessInternalW
Flow analysis was incomplete, some code may be missing
kernel32!CreateProcessInternalW+0x308:
...
...

當遇到這種狀況,我們得放棄 diassembly 整個 function ,而是直接指明記憶體位址:

kd> x kernel32!CreateProcessInternalW
7c8191eb kernel32!CreateProcessInternalW = 

kd> u 7c8191eb L600
kernel32!CreateProcessInternalW:
...
; ProcessParameter
; ebp-234h: _RTL_USER_PROCESS_PARAMETERS*
7c81a11d 8d85ccfdffff    lea     eax,[ebp-234h]
7c81a123 50              push    eax                        ; pProcessParameters
7c81a124 ff158814807c    call    dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)]
7c81a12a 33d2            xor     edx,edx                    ; edx = 0
7c81a12c 3bc2            cmp     eax,edx

...
...
7c81a538 ff158414807c    call    dword ptr [kernel32!_imp__RtlDestroyProcessParameters (7c801484)]

幸運的找到:

7c9432ec ntdll!RtlDestroyProcessParameters
7c9433c1 ntdll!RtlCreateProcessParameters

利用兩個 functions ,我們可以縮小可疑範圍,7c81a11d ~ 7c81a538 給了我們一個暗示:ebp-234h 是個 local variable ,並且傳遞至 RtlCreateProcessParameters() ,當結束後進行是否為 NULL 的比較,那麼 ebp-234h 基本上就很有機會就是一個 pointer point to _RTL_USER_PROCESS_PARAMETERS。另外在向上回溯,可以找找看是否還有其他參數被傳遞至 RtlCreateProcessParameters() :

7c81a0d7 ffd6            call    esi
7c81a0d9 8d85b4fdffff    lea     eax,[ebp-24Ch]
7c81a0df 50              push    eax                    ; parameter10
7c81a0e0 8d859cfdffff    lea     eax,[ebp-264h]
7c81a0e6 50              push    eax                    ; parameter9
7c81a0e7 8d85a4fdffff    lea     eax,[ebp-25Ch]
7c81a0ed 50              push    eax                    ; parameter8
7c81a0ee 8d8594fdffff    lea     eax,[ebp-26Ch]
7c81a0f4 50              push    eaxe                   ; parameter7
7c81a0f5 ffb570fdffff    push    dword ptr [ebp-290h]   ; parameter6
7c81a0fb 8d8584fdffff    lea     eax,[ebp-27Ch]
7c81a101 50              push    eax                    ; parameter5
7c81a102 f7db            neg     ebx
7c81a104 1bdb            sbb     ebx,ebx
7c81a106 8d8578fdffff    lea     eax,[ebp-288h]
7c81a10c 23d8            and     ebx,eax
7c81a10e 53              push    ebx                    ; parameter4
7c81a10f 8d85acfdffff    lea     eax,[ebp-254h]
7c81a115 50              push    eax                    ; parameter3
7c81a116 8d858cfdffff    lea     eax,[ebp-274h]
7c81a11c 50              push    eax                    ; parameter2
7c81a11d 8d85ccfdffff    lea     eax,[ebp-234h]
7c81a123 50              push    eax                    ; parameter1
7c81a124 ff158814807c    call    dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)]

看到了 10 個參數被傳遞至 RtlCreateProcessParameters() ,我們可以選擇 diassembly 它看是否有與 command line 相關的操作,或是繼續搜尋其他 7c81a11d ~ 7c81a538 內 ebp-234h 的操作,這裡有一個或許可行的快速篩選法,因為 CommandLine 位於 _RTL_USER_PROCESS_PARAMETERS offset 40 bytes 的地方:

kd> dt _RTL_USER_PROCESS_PARAMETERS
ntdll!_RTL_USER_PROCESS_PARAMETERS
   ...
   +0x040 CommandLine      : _UNICODE_STRING
   ...

我們便找尋 40h 的關鍵字,而搜尋結果的確有幾筆與 40h 有關的操作,但幸運地,都不是和 CommandLine 有關的。於是乎,把焦點放回 RtlCreateProcessParameters():

kd> x ntdll!RtlCreateProcessParameters
7c9433c1 ntdll!RtlCreateProcessParameters

kd> u 7c9433c1 L200
ntdll!RtlCreateProcessParameters:
...
7c9435e1 6a04            push    4                          ; Protect
7c9435e3 6800100000      push    1000h                      ; AllocationType
7c9435e8 8d45cc          lea     eax,[ebp-34h]
7c9435eb 50              push    eax                        ; RegionSize
7c9435ec 53              push    ebx                        ; ZeroBits
7c9435ed 8d45e4          lea     eax,[ebp-1Ch]
7c9435f0 50              push    eax                        ; BaseAddress
7c9435f1 6aff            push    0FFFFFFFFh                 ; ProcessHandle
7c9435f3 e8e69efeff      call    ntdll!ZwAllocateVirtualMemory (7c92d4de)
...

由於 ZwAllocateVirtualMemory() 是公開的 API ,所以很快地可以對參數進行 mapping ,馬上就發現 local vaiable ebp-1ch 存放的就是 allocate 的結果,並且在稍後就有一個 +40h 的操作:

kd> u 7c9433c1 L200
ntdll!RtlCreateProcessParameters:
...
7c94369a 40              inc     eax
7c94369b 40              inc     eax
7c94369c 50              push    eax
7c94369d 8b45e4          mov     eax,dword ptr [ebp-1Ch]    ; pBase
7c9436a0 83c040          add     eax,40h                    ; pBase + 40 = CommandLine
7c9436a3 57              push    edi
7c9436a4 50              push    eax
7c9436a5 8d45d8          lea     eax,[ebp-28h]
7c9436a8 50              push    eax
7c9436a9 e8ab000000      call    ntdll!RtlpCopyProcString (7c943759)
...

這和 CommandLine 的操作看似吻合,但我們仍需要找到更直接的證據,首先可以使用動態的證據,也就是設定 breakpoint 去證明,我們將 breakpoint 先設定在 add eax,40h:

kd> bp 7c9436a0

kd> g
Breakpoint 2 hit
ntdll!RtlCreateProcessParameters+0x2f5:
7c9436a0 83c040          add     eax,40h

kd> dd ebp-1c
0013ed00  00380000 0013ecd4 0013edf8 0013f018
0013ed10  7c92ee18 7c943748 00000000 0013f028

kd> dd 00380040 
00380040  00000000 00000000 00010000 00000000
00380050  00000000 00000000 00000000 00000000

接著,執行數個指令後,等到 RtlpCopyProcString() 執行完成後:

kd> dd 00380040 
00380040  00260024 003805c0 00010000 00000000
00380050  00000000 00000000 00000000 00000000

kd> du 003805c0
003805c0  "CrtDemo05.exe test"

再者,我們可以來個靜態分析:

kd> u 7c9433c1 L200
ntdll!RtlCreateProcessParameters:
...
7c94370b 8b45e4          mov     eax,dword ptr [ebp-1Ch]
7c94370e 6689988a000000  mov     word ptr [eax+8Ah],bx
7c943715 ff75e4          push    dword ptr [ebp-1Ch]
7c943718 e8f6fbffff      call    ntdll!RtlDeNormalizeProcessParams (7c943313)
7c94371d 8b4d08          mov     ecx,dword ptr [ebp+8]
7c943720 8901            mov     dword ptr [ecx],eax
...

很快地就有一個可疑的證物: ebp+8 也就是 RtlCreateProcessParameters() 的第一個參數,它被 eax 給賦值。雖然 eax 在第一行被指定為 ebp-1Ch ,看似符合我們的假設,但因為中間有個 RtlDeNormalizeProcessParams() 的呼叫,不能掉以輕心,必須進去瞧瞧:

kd> uf RtlDeNormalizeProcessParams
Flow analysis was incomplete, some code may be missing
ntdll!RtlDeNormalizeProcessParams:
7c943313 8bff            mov     edi,edi
7c943315 55              push    ebp
7c943316 8bec            mov     ebp,esp
7c943318 8b4508          mov     eax,dword ptr [ebp+8]
7c94331b 85c0            test    eax,eax
7c94331d 7478            je      ntdll!RtlDeNormalizeProcessParams+0x84 (7c943397)
...

幸運的, RtlDeNormalizeProcessParams() 沒有可觀的實作,也僅在 function 開頭將 eax 給予 ebp+8 所代表的位址;而 ebp+8 也正就是 ebp-1Ch,其餘的對應操作都是 read 。確定了 RtlDeNormalizeProcessParams() 不會更改 ebp-1Ch 後,就可以放心回到 RtlCreateProcessParameters():

kd> u 7c9433c1 L200
ntdll!RtlCreateProcessParameters:
...
7c94345b 8b7d18          mov     edi,dword ptr [ebp+18h]
...
7c943685 e8cf000000      call    ntdll!RtlpCopyProcString (7c943759)
7c94368a 668b07          mov     ax,word ptr [edi]
7c94368d 663b4702        cmp     ax,word ptr [edi+2]
7c943691 0f84f79a0200    je      ntdll!RtlCreateProcessParameters+0x2e9 (7c96d18e)
7c943697 0fb7c0          movzx   eax,ax
7c94369a 40              inc     eax
7c94369b 40              inc     eax
7c94369c 50              push    eax
7c94369d 8b45e4          mov     eax,dword ptr [ebp-1Ch]    ; pBase
7c9436a0 83c040          add     eax,40h                    ; pBase + 40 = CommandLine
7c9436a3 57              push    edi
7c9436a4 50              push    eax
7c9436a5 8d45d8          lea     eax,[ebp-28h]
7c9436a8 50              push    eax
7c9436a9 e8ab000000      call    ntdll!RtlpCopyProcString (7c943759)

可以發現,可疑的參數有:

  1. 7c94369a                 inc eax
    7c94369b                 inc eax
    7c94369c                  push eax
  2. 7c94345b                 mov edi,dword ptr [ebp+18h]
    7c9436a3                 push edi
  3. 7c9436a5                 lea eax,[ebp-28h]
    7c9436a8                 push eax

為了找出實際的賦值給 ebp-1Ch+40h 的對象,這邊可以使用 breakpoint 去檢驗,分別是:

kd> bp 7c94369c
kd> g
Breakpoint 5 hit
ntdll!RtlCreateProcessParameters+0x2f1:
7c94369c 50              push    eax
kd> r
eax=00000026 ebx=00000000 ecx=00380034 edx=00000000 esi=00000208 edi=0013edac
eip=7c94369c esp=0013ecd4 ebp=0013ed1c iopl=0         nv up ei pl nz na po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000203

kd> dpu 0013edac+4 L1
0013edb0  0015adc8 "CrtDemo05.exe test"

kd> bp 7c9436a8
kd> g
Breakpoint 6 hit
kd> dd ebp-28h
0013ecf4  003805c0 00000000 00000634 00380000
0013ed04  0013ecd4 0013edf8 0013f018 7c92ee18

kd> dpu 003805c0
003805c0  00000000

透過 breakpoint 的實驗,就可以知道 edi 才是賦值給 ebp-1Ch+40h 的來源,而且還是 RtlCreateProcessParameters() 的第五個參數。所以可以反推 edi 是怎麼來的,並一路反推,便可得到 command line 最初的來源。

kd> uf kernel32!CreateProcessW
kernel32!CreateProcessW:
7c802332 8bff            mov     edi,edi
7c802334 55              push    ebp
7c802335 8bec            mov     ebp,esp
7c802337 6a00            push    0                      ; parameter12
7c802339 ff752c          push    dword ptr [ebp+2Ch]    ; lpProcessInformation
7c80233c ff7528          push    dword ptr [ebp+28h]    ; lpStartupInfo
7c80233f ff7524          push    dword ptr [ebp+24h]    ; lpCurrentDirectory
7c802342 ff7520          push    dword ptr [ebp+20h]    ; lpEnvironment
7c802345 ff751c          push    dword ptr [ebp+1Ch]    ; dwCreationFlags
7c802348 ff7518          push    dword ptr [ebp+18h]    ; bInheritHandles
7c80234b ff7514          push    dword ptr [ebp+14h]    ; lpThreadAttributes
7c80234e ff7510          push    dword ptr [ebp+10h]    ; lpProcessAttributes
7c802351 ff750c          push    dword ptr [ebp+0Ch]    ; lpCommandLine
7c802354 ff7508          push    dword ptr [ebp+8]      ; lpApplicationName
7c802357 6a00            push    0                      ; parameter1
7c802359 e88d6e0100      call    kernel32!CreateProcessInternalW (7c8191eb)
7c80235e 5d              pop     ebp
7c80235f c22800          ret     28h

kd> x kernel32!CreateProcessInternalW
7c8191eb kernel32!CreateProcessInternalW = <no type information>

kd> u 7c8191eb L600
kernel32!CreateProcessInternalW:
...
7c819214 8b4510          mov     eax,dword ptr [ebp+10h]    ; lpCommandLine
7c819217 8985e0f8ffff    mov     dword ptr [ebp-720h],eax
...
7c819958 8b8de0f8ffff    mov     ecx,dword ptr [ebp-720h]   ; lpCommandLine
...
7c819964 ffb56cf9ffff    push    dword ptr [ebp-694h]    ; parameter13
7c81996a ffb500f7ffff    push    dword ptr [ebp-900h]    ; parameter12
7c819970 8a85b7f8ffff    mov     al,byte ptr [ebp-749h]
7c819976 f6d8            neg     al
7c819978 1bc0            sbb     eax,eax
7c81997a 83e002          and     eax,2
7c81997d 50              push    eax                    ; parameter11
7c81997e ff751c          push    dword ptr [ebp+1Ch]    ; parameter10
7c819981 8b85f4f6ffff    mov     eax,dword ptr [ebp-90Ch]
7c819987 0b4520          or      eax,dword ptr [ebp+20h]
7c81998a 50              push    eax                     ; parameter9
7c81998b 8d8560f7ffff    lea     eax,[ebp-8A0h]
7c819991 50              push    eax                     ; parameter8
7c819992 ffb5b0f8ffff    push    dword ptr [ebp-750h]    ; parameter7
7c819998 51              push    ecx                     ; parameter6
7c819999 ffb550f7ffff    push    dword ptr [ebp-8B0h]    ; parameter5
7c81999f ffb5e4f8ffff    push    dword ptr [ebp-71Ch]    ; parameter4
7c8199a5 ffb5b8f7ffff    push    dword ptr [ebp-848h]    ; parameter3
7c8199ab ffb594f9ffff    push    dword ptr [ebp-66Ch]    ; parameter2
7c8199b1 ffb534f7ffff    push    dword ptr [ebp-8CCh]    ; parameter1
7c8199b7 e8e2050000      call    kernel32!BasePushProcessParameters (7c819f9e)

kernel32!BasePushProcessParameters:
...
7c819fd3 8b4d1c          mov     ecx,dword ptr [ebp+1Ch]    ;
7c819fd6 898d5cfdffff    mov     dword ptr [ebp-2A4h],ecx   ; srcCmdLine
...
7c81a045 8d85d0fdffff    lea     eax,[ebp-230h]
7c81a04b 50              push    eax
7c81a04c 8d858cfdffff    lea     eax,[ebp-274h]
7c81a052 50              push    eax
7c81a053 ffd6            call    esi
7c81a055 ffb55cfdffff    push    dword ptr [ebp-2A4h]   ; srcCmdLine
7c81a05b 8d8584fdffff    lea     eax,[ebp-27Ch]
7c81a061 50              push    eax                    ; commandLine
7c81a062 ffd6            call    esi                    ; RtlInitUnicodeString
...
7c81a0cf 50              push    eax
7c81a0d0 8d8594fdffff    lea     eax,[ebp-26Ch]
7c81a0d6 50              push    eax
7c81a0d7 ffd6            call    esi
7c81a0d9 8d85b4fdffff    lea     eax,[ebp-24Ch]
7c81a0df 50              push    eax                        ; runtimeData
7c81a0e0 8d859cfdffff    lea     eax,[ebp-264h]
7c81a0e6 50              push    eax                        ; shellInfo
7c81a0e7 8d85a4fdffff    lea     eax,[ebp-25Ch]
7c81a0ed 50              push    eax                        ; desktopInfo
7c81a0ee 8d8594fdffff    lea     eax,[ebp-26Ch]
7c81a0f4 50              push    eax                        ; windowTitle
7c81a0f5 ffb570fdffff    push    dword ptr [ebp-290h]       ; environment
7c81a0fb 8d8584fdffff    lea     eax,[ebp-27Ch]
7c81a101 50              push    eax                        ; commandLine, parameter5
7c81a102 f7db            neg     ebx
7c81a104 1bdb            sbb     ebx,ebx
7c81a106 8d8578fdffff    lea     eax,[ebp-288h]
7c81a10c 23d8            and     ebx,eax
7c81a10e 53              push    ebx                        ; currentDir
7c81a10f 8d85acfdffff    lea     eax,[ebp-254h]
7c81a115 50              push    eax                        ; dllPath
7c81a116 8d858cfdffff    lea     eax,[ebp-274h]
7c81a11c 50              push    eax                        ; imagePath
; ProcessParameter
; ebp-234h: _RTL_USER_PROCESS_PARAMETERS*
7c81a11d 8d85ccfdffff    lea     eax,[ebp-234h]
7c81a123 50              push    eax                        ; pProcessParameters
7c81a124 ff158814807c    call    dword ptr [kernel32!_imp__RtlCreateProcessParameters (7c801488)]

最後,我們還沒來得及關心 creater 是如何的把 createe 的 PEB 建立完成,其實就在 RtlCreateProcessParameters() 稍後,透過 NtWriteVirtualMemory() 完成。

7c81a1eb 8b35f413807c    mov     esi,dword ptr [kernel32!_imp__NtWriteVirtualMemory (7c8013f4)]
...
7c81a38b 6a04            push    4                          ; Protect
7c81a38d bb00100000      mov     ebx,1000h
7c81a392 53              push    ebx                        ; AllocationtType
7c81a393 8d85c8fdffff    lea     eax,[ebp-238h]
7c81a399 50              push    eax                        ; RegionSize
7c81a39a 52              push    edx                        ; ZeroBits
7c81a39b 8d85c4fdffff    lea     eax,[ebp-23Ch]
7c81a3a1 50              push    eax                        ; BaseAddress
7c81a3a2 ffb580fdffff    push    dword ptr [ebp-280h]       ; hNewProcessHandle
7c81a3a8 8b3d9011807c    mov     edi,dword ptr [kernel32!_imp__NtAllocateVirtualMemory (7c801190)]
7c81a3ae ffd7            call    edi
7c81a3b0 898574fdffff    mov     dword ptr [ebp-28Ch],eax
7c81a3b6 8b85c8fdffff    mov     eax,dword ptr [ebp-238h]
7c81a3bc 898558fdffff    mov     dword ptr [ebp-2A8h],eax
7c81a3c2 83bd74fdffff00  cmp     dword ptr [ebp-28Ch],0
7c81a3c9 0f8c04950200    jl      kernel32!BasePushProcessParameters+0x504 (7c8438d3)
7c81a3cf 8b8dccfdffff    mov     ecx,dword ptr [ebp-234h]
7c81a3d5 8901            mov     dword ptr [ecx],eax
7c81a3d7 f6452b10        test    byte ptr [ebp+2Bh],10h
7c81a3db 0f85fa940200    jne     kernel32!BasePushProcessParameters+0x51d (7c8438db)
7c81a3e1 f6452b20        test    byte ptr [ebp+2Bh],20h
7c81a3e5 0f85ff940200    jne     kernel32!BasePushProcessParameters+0x52d (7c8438ea)
7c81a3eb f6452b40        test    byte ptr [ebp+2Bh],40h
7c81a3ef 0f8504950200    jne     kernel32!BasePushProcessParameters+0x53d (7c8438f9)
7c81a3f5 6a00            push    0                          ; nBytesWritten
7c81a3f7 8b85ccfdffff    mov     eax,dword ptr [ebp-234h]
7c81a3fd ff7004          push    dword ptr [eax+4]          ; nBytesToWrite    : pProcParameters->Length
7c81a400 50              push    eax                        ; Buffer           : pProcParameters
7c81a401 ffb5c4fdffff    push    dword ptr [ebp-23Ch]       ; BaseAddress      :
7c81a407 ffb580fdffff    push    dword ptr [ebp-280h]       ; hNewProcessHandle:
                                 ; NtWriteVirtualMemory( *(ebp-280h), *(ebp-23Ch), eax, ???, 0 )
7c81a40d ffd6            call    esi                    ; _imp__NtWriteVirtualMemory

網路上可以找到人家 reverse 過的 RtlCreateProcessParameters() prototype 可以加速 trace 的速度:

NTSTATUS RtlCreateProcessParameters( 
    PRTL_USER_PROCESS_PARAMETERS *ProcessParameters,
    PUNICODE_STRING     ImagePathName,
    PUNICODE_STRING     DllPath,
    PUNICODE_STRING     CurrentDirectory,
    PUNICODE_STRING     CommandLine,
    PWSTR               Environment, // Not sured
    PUNICODE_STRING     WindowTitle,
    PUNICODE_STRING     DesktopInfo,
    PUNICODE_STRING     ShellInfo,
    PUNICODE_STRING     RuntimeData );

不過如果想要硬派的自己來也是可以的,可以試試看這篇文章的作法 : )

Summary

image

MiniFilter InstanceSetupCallback is not called?

一般來說,MiniFilter 的 InstanceSetupCallback 會在 filter manager 把 minifilter attache 到 volume 後呼叫。如果沒有的話,可以檢查一下 minifilter 的 INF 是否把 instance fla...