OutputDebugString()

來源:互聯網
上載者:User

標籤:des   style   http   使用   strong   檔案   

堅定的 Win32 開發人員可能對 OutputDebugString() API 函數比較熟悉,它能夠使你的程式和調試器進行交談。它要比建立記錄檔easy,並且全部“真正的”調試器都能使用它。應用程式和調試器交談的機制相當簡單,而本文將揭示整件事情是怎樣工作的。

本文首先是由下面事件促使的,我們觀察到 OutputDebugString() 在管理員和非管理使用者試圖一起工作或遊戲時並不總是能可靠地工作(至少在 Win2000 上)。我們懷疑是一些相關的核心對象的許可權問題,此間涉略了相當多不得不寫下來的資訊。

請注意,雖然我們使用了“調試器”這一術語,但不是從調試 API 的意義上來使用的:並沒有“單步運行”、“斷點”或者“附著到進程”等能夠在 MS Visual C 或者一些真正的互動開發環境中找到的東西。從某種意義上來說,不論什麼實現了協議的程式都是“調試器”。可能是一個很小的命令列工具,或者像來自於 SysInternals 那幫聰明的傢伙們的 DebugView 那樣的進階貨。

內容檔案夾
  • 應用程式使用方法
  • 協議
  • 許可權問題
  • 實現細節
  • 胡思亂想
  • Unixwiz.net 工具:dbmutex

應用程式使用方法

<windows.h> 檔案聲明了 OutputDebugString() 函數的兩個版本號碼 - 一個用於 ASCII,一個用於 Unicode - 不像絕大多數 Win32 API 一樣,原始版本號碼是 ASCII。而大多數的 Win32 API 的原始版本號碼是 Unicode。

使用一個 NULL 結尾的字串緩衝區簡單調用 OutputDebugString() 將導致資訊出如今調試器中,假設有調試器的話。構建一條資訊並發送之的通經常使使用方法是:

sprintf(msgbuf, "Cannot open file %s [err=%ld]/n", fname, GetLastError());OutputDebugString(msgbuf);

只是在實際環境中我們中的不少人會建立一個前端函數,以同意我們使用 printf 風格的格式化。以下的 odprintf() 函數格式化字串,確保結尾有一個合適的斷行符號換行(刪除原來的行結尾),而且發送資訊到調試器。

#include <stdio.h>#include <stdarg.h>#include <ctype.h>void __cdecl odprintf(const char *format, ...){char buf[4096], *p = buf;va_list args;va_start(args, format);p += _vsnprintf(p, sizeof buf - 1, format, args);va_end(args);while ( p > buf  &&  isspace(p[-1]) )*--p = ‘/0‘;*p++ = ‘/r‘;*p++ = ‘/n‘;*p   = ‘/0‘;OutputDebugString(buf);}

於是在代碼中使用它就非常easy:

        ...odprintf("Cannot open file %s [err=%ld]", fname, GetLastError());...

我們已經這樣使用多年了。

協議

在應用程式和調試器之間傳遞資料是通過一個 4KB 大小的共用記憶體塊完畢的,並有一個相互排斥量和兩個事件對象用來保護對他的訪問。以下就是相關的四個核心對象:

對象名稱 物件類型
DBWinMutex Mutex
DBWIN_BUFFER Section (共用記憶體)
DBWIN_BUFFER_READY Event
DBWIN_DATA_READY Event

相互排斥量通常一直保留在系統中,其它三個對象僅當調試器要接收資訊才出現。其實 - 假設一個調試器發現後三個對象已經存在,它會拒絕執行。

當 DBWIN_BUFFER 出現時,會被組織成下面結構。進程 ID 顯示資訊的來源,字串資料填充這 4K 的剩餘部分。依照約定,資訊的末尾總是包含一個 NULL 位元組。

struct dbwin_buffer {                DWORD   dwProcessId;                char    data[4096-sizeof(DWORD)];                };                

OutputDebugString() 被應用調用時,它運行下面步驟。注意在任何位置的錯誤都將放棄整個事情,調試請求被覺得是什麼也不做(不會發送字串)。

  1. 開啟 DBWinMutex 而且等待,直到我們取得了獨佔訪問。
  2. 映射 DBWIN_BUFFER 段到記憶體中:假設沒有發現,則沒有調試器在執行,將忽略整個請求。
  3. 開啟 DBWIN_BUFFER_READYDBWIN_DATA_READY 事件對象。就像共用記憶體段一樣,缺少對象意味著沒有可用的調試器。
  4. 等待 DBWIN_BUFFER_READY 事件對象為有訊號狀態:表示記憶體緩衝區不再被佔用。大部分時候,這一事件對象一被檢查就處於有訊號狀態,但等待緩衝區就緒不會超過 10 秒(逾時將放棄請求)。
  5. 複製資料直到記憶體緩衝區中接近 4KB,再儲存當前進程 ID。總是放置一個 NULL 位元組到字串結尾。
  6. 通過設定 DBWIN_DATA_READY 事件對象告訴調試器緩衝區就緒。調試器從那兒取走它。
  7. 釋放相互排斥量。
  8. 關閉事件對象和段對象,但保留相互排斥量的控制代碼以備後用。

在調試器端會簡單一點。相互排斥量根本不須要,假設事件對象和/或共用記憶體對象已經存在,則假定其它調試器已經在執行。系統中隨意時刻僅僅能存在一個調試器。

  1. 建立共用記憶體段以及兩個事件對象。假設失敗,退出。
  2. 設定 DBWIN_BUFFER_READY 事件對象,由此應用程式得知緩衝區可用。
  3. 等待 DBWIN_DATA_READY 事件對象變為有訊號狀態。
  4. 從記憶體緩衝區中提取進程 ID 和 NULL 結尾的字串。
  5. 轉到步驟 2。

這使我們覺得這決不是一種低消耗的發送資訊的方法,應用程式的執行速度會受到調試器的左右。

許可權問題

我們發現 OutputDebugString() 有時不可靠已經好幾年了,並且我們十分不解為什麼微軟這麼長時間也沒把它搞好。奇怪的是,問題總是環繞著 DBWinMutex 對象出現,這就須要我們察看許可系統以找出為什麼會這麼麻煩。

相互排斥量對象會一直存活著直到使用它的最後一個程式關閉其控制代碼,故而它能在初始建立它的應用程式退出後保留相當長的時間。由於此對象被廣泛地共用,所以它必須被賦予明白的許能夠同意不論什麼人使用它。其實,“預設”許可差點兒從不適用,這一問題被計為在 NT 3.51 和 NT 4.0 中我們觀察到的第一個問題。

當時的修正方法是使用一個廣泛開放的 DACL 建立相互排斥量,以此來同意不論什麼人訪問它,可是看樣子在 Win2000 裡這些許可被加強了。表面上它看起來是正確的,就像我們在下表中看到的:

SYSTEM MUTEX_ALL_ACCESS
Administrators MUTEX_ALL_ACCESS
Everybody SYNCHRONIZE | READ_CONTROL | MUTEX_QUERY_STATE

希望發送調試資訊的應用僅僅須要等待和擷取該相互排斥量的能力,也即體現為擁有 SYNCHRONIZE 許可權。上列的許可對於全部參與的使用者都是全然正確的。

只是假設有人觀察 CreateMutex() 在對象已經存在時的行為,就會發現奇怪的事情。在這樣的情況下,Win32 的表現就好像我們進行了例如以下調用:

OpenMutex(MUTEX_ALL_ACCESS, FALSE, "DBWinMutex");                        

雖然我們確實僅僅須要 SYNCHRONIZE 訪問,但它還是假定調用者要做不論什麼事情MUTEX_ALL_ACCESS)。由於非管理員沒有這些許可權 - 僅有上列的少許 - 相互排斥量不能被開啟或者擷取,於是 OutputDebugString() 不做不論什麼事情就悄悄地返回了。

甚至將全部的軟體開發都以管理員來執行也不是一個完整的修正方法:假設存在其它的使用者(比如服務)以非管理員執行而許可配置不對,它們的調試資訊將會丟失。

我們感覺真正的修正須要微軟為 CreateMutex() 加入?一個參數 - 假設對象已經存在時用於隱含的 OpenMutex() 調用的訪問掩碼。或許某天我們會看到一個 CreateMutexEx(),但在此期間我們必須採用另外的方法。代之以,當對象已經存活於記憶體中時我們將硬性改變其上的許可配置。

這須要調用 SetKernelObjectSecurity(),下列程式片斷展示一個程式怎樣才幹開啟相互排斥量並安裝一個新的 DACL。此 DACL 即使在程式退出後也仍然保持著,僅僅要任一其它程式還維護有它(譯者註:應該是指相互排斥量)的控制代碼。

...                        // open the mutex that we‘re going to adjust                        HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "DBWinMutex");                        // create SECURITY_DESCRIPTOR with an explicit, empty DACL                        // that allows full access to everybody                        SECURITY_DESCRIPTOR     sd;                        InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);                        SetSecurityDescriptorDacl(                        &sd,            // addr of SD                        TRUE,           // TRUE=DACL present                        NULL,           // ... but it‘s empty (wide open)                        FALSE);         // DACL explicitly set, not defaulted                        // plug in the new DACL                        SetKernelObjectSecurity(hMutex, DACL_SECURITY_INFORMATION, &sd);                        ...                        

這一方法明白地走向了正確的道路,但我們還須要找一個地方來放置此邏輯。把它放在一個一經請求即執行的小程式中是能夠的,可是看起來它有可能被中斷。我們的辦法是寫一個 Win32 服務來幹這件事情。

我們的 dbmutex 工具完畢的就是這一工作:它在系統引導時啟動,開啟或者建立相互排斥量,然後設定對象的安全性以同意廣泛的訪問。然後休眠直到系統關閉,在此過程中保持相互排斥量的開啟狀態。它不消耗不論什麼 CPU 時間。

實現細節

我們花了非常多時間使用 IDA Pro 深入到 Windows 2000 KERNEL32.DLL 的實現中,我們覺得,對於它在更精確的基礎上究竟是怎樣工作的已經有了良好的掌握。在這兒我們給出 OutputDebugString() 函數的虛擬碼(我們沒有編譯過它),以及建立相互排斥量的函數。

我們有益略去了大多數的錯誤檢查:假設事情變糟了,它將釋放全部已指派的資源並退出,就像沒有調試器存在一樣。目的是展示一般行為而不是對代碼的完整的逆向project。

“setup” 函數 - 名字是我們起的 - 建立相互排斥量或者在已經存在時開啟它。經過一些努力來設定相互排斥量對象的安全性以使不論什麼人都能用它,雖然我們會看到事實上並沒有全然正確地得到它。

• OutputDebugString.txt

胡思亂想

一些人可能會感到這是一個安全性問題,事實上並非。非管理使用者確實擁有適當使用 OutputDebugString() 的全部許可權,只是因為“請求比所需很多其它許可權”這一常見問題,一個合理的請求因形成了錯誤的形態而被拒絕了。

但並不像大部分的這樣的問題那樣,這並不是是有意的。大多數的錯誤是開發人員顯式請求了很多其它(如“MUTEX_ALL_ACCESS”),而這次的掩碼是由 CreateMutex() 的行為隱含的。這使得假設 Win32 API 不做修改的話更加難於避免。

---

 

當分析 KERNEL32.DLL 中的 OutputDebugStringA() 時,非管理員怎樣可以有可能去削弱系統變得明顯起來。一旦得到相互排斥量,一個要發送調試資訊的應用會等待 DBWIN_BUFFER_READY 事件對象就緒最多十秒鐘,假設逾時則放棄。這看起來是一個謹慎的防範措施,假設調試系統忙的話,用以避免被餓死。

但在更早的步驟裡,等待相互排斥量,沒有這種逾時設定。假設系統中的不論什麼進程 - 包含非特權進程 - 能夠以請求 SYNCHRONIZE 許可權開啟此相互排斥量,而且不釋放它,全部其它試圖擷取此相互排斥量的進程將會無限停止完蛋。

我們的研究表明,全部類型的程式都會發送任意的調試資訊(比如,MusicMatch Jukebox 就有一個嘮嘮叨叨的鍵盤鉤子),這些線程通過非常少的幾行代碼就能停止住。沒有必要停止整個程式 - 可能還有其它的線程 - 但在實際中,開發人員不計劃使用 OutputDebugString() 將會是一條拒絕服務之路(譯者註:此句沒有全然明確,請參看原文)。

---

 

最奇怪的是,我們發現 OutputDebugString() 並不是一個天然的 Unicode 函數。大多數的 Win32 API 具有“真正的”使用了 Unicode 的函數(“W” 版本號碼),假設調用“A”版本號碼的函數則它們自己主動從 ASCII 轉換到 UNICODE。

可是,由於 OutputDebugString 把在記憶體緩衝區中的資料終於是作為 ASCII 傳遞到調試器中的,它們具有相反於常規的 A/W 配對。這就暗示了假設要在 Unicode 程式裡發送一個快捷資訊到調試器,能夠通過直接調用 “A” 版本號碼來實現:

OutputDebugStringA("Got here to place X");

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.