2010年1月25日
#
CMake號稱是很輕大跨平臺的編譯工具,于是想試用一下,沒想到其Windows下的安裝程序如此弱智,把我的環境變量配置的Path路徑全給沖了,這樣的垃圾工具還是少用為妙。既然號稱跨平臺,連Windows都支持的這么差,也不支持中文,想必其他平臺的也好不到那里去。還是老老實實的寫我的Makefile吧。
2009年10月14日
#
可以通過反匯編來知道接口函數的參數,建議使用W32DSM來分析,也可以直接使用VC來分析,就是麻煩一點。
現在使用W32DSM來具體說明:
1。先打開需要分析的DLL,然后通過菜單功能-》出口來找到需要分析的函數,雙擊就可以了。
它可以直接定位到該函數。
2。看準該函數的入口,一般函數是以以下代碼作為入口點的。
push ebp
mov ebp, esp
...
3。然后往下找到該函數的出口,一般函數出口有以下語句。
...
ret xxxx;//其中xxxx就是函數差數的所有的字節數,為4的倍數,xxxx除以4得到的結果
就是參數的個數。
其中參數存放的地方:
ebp+08 //第一個參數
ebp+0C //第二個參數
ebp+10 //第三個參數
ebp+14 //第四個參數
ebp+18 //第五個參數
ebp+1C //第六個參數
。。。。
-------------------------------------------
還有一種經常看到的調用方式:
sub esp,xxxx //開頭部分
//函數的內容
。。。
//函數的內容
add esp,xxxx
ret //結尾部分
其中xxxx/4的結果也是參數的個數。
-------------------------------------------------
還有一種調用方式:
有于該函數比較簡單,沒有參數的壓棧過程,
里面的
esp+04就是第一個參數
esp+08就是第二個參數
。。。
esp+xx就是第xx/4個參數
你說看到的xx的最大數除以4后的結果,就是該函數所傳遞的參數的個數。
----------------------------------------------
到現在位置,你應該能很清楚的看到了傳遞的參數的個數。至于傳遞的是些什么內容,還需要進一步的分析。
最方便的辦法就是先找到是什么軟件在調用此函數,然后通過調試的技術,找到該函數被調用的地方。一般都是PUSH指令
來實現參數的傳遞的。這時可以看一下具體是什么東西被壓入堆棧了,一般來說,如果參數是整數,一看就可以知道了,
如果是字符串的話也是比較簡單的,只要到那個地址上面去看一下就可以了。
如果傳遞的結構的話,沒有很方便的辦法解決,就是讀懂該匯編就可以了。對于以上的分析,本人只其到了拋磚引玉,
希望對大家有點用處。
2009年9月29日
#
SFP Overwrite
I'd like a share a little trick I figured out today. I'm sure it has been done before and probably many times but its new to me so I'll describe it here. I asked myself if anything cool could be achieved by overwriting the Saved Frame Pointer instead of the Return Address. I'm not sure what sort of security-related attacks could be leveraged by doing this, but I did find that its possible to affect the program flow in the calling function. This is just another trick to change the flow of execution and even works on a non-executable stack.
The vulnerable program source code
The attack program source code
EBP and Local Variables
The extended base pointer is a register used by the CPU as base for the current stack frame. As we discussed earlier, the EBP for the function that called the current one is stored in the current function's Saved Frame Pointer. The EBP points to this SFP; in this way, there is a chain of saved EBPs for each stack frame. Check out the EIP Redirection page for more details about this. The location of the current stack frame's SFP is stored in the Extended Base Pointer.
Keeping track of the chain of the program's stack frames is not the only purpose of the EBP/SFPs. As the name implies, the Extended Base Pointer register holds an address that is used as the "base" of the current stack frame (this address is the frame's SFP). The code in the current function references local variables using EBP as a base. For example, the C code for setting a variable equal to 3 is myVar = 3;. This might be assembled into something like: MOV SS:[EBP - 4], 0x03. This means that the local variable myVar is referred to as "the address on the stack stored in EBP, minus 4 bytes" by the assembled code. Another variable in the same function called myVar2 might be located at SS:[EBP - 8]. We can see that if an attacker could change the value of EBP, they could also change where the code thinks local variables are.
Using SFP to Modify EBP

Imagine that there is function called FunctionA that is not vulnerable to any sort of buffer overflow. Inside it is a local integer variable that is used by the function later on. Before the variable is used, another function called FunctionB is called. This one happens to be vulnerable to a buffer overflow. Our goal is to trick FunctionA into using a variable that we define instead of the one that it declared.
We wait until the execution reaches FunctionB and we are able to input our malicious buffer. At the beginning we'll have the value of the fake variable that FunctionA will be tricked into using. From there, we pad the buffer with junk until the saved frame pointer is reached. Now we push the address on the stack that is directly below our fake variable. The reason we do this is that FunctionA references its real variable as SS:[ebp - 4]. Therefore, our injected EBP must be 4 bytes beyond our fake variable. By setting it up in this way, if we can manage to get that address into the Extended Base Pointer while FunctionA is executing; our fake variable will be referenced instead of the real one. We are not overwriting the value in the real variable. Instead, we are changing FunctionA's perception of its location to an address inside the buffer that we control.
The Target
Now its time to put our attack into a context. At the top of the source code, you'll notice the vulnerable test() function. All it does is declare a 5 byte buffer and fail to protect it while its being filled with user input. The main() function declares a local integer variable (4 bytes) and feeds the decimal value 3 into it. Main() then calls the test() function which asks the user for input but doesn't do anything with it. After test() returns, a loop is entered. The loop keeping looping as long as the integer c is greater than zero. Each time it repeats, the value of c is decreased by one and "Later" is displayed on the screen. Overall, this thing prints that word c times. Because the value of this integer was set to 3 earlier, this means that "Later" is printed 3 times by this loop. 
We will trick this program into thinking that a variable we create using the vulnerable buffer is actually c. By filling this fake variable with the decimal value 500,000,001, we'll make this loop run and print "Later" 500 million and one times instead of the expected three.
Buiding the Attack
As before, we'll be writing a program in C that will feed a malicious string into the input of the vulnerable program. Because our ultimate goal is to overwrite the test() function's SFP, we'll need to pad the string appropriately to get there. The first 4 bytes of the attack string will be the value of our fake variable that main() will eventually use in its loop. These bytes need to equal the goal of 500,000,001 so we'll feed "\x01\x65\xCD\x1D" first. The next four do not matter in this case, but take note that they will become main()'s fake SFP. From here use OllyDbg to find the distance between this fake main() SFP and test()'s SFP. If you are unsure where the test() function's SFP is, you can always step the program until inside test() and check the EBP register. You'll find that the buffer begins at 0x0027FF00 and the test() function's SFP is located at 0x0027FF18. After converting to decimal and subtracting the 8 bytes we already used up so far, it means we'll need to add 16 bytes of padding to reach test()'s SFP.

Next we'll actually overwrite the SFP. Have a look at the main() function and you'll find that the loop references the variable c as SS:[EBP - 4]. This means that if we want it to reference that 4 byte value at the beginning of the buffer instead, we need main()'s EBP to point to the address 4 bytes beyond the beginning of our attack buffer. We've already pointed out that any value we can sneak into test()'s SFP will end up in the EBP when main() returns. So we end the attack string with the following 3 bytes: "\x04\xFF\x27". The value is chosen because 0x0027FF04 - 4 is the address of our fake variable. Writing all four bytes is, again, not necessary because gets() automatically sticks a null byte at the end of the string inputted.
Final Overview
By crafting the above buffer and ensuring that the last 3 bytes overwrite test()'s SFP, we directly control EBP when test() returns back to main(). Because main() uses EBP to reference an important variable c and we control EBP; we make main() think that c is actually where our old overwritten buffer began. The data is still there because there was no reason for the program to erase/modify it. Because c is referenced as SS:[EBP - 4] in main(), we need that overwritten Stack Frame Pointer to point to the address four bytes below the fake variable location on the stack. When main uses our fake EBP to reference variable c is it will subtract 4 from it and find our fake variable value. This was filled in with the decimal value of 500,000,001. Main() runs the loop to print "Later" a number of times determined by c. So by doing all of this trickery we can make the program display "Later" 500,000,001 times instead of 3.
2009年8月14日
#
AspectC++ Add-In for Microsoft® Visual C++®
http://www.pure-systems.com/fileadmin/downloads/acaddin/Aspectc-AddIn-Setup-010101.exe
我用的是VS2003環境
調試目標:C:\Program Files\pure-systems\AspectC++ Add-In\bin\CPPAddin.dll
工具:用OD調試,IDA+Hex-rays分析
重點集中在以下幾個地址
0x10008e60 彈許可文件詢問的對話框
0x10017a20 解析xml格式的許可文件
0x10017730 貌似許可檢查的地方
這個是
0x10008e60的由Hex-ray分析出來的偽代碼,
代碼:
char __usercall sub_1000E860<al>(int a1<ebx>)
{
int ST14_4_0; // ST14_4@0
int v2; // ST10_4@1
const CHAR *v3; // eax@3
const CHAR *v4; // eax@10
int v6; // eax@2
char v7; // bl@2
char v8; // al@9
int v9; // [sp+Ch] [bp-9Ch]@1
signed int v10; // [sp+A4h] [bp-4h]@1
char v11; // [sp+10h] [bp-98h]@1
char v12; // [sp+48h] [bp-60h]@2
char v13; // [sp+2Ch] [bp-7Ch]@2
LPCSTR lpText; // [sp+14h] [bp-94h]@3
unsigned int v15; // [sp+28h] [bp-80h]@3
char v16; // [sp+64h] [bp-44h]@15
BYTE3(v9) = 0;
sub_10016AA0();
v10 = 0;
std__basic_string_char_std__char_traits_char__std__allocator_char____basic_string_char_std__char_traits_char__std__allocator_char__(&v11);
LOBYTE(v10) = 1;
v2 = a1;
while ( 1 )
{
sub_100169B0(16, (int)&v12);
LOBYTE(v10) = 2;
v6 = std__operator_(&v13, v6, "\\etc\\licence");
LOBYTE(v10) = 3;
v7 = sub_10017A20((int)&unk_10024460, v6, (int)&v11) == 0;
LOBYTE(v10) = 2;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v13);
LOBYTE(v10) = 1;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v12);
if ( !v7 )
break;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v11,
"\nThe add-in will not work without a valid licence.\n\nInstall the licence file now?",
v2);
v3 = lpText;
if ( v15 < 0x10 )
v3 = (const CHAR *)&lpText;
if ( MessageBoxA(0, v3, "AspectC++ Add-In for Visual Studio .NET", 0x24u) != 6 )
break;
if ( !sub_1000E690() )
{
MessageBoxA(0, "Installation of licence file failed.", "AspectC++ Add-In", 0x40u);
break;
}
}
if ( byte_10024465 )
{
sub_10017730((int)&unk_10024460, 16, (int)&v11);
if ( v8 && (unsigned __int8)sub_10017520(&v11) != 1 )
{
BYTE3(v9) = 1;
}
else
{
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v11,
"\nThe add-in will not work.",
ST14_4_0);
v4 = lpText;
if ( v15 < 0x10 )
v4 = (const CHAR *)&lpText;
MessageBoxA(0, v4, "AspectC++ Add-In", 0x40u);
}
}
LOBYTE(v10) = 0;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v11);
v10 = -1;
sub_10016910((int)&v16);
return BYTE3(v9);
}
基本上可以得出默認是分析
C:\Program Files\pure-systems\AspectC++ Add-In\etc\licence 許可文件
分析過程應該在0x10017a20里
代碼:
char __thiscall sub_10017A20(int this, int a2, int a3)
{
int v3; // ebp@1
int v4; // esi@1
int v5; // eax@2
int v6; // eax@4
int v7; // edi@5
char result; // al@8
int v9; // ecx@1
int v10; // eax@5
int v11; // eax@6
int v12; // ST04_4@6
char v13; // bl@6
char v14; // [sp+Ch] [bp-28h]@6
signed int v15; // [sp+30h] [bp-4h]@6
v3 = a3;
v4 = this;
v9 = this + 96;
*(_DWORD *)v9 = 0;
*(_DWORD *)(v9 + 4) = 0;
*(_DWORD *)(v9 + 8) = 0;
*(_DWORD *)(v9 + 12) = 0;
*(_BYTE *)(v4 + 5) = 0;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator_(v3, "Loading licence file failed.");
if ( *(_DWORD *)(a2 + 24) < 0x10u )
v5 = a2 + 4;
else
v5 = *(_DWORD *)(a2 + 4);
v6 = xmlParseFile(v5);
*(_DWORD *)v4 = v6;
if ( v6
&& (v10 = xmlDocGetRootElement(v6), v7 = v10, v10)
&& (v11 = std__basic_string_char_std__char_traits_char__std__allocator_char____basic_string_char_std__char_traits_char__std__allocator_char__(
&v14,
"licence"), v12 = *(_DWORD *)(v7 + 8), v15 = 0, v13 = std__operator__(v11, v12), v15 = -1, std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v14), v13)
&& sub_10017930(v7) )
{
*(_BYTE *)(v4 + 5) = 1;
sub_10017730(v4, v3, v3);
result = 1;
}
else
{
result = 0;
}
return result;
}
這是0x10017730的過程
代碼:
void __userpurge sub_10017730(int this<ecx>, int ebp0<ebp>, int a2)
{
int ST18_4_0; // ST18_4@0
int ST1C_4_0; // ST1C_4@0
int v5; // esi@1
signed int v6; // eax@4
int v7; // ST18_4@6
int v8; // eax@20
int v9; // eax@4
int v10; // ST1C_4@12
int v11; // ST1C_4@18
int r; // [sp+60h] [bp+0h]@1
int v13; // [sp+50h] [bp-10h]@1
signed int v14; // [sp+5Ch] [bp-4h]@3
char v15; // [sp+34h] [bp-2Ch]@4
char v16; // [sp+18h] [bp-48h]@4
int v17; // [sp+1Ch] [bp-44h]@12
unsigned int v18; // [sp+30h] [bp-30h]@20
int v19; // [sp+2Ch] [bp-34h]@22
v5 = this;
v13 = r ^ dword_100242D0;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator_(a2, "Invalid licence file found.");
if ( *(_BYTE *)(v5 + 5) && !*(_BYTE *)(v5 + 4) )
{
sub_10017B20((int)&a2, ebp0);
v14 = 0;
if ( !sub_1001A050((int)&a2) )
{
LABEL_23:
v14 = -1;
sub_1001A060(&a2);
goto LABEL_24;
}
v9 = std__operator_(&v15, v5 + 12, v5 + 40);
LOBYTE(v14) = 1;
std__operator_(&v16, v9, v5 + 68);
LOBYTE(v14) = 3;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v15);
v6 = *(_DWORD *)(v5 + 8);
if ( v6 || !*(_DWORD *)(v5 + 96) )
{
if ( v6 == 1 && *(_DWORD *)(v5 + 96) && *(_DWORD *)(v5 + 100) && *(_DWORD *)(v5 + 104) && *(_DWORD *)(v5 + 108) )
{
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v16, "evaluation", ST18_4_0);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 96),
ST1C_4_0);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 100),
v10);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 104),
ST1C_4_0);
v7 = *(_DWORD *)(v5 + 108);
}
else
{
if ( v6 != 2
|| !*(_DWORD *)(v5 + 96)
|| !*(_DWORD *)(v5 + 100)
|| !*(_DWORD *)(v5 + 104)
|| !*(_DWORD *)(v5 + 108) )
goto LABEL_20;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v16, "user", ST18_4_0);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 96),
ST1C_4_0);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 100),
v11);
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(
&v17,
*(_DWORD *)(v5 + 104),
ST1C_4_0);
v7 = *(_DWORD *)(v5 + 108);
}
}
else
{
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v16, "beta", ST18_4_0);
v7 = *(_DWORD *)(v5 + 96);
}
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v17, v7, ST1C_4_0);
LABEL_20:
v8 = v17;
if ( v18 < 0x10 )
v8 = (int)&v17;
*(_BYTE *)(v5 + 4) = sub_10019E30(v5 + 112, v8, v19, (int)&a2);
LOBYTE(v14) = 0;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v16);
goto LABEL_23;
}
LABEL_24:
sub_1001A7B0( r ^ v13, ebp0);
}
昨天我自己調試的時候也犯了一個錯誤,就是調試CppAddin.dll很痛苦,浪費不少時間和分散很多的精力,中間老是受到Va_X.dll的干擾,而且msdev.exe在CppAddin沒加載時沒法直接下斷點,必須得在OD中設置一個新模塊加載的斷點(選項->調試選項->事件->中斷于新模塊),這樣下來,每次調試失敗后重啟的過程很是漫長,浪費了不少時間,發帖子的求助的時候,我為了大大們給我能方便的解決問題,盡量把非技術問題都試驗了一下,以免大大們被這些問題困住了,一怒之下不給我管我了。但是這樣一來,也為我自己解決和發現問題加快了速度。
總結一下:
1.解決問題要東方不亮,西方亮,不要將自己精力消耗在遠離問題本身的地方
2.堅持就是勝利,哪怕是菜鳥,不懂多少匯編(看代碼還得靠偉大、光榮、正確的F5),也能取得成績
下面寫寫分析過程:
其實昨天已經找到了關鍵的過程,那就是0x10017a20,這里面就是分析xml格式的許可文件
這里面有兩個調用,sub_10017930()和 sub_10017730()
大概可以看得出來,sub_10017930參與了xml格式的許可數據的解析過程,它調用成功之后,才調用10017730對數據進行驗證,要分析許可數據格式,必須研究10017930
代碼:
char __stdcall sub_10017930(int a1)
{
int v1; // ebp@1
int v2; // esi@1
int v3; // esi@3
char result; // al@7
int v5; // eax@1
int v6; // eax@2
char v7; // bl@2
char v8; // [sp+Ch] [bp-28h]@2
signed int v9; // [sp+30h] [bp-4h]@2
v1 = a1;
v5 = xmlGetProp(a1, "version");
v2 = v5;
if ( v5
&& (v6 = std__basic_string_char_std__char_traits_char__std__allocator_char____basic_string_char_std__char_traits_char__std__allocator_char__(
&v8,
L"1"), v9 = 0, v7 = std__operator__(v6, v2), v9 = -1, std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v8), v7) )
{
v3 = *(_DWORD *)(v1 + 12);
result = (unsigned __int8)sub_10016D60(*(_DWORD *)(v1 + 12))
&& (unsigned __int8)sub_10016E70(v3)
&& (unsigned __int8)sub_10017570(v3)
&& (unsigned __int8)sub_100172C0(v3);
}
else
{
result = 0;
}
return result;
}
在這里面取了xml的一個屬性值version,并且還有四個都不能失敗的過程:
10016d60 取product 元素
10016e70 取creation 元素
10017570 取type元素
100172c0 取signature元素
為了熟悉libxml,我特意編寫了一段典型libxml用法,并分析了其反向工程后的代碼,
代碼:
xmlDocPtr doc = NULL;
xmlNodePtr cur = NULL;
string filename("test.xml");
doc = xmlParseFile(filename.c_str());
cur = xmlDocGetRootElement(doc);
if (xmlStrcmp(cur->name, BAD_CAST "licence"))
{
fprintf(stderr,"document of the wrong type, root node != mail");
xmlFreeDoc(doc);
/*return -1; */
}
cur = cur->xmlChildrenNode;
xmlNodePtr propNodePtr = cur;
while(cur != NULL)
{
//¨¨?3??¨²¦Ì??D¦Ì??¨²¨¨Y
if ((!xmlStrcmp(cur->name, (const xmlChar *)"product")))
{
string name;
string version;
xmlChar* szAttr = xmlGetProp(cur,BAD_CAST "name");
name=(char*)szAttr;
szAttr=xmlGetProp(cur,BAD_CAST "version");
version=(const char *)szAttr;
cout<<"Name:"<<name<<" Version:"<<version<<endl;
xmlFree(szAttr);
}
cur=cur->next;
}
xmlFreeDoc(doc);
/*return 0;*/
得出的結論是,凡是屬性值的獲取,都會調用xmlGetProp,而子元素的獲取則是通過直接訪問內存結構或者類似c++的函數訪問,不會看到函數名稱的引用,根據這一結論和具體的調試,大致推出來licence文件的結構如下:
代碼:
<?xml version="1.0" encoding="UTF-8"?>
<licence version="1">
<product name="aspect" version="1.0"/>
<product name="pointcut" version="1.0" />
<product name="advice" version="1.0" />
<product name="slice" version="1.0" />
<creation date="2009-8-13"/>
<type name="user">
<data code="xxxx" name="jans2002" email="jans2002@gmail.com" regnr="ccccc"/>
</type>
<signature sign="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
</licence>
有了這個licence文件,把它放到
C:\Program Files\pure-systems\AspectC++ Add-In\etc\目錄下
一切問題就變得簡單了,順利執行完了0x10017930,一路殺到0x10017730
代碼:
int __thiscall sub_10017730(int this, int a2)
{
int v2; // esi@1
signed int v3; // eax@4
int v4; // ST14_4@6
int v5; // eax@20
int v7; // eax@4
int r; // [sp+4Ch] [bp+0h]@1
int v9; // [sp+3Ch] [bp-10h]@1
signed int v10; // [sp+48h] [bp-4h]@3
char v11; // [sp+20h] [bp-2Ch]@4
char v12; // [sp+4h] [bp-48h]@4
int v13; // [sp+8h] [bp-44h]@20
unsigned int v14; // [sp+1Ch] [bp-30h]@20
int v15; // [sp+18h] [bp-34h]@22
v2 = this;
v9 = r ^ dword_100242D0;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator_(a2, "Invalid licence file found.");
if ( *(_BYTE *)(v2 + 5) && !*(_BYTE *)(v2 + 4) )
{
sub_10017B20();
v10 = 0;
if ( !(unsigned __int8)sub_1001A050() )
{
LABEL_23:
v10 = -1;
sub_1001A060();
return sub_1001A7B0();
}
v7 = std__operator_(&v11, v2 + 12, v2 + 40);
LOBYTE(v10) = 1;
std__operator_(&v12, v7, v2 + 68);
LOBYTE(v10) = 3;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v11);
v3 = *(_DWORD *)(v2 + 8);
if ( v3 || !*(_DWORD *)(v2 + 96) )
{
if ( v3 == 1 && *(_DWORD *)(v2 + 96) && *(_DWORD *)(v2 + 100) && *(_DWORD *)(v2 + 104) && *(_DWORD *)(v2 + 108) )
{
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, "evaluation");
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 96));
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 100));
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 104));
v4 = *(_DWORD *)(v2 + 108);
}
else
{
if ( v3 != 2
|| !*(_DWORD *)(v2 + 96)
|| !*(_DWORD *)(v2 + 100)
|| !*(_DWORD *)(v2 + 104)
|| !*(_DWORD *)(v2 + 108) )
goto LABEL_20;
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, "user");
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 96));
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 100));
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, *(_DWORD *)(v2 + 104));
v4 = *(_DWORD *)(v2 + 108);
}
}
else
{
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, "beta");
v4 = *(_DWORD *)(v2 + 96);
}
std__basic_string_char_std__char_traits_char__std__allocator_char____operator__(&v12, v4);
LABEL_20:
v5 = v13;
if ( v14 < 0x10 )
v5 = (int)&v13;
*(_BYTE *)(v2 + 4) = sub_10019E30(v2 + 112, v5, v15, (int)&a2);
LOBYTE(v10) = 0;
std__basic_string_char_std__char_traits_char__std__allocator_char_____basic_string_char_std__char_traits_char__std__allocator_char__(&v12);
goto LABEL_23;
}
return sub_1001A7B0();
}
由于有了正確格式的licence,前面執行的都很順利,一直到了sub_10019E30,跟進去一看,一切了然了:
代碼:
bool __thiscall sub_10019E30(int this, int a2, int a3, int a4)
{
int v5; // esi@1
int v6; // eax@1
int v7; // eax@1
char v8; // [sp+4h] [bp-10h]@1
v5 = this;
v6 = EVP_sha1();
EVP_DigestInit(&v8, v6);
EVP_DigestUpdate(&v8, a2, a3);
v7 = sub_1001A030();
return EVP_VerifyFinal(&v8, v5, *(_DWORD *)(v5 + 4096), v7) == 1;
}
前面一定都是加密驗證的,但是函數最后來了一句:
代碼:
EVP_VerifyFinal(&v8, v5, *(_DWORD *)(v5 + 4096), v7) == 1;
嘿嘿,看名字估計就是這個了,看看這個函數的返回值,1表示成功,其他表示失敗,那就是只要讓此函數永遠返回1,就大功告成了,該到匯編代碼了:
代碼:
004066F0 /$ 83EC 10 sub esp, 10 ; 檢查數字簽名部分
004066F3 |. 56 push esi
004066F4 |. 8BF1 mov esi, ecx
004066F6 |. E8 81270000 call <jmp.&LIBEAY32.#333>
004066FB |. 50 push eax
004066FC |. 8D4424 08 lea eax, dword ptr [esp+8]
00406700 |. 50 push eax
00406701 |. E8 70270000 call <jmp.&LIBEAY32.#268>
00406706 |. 8B4C24 24 mov ecx, dword ptr [esp+24]
0040670A |. 8B5424 20 mov edx, dword ptr [esp+20]
0040670E |. 51 push ecx
0040670F |. 52 push edx
00406710 |. 8D4424 14 lea eax, dword ptr [esp+14]
00406714 |. 50 push eax
00406715 |. E8 56270000 call <jmp.&LIBEAY32.#269>
0040671A |. 8B4C24 34 mov ecx, dword ptr [esp+34]
0040671E |. 83C4 14 add esp, 14
00406721 |. E8 2A100000 call 00407750
00406726 |. 8B8E 00100000 mov ecx, dword ptr [esi+1000]
0040672C |. 50 push eax
0040672D |. 51 push ecx
0040672E |. 8D5424 0C lea edx, dword ptr [esp+C]
00406732 |. 56 push esi
00406733 |. 52 push edx
00406734 |. E8 49270000 call <jmp.&LIBEAY32.#290>
00406739 |. 83C4 10 add esp, 10
0040673C |. 48 dec eax
0040673D |. F7D8 neg eax
0040673F 1BC0 sbb eax, eax
00406741 90 inc eax
00406742 5E pop esi
00406743 83C4 10 add esp, 10
00406746 \. C2 0C00 retn 0C
一般來講,函數返回值都會用EAX,不管三七二十一了,先吧EAX做了再說,把上面那個黑體的
inc eax 改成nop(呵呵,nop是大家的最愛,機器碼是90了,這是改過的了)
保存到文件。
大功告成哈!!
身為菜鳥,能取得這樣的結果,真是讓人興奮,謝謝各位閱讀,還請各位高手不吝賜教。
下載安裝程序及破解
在一個使用UNICODE的工程里使用log4cplus 1.0.3庫,發現總是發生鏈接錯誤。猜想可能是log4cplus需要在UNICODE下編譯,基于Debug和Release兩個復制了Unicode Debug/Release配置,開始編譯…,UniRelease很容易通過了編譯,但是UniDebug卻總是發生鏈接錯誤,但是函數肯定已經實現了,將鏈接錯誤發生的文件Appender.obj文件打開,然后比較編譯器里報出的信息
一個是
?error@OnlyOnceErrorHandler@log4cplus@@UAEXABV?$basic_string@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@@Z
而鏈式器找不到的符號是:
?error@OnlyOnceErrorHandler@log4cplus@@UAEXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z
這兩個符號都很晦澀,很難讓人搞明白什么意思,放狗一搜,還真有工具叫undname.exe,在VC7.1里自帶,
Undecoration of :- "?error@OnlyOnceErrorHandler@log4cplus@@UAEXABV?$basic_string
@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@@Z"
is :- "public: virtual void __thiscall log4cplus::OnlyOnceErrorHandler::error(cl
ass std::basic_string<unsigned short,struct std::char_traits<unsigned short>,cla
ss std::allocator<unsigned short> > const &)"
Undecoration of :- "?error@OnlyOnceErrorHandler@log4cplus@@UAEXABV?$basic_string
@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z"
is :- "public: virtual void __thiscall log4cplus::OnlyOnceErrorHandler::error(cl
ass std::basic_string<char,struct std::char_traits<char>,class std::allocator<ch
ar> > const &)"
一對比,就知道可能還是UNICODE的原因,仔細檢查C++預處理符號設置,發現好多都還是MCBS,修改,重新編譯,問題解決。
2009年4月5日
#
NAME = loggingserver
all : $(NAME).exe
$(NAME).obj: $(NAME).cxx
cl $(NAME).cxx /G7 /MD /Ox /Ot /W3 /c /EHsc /I "D:\log4cplus-1.0.3\include"
$(NAME).exe: $(NAME).obj
link $(NAME).obj /out:$(NAME).exe /LIBPATH:"D:\log4cplus-1.0.3\msvc6\Debug" /SUBSYSTEM:CONSOLE /MACHINE:X86 log4cplusd.lib
clean:
del *.obj
del *.exe
2009年2月20日
#
解開 Windows 下的臨界區中的代碼死鎖
Matt Pietrek和 Russ Osterlund
本文假定您熟悉 Win32、C++ 和多線程處理。
下載本文的代碼:CriticalSections.exe (415KB)
摘要
臨界區是一種防止多個線程同時執行一個特定代碼節的機制,這一主題并沒有引起太多關注,因而人們未能對其深刻理解。在需要跟蹤代碼中的多線程處理的性能時,對 Windows 中臨界區的深刻理解非常有用。 本文深入研究臨界區的原理,以揭示在查找死鎖和確認性能問題過程中的有用信息。它還包含一個便利的實用工具程序,可以顯示所有臨界區及其當前狀態。
在我們許多年的編程實踐中,對于 Win32? 臨界區沒有受到非常多的“under the hood”關注而感到非常奇怪。當然,您可能了解有關臨界區初始化與使用的基礎知識,但您是否曾經花費時間來深入研究 WINNT.H 中所定義的 CRITICAL_SECTION 結構呢?在這一結構中有一些非常有意義的好東西被長期忽略。我們將對此進行補充,并向您介紹一些很有意義的技巧,這些技巧對于跟蹤那些難以察覺的多線程處理錯誤非常有用。更重要的是,使用我們的 MyCriticalSections 實用工具,可以明白如何對 CRITICAL_SECTION 進行微小地擴展,以提供非常有用的特性,這些特性可用于調試和性能調整(要下載完整代碼,參見本文頂部的鏈接)。
老實說,作者們經常忽略 CRITICAL_SECTION 結構的部分原因在于它在以下兩個主要 Win32 代碼庫中的實現有很大不同:Microsoft? Windows? 95 和 Windows NT?。人們知道這兩種代碼庫都已經發展出大量后續版本(其最新版本分別為 Windows Me 和 Windows XP),但沒有必要在此處將其一一列出。關鍵在于 Windows XP 現在已經發展得非常完善,開發商可能很快就會停止對 Windows 95 系列操作系統的支持。我們在本文中就是這么做的。
誠然,當今最受關注的是 Microsoft .NET Framework,但是良好的舊式 Win32 編程不會很快消失。如果您擁有采用了臨界區的現有 Win32 代碼,您會發現我們的工具以及對臨界區的說明都非常有用。但是請注意,我們只討論 Windows NT 及其后續版本,而沒有涉及與 .NET 相關的任何內容,這一點非常重要。
臨界區:簡述
如果您非常熟悉臨界區,并可以不假思索地進行應用,那就可以略過本節。否則,請向下閱讀,以對這些內容進行快速回顧。如果您不熟悉這些基礎內容,則本節之后的內容就沒有太大意義。
臨界區是一種輕量級機制,在某一時間內只允許一個線程執行某個給定代碼段。通常在修改全局數據(如集合類)時會使用臨界區。事件、多用戶終端執行程序和信號量也用于多線程同步,但臨界區與它們不同,它并不總是執行向內核模式的控制轉換,這一轉換成本昂貴。稍后將會看到,要獲得一個未占用臨界區,事實上只需要對內存做出很少的修改,其速度非常快。只有在嘗試獲得已占用臨界區時,它才會跳至內核模式。這一輕量級特性的缺點在于臨界區只能用于對同一進程內的線程進行同步。
臨界區由 WINNT.H 中所定義的 RTL_CRITICAL_SECTION 結構表示。因為您的 C++ 代碼通常聲明一個 CRITICAL_SECTION 類型的變量,所以您可能對此并不了解。研究 WINBASE.H 后您會發現:
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
我們將在短時間內揭示 RTL_CRITICAL_SECTION 結構的實質。此時,重要問題在于 CRITICAL_SECTION(也稱作 RTL_CRITICAL_SECTION)只是一個擁有易訪問字段的結構,這些字段可以由 KERNEL32 API 操作。
在將臨界區傳遞給 InitializeCriticalSection 時(或者更準確地說,是在傳遞其地址時),臨界區即開始存在。初始化之后,代碼即將臨界區傳遞給 EnterCriticalSection 和 LeaveCriticalSection API。一個線程自 EnterCriticalSection 中返回后,所有其他調用 EnterCriticalSection 的線程都將被阻止,直到第一個線程調用 LeaveCriticalSection 為止。最后,當不再需要該臨界區時,一種良好的編碼習慣是將其傳遞給 DeleteCriticalSection。
在臨界區未被使用的理想情況中,對 EnterCriticalSection 的調用非常快速,因為它只是讀取和修改用戶模式內存中的內存位置。否則(在后文將會遇到一種例外情況),阻止于臨界區的線程有效地完成這一工作,而不需要消耗額外的 CPU 周期。所阻止的線程以內核模式等待,在該臨界區的所有者將其釋放之前,不能對這些線程進行調度。如果有多個線程被阻止于一個臨界區中,當另一線程釋放該臨界區時,只有一個線程獲得該臨界區。
深入研究:RTL_CRITICAL_SECTION 結構
即使您已經在日常工作中使用過臨界區,您也非常可能并沒有真正了解超出文檔之外的內容。事實上存在著很多非常容易掌握的內容。例如,人們很少知道一個進程的臨界區是保存于一個鏈表中,并且可以對其進行枚舉。實際上,WINDBG 支持 !locks 命令,這一命令可以列出目標進程中的所有臨界區。我們稍后將要談到的實用工具也應用了臨界區這一鮮為人知的特征。為了真正理解這一實用工具如何工作,有必要真正掌握臨界區的內部結構。記著這一點,現在開始研究 RTL_CRITICAL_SECTION 結構。為方便起見,將此結構列出如下:
struct RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};
以下各段對每個字段進行說明。
DebugInfo 此字段包含一個指針,指向系統分配的伴隨結構,該結構的類型為 RTL_CRITICAL_SECTION_DEBUG。這一結構中包含更多極有價值的信息,也定義于 WINNT.H 中。我們稍后將對其進行更深入地研究。
LockCount 這是臨界區中最重要的一個字段。它被初始化為數值 -1;此數值等于或大于 0 時,表示此臨界區被占用。當其不等于 -1 時,OwningThread 字段(此字段被錯誤地定義于 WINNT.H 中 — 應當是 DWORD 而不是 HANDLE)包含了擁有此臨界區的線程 ID。此字段與 (RecursionCount -1) 數值之間的差值表示有多少個其他線程在等待獲得該臨界區。
RecursionCount 此字段包含所有者線程已經獲得該臨界區的次數。如果該數值為零,下一個嘗試獲取該臨界區的線程將會成功。
OwningThread 此字段包含當前占用此臨界區的線程的線程標識符。此線程 ID 與 GetCurrentThreadId 之類的 API 所返回的 ID 相同。
LockSemaphore 此字段的命名不恰當,它實際上是一個自復位事件,而不是一個信號。它是一個內核對象句柄,用于通知操作系統:該臨界區現在空閑。操作系統在一個線程第一次嘗試獲得該臨界區,但被另一個已經擁有該臨界區的線程所阻止時,自動創建這樣一個句柄。應當調用 DeleteCriticalSection(它將發出一個調用該事件的 CloseHandle 調用,并在必要時釋放該調試結構),否則將會發生資源泄漏。
SpinCount 僅用于多處理器系統。MSDN? 文檔對此字段進行如下說明:“在多處理器系統中,如果該臨界區不可用,調用線程將在對與該臨界區相關的信號執行等待操作之前,旋轉 dwSpinCount 次。如果該臨界區在旋轉操作期間變為可用,該調用線程就避免了等待操作。”旋轉計數可以在多處理器計算機上提供更佳性能,其原因在于在一個循環中旋轉通常要快于進入內核模式等待狀態。此字段默認值為零,但可以用 InitializeCriticalSectionAndSpinCount API 將其設置為一個不同值。
RTL_CRITICAL_SECTION_DEBUG 結構
前面我們注意到,在 RTL_CRITICAL_SECTION 結構內,DebugInfo 字段指向一個 RTL_CRITICAL_SECTION_DEBUG 結構,該結構給出如下:
struct _RTL_CRITICAL_SECTION_DEBUG
{
WORD Type;
WORD CreatorBackTraceIndex;
RTL_CRITICAL_SECTION *CriticalSection;
LIST_ENTRY ProcessLocksList;
DWORD EntryCount;
DWORD ContentionCount;
DWORD Spare[ 2 ];
}
這一結構由 InitializeCriticalSection 分配和初始化。它既可以由 NTDLL 內的預分配數組分配,也可以由進程堆分配。RTL_CRITICAL_SECTION 的這一伴隨結構包含一組匹配字段,具有迥然不同的角色:有兩個難以理解,隨后兩個提供了理解這一臨界區鏈結構的關鍵,兩個是重復設置的,最后兩個未使用。
下面是對 RTL_CRITICAL_SECTION 字段的說明。
Type 此字段未使用,被初始化為數值 0。
CreatorBackTraceIndex 此字段僅用于診斷情形中。在注冊表項 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb 值。注意,只有在運行稍后說明的 Gflags 命令時才會顯示這些值。這些注冊表值的設置正確時,CreatorBackTraceIndex 字段將由堆棧跟蹤中所用的一個索引值填充。在 MSDN 中搜索 GFlags 文檔中的短語“create user mode stack trace database”和“enlarging the user-mode stack trace database”,可以找到有關這一內容的更多信息。
CriticalSection 指向與此結構相關的 RTL_CRITICAL_SECTION。圖 1 說明該基礎結構以及 RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG 和事件鏈中其他參與者之間的關系。
圖 1 臨界區處理流程
ProcessLocksList LIST_ENTRY 是用于表示雙向鏈表中節點的標準 Windows 數據結構。RTL_CRITICAL_SECTION_DEBUG 包含了鏈表的一部分,允許向前和向后遍歷該臨界區。本文后面給出的實用工具說明如何使用 Flink(前向鏈接)和 Blink(后向鏈接)字段在鏈表中的成員之間移動。任何從事過設備驅動程序或者研究過 Windows 內核的人都會非常熟悉這一數據結構。
EntryCount/ContentionCount 這些字段在相同的時間、出于相同的原因被遞增。這是那些因為不能馬上獲得臨界區而進入等待狀態的線程的數目。與 LockCount 和 RecursionCount 字段不同,這些字段永遠都不會遞減。
Spares 這兩個字段未使用,甚至未被初始化(盡管在刪除臨界區結構時將這些字段進行了清零)。后面將會說明,可以用這些未被使用的字段來保存有用的診斷值。
即使 RTL_CRITICAL_SECTION_DEBUG 中包含多個字段,它也是常規臨界區結構的必要成分。事實上,如果系統恰巧不能由進程堆中獲得這一結構的存儲區,InitializeCriticalSection 將返回為 STATUS_NO_MEMORY 的 LastError 結果,然后返回處于不完整狀態的臨界區結構。
臨界區狀態
當程序執行、進入與離開臨界區時,RTL_CRITICAL_SECTION 和 RTL_CRITICAL_SECTION_DEBUG 結構中的字段會根據臨界區所處的狀態變化。這些字段由臨界區 API 中的簿記代碼更新,在后面將會看到這一點。如果程序為多線程,并且其線程訪問是由臨界區保護的公用資源,則這些狀態就更有意義。
但是,不管代碼的線程使用情況如何,有兩種狀態都會出現。第一種情況,如果 LockCount 字段有一個不等于 -1 的數值,此臨界區被占用,OwningThread 字段包含擁有該臨界區的線程的線程標識符。在多線程程序中,LockCount 與 RecursionCount 聯合表明當前有多少線程被阻止于該臨界區。第二種情況,如果 RecursionCount 是一個大于 1 的數值,其告知您所有者線程已經重新獲得該臨界區多少次(也許不必要),該臨界區既可以通過調用 EnterCriticalSection、也可以通過調用 TryEnterCriticalSection 獲得。大于 1 的任何數值都表示代碼的效率可能較低或者可能在以后發生錯誤。例如,訪問公共資源的任何 C++ 類方法可能會不必要地重新進入該臨界區。
注意,在大多數時間里,LockCount 與 RecursionCount 字段中分別包含其初始值 -1 和 0,這一點非常重要。事實上,對于單線程程序,不能僅通過檢查這些字段來判斷是否曾獲得過臨界區。但是,多線程程序留下了一些標記,可以用來判斷是否有兩個或多個線程試圖同時擁有同一臨界區。
您可以找到的標記之一是即使在該臨界區未被占用時 LockSemaphore 字段中仍包含一個非零值。這表示:在某一時間,此臨界區阻止了一個或多個線程 — 事件句柄用于通知該臨界區已被釋放,等待該臨界區的線程之一現在可以獲得該臨界區并繼續執行。因為 OS 在臨界區阻止另一個線程時自動分配事件句柄,所以如果您在不再需要臨界區時忘記將其刪除,LockSemaphore 字段可能會導致程序中發生資源泄漏。
在多線程程序中可能遇到的另一狀態是 EntryCount 和 ContentionCount 字段包含一個大于零的數值。這兩個字段保存有臨界區對一個線程進行阻止的次數。在每次發生這一事件時,這兩個字段被遞增,但在臨界區存在期間不會被遞減。這些字段可用于間接確定程序的執行路徑和特性。例如,EntryCount 非常高時則意味著該臨界區經歷著大量爭用,可能會成為代碼執行過程中的一個潛在瓶頸。
在研究一個死鎖程序時,還會發現一種似乎無法進行邏輯解釋的狀態。一個使用非常頻繁的臨界區的 LockCount 字段中包含一個大于 -1 的數值,也就是說它被線程所擁有,但是 OwningThread 字段為零(這樣就無法找出是哪個線程導致問題)。測試程序是多線程的,在單處理器計算機和多處理器計算機中都會出現這種情況。盡管 LockCount 和其他值在每次運行中都不同,但此程序總是死鎖于同一臨界區。我們非常希望知道是否有任何其他開發人員也遇到了導致這一狀態的 API 調用序列。
構建一個更好的捕鼠器
在我們學習臨界區的工作方式時,非常偶然地得到一些重要發現,利用這些發現可以得到一個非常好的實用工具。第一個發現是 ProcessLocksList LIST_ENTRY 字段的出現,這使我們想到進程的臨界區可能是可枚舉的。另一個重大發現是我們知道了如何找出臨界區列表的頭。還有一個重要發現是可以在沒有任何損失的情況下寫 RTL_CRITICAL_SECTION 的 Spare 字段(至少在我們的所有測試中如此)。我們還發現可以很容易地重寫系統的一些臨界區例程,而不需要對源文件進行任何修改。
最初,我們由一個簡單的程序開始,其檢查一個進程中的所有臨界區,并列出其當前狀態,以查看是否擁有這些臨界區。如果擁有,則找出由哪個線程擁有,以及該臨界區阻止了多少個線程?這種做法對于 OS 的狂熱者們比較適合,但對于只是希望有助于理解其程序的典型的程序員就不是非常有用了。
即使是在最簡單的控制臺模式“Hello World”程序中也存在許多臨界區。其中大部分是由 USER32 或 GDI32 之類的系統 DLL 創建,而這些 DLL 很少會導致死鎖或性能問題。我們希望有一種方法能濾除這些臨界區,而只留下代碼中所關心的那些臨界區。RTL_CRITICAL_SECTION_DEBUG 結構中的 Spare 字段可以很好地完成這一工作。可以使用其中的一個或兩個來指示:這些臨界區是來自用戶編寫的代碼,而不是來自 OS。
于是,下一個邏輯問題就變為如何確定哪些臨界區是來自您編寫的代碼。有些讀者可能還記得 Matt Pietrek 2001 年 1 月的 Under The Hood 專欄中的 LIBCTINY.LIB。LIBCTINY 所采用的一個技巧是一個 LIB 文件,它重寫了關鍵 Visual C++ 運行時例程的標準實現。將 LIBCTINY.LIB 文件置于鏈接器行的其他 LIB 之前,鏈接器將使用這一實現,而不是使用 Microsoft 所提供的導入庫中的同名后續版本。
為對臨界區應用類似技巧,我們創建 InitializeCriticalSection 的一個替代版本及其相關導入庫。將此 LIB 文件置于 KERNEL32.LIB 之前,鏈接器將鏈接我們的版本,而不是 KERNEL32 中的版本。對 InitializeCriticalSection 的實現顯示在圖 2 中。此代碼在概念上非常簡單。它首先調用 KERNEL32.DLL 中的實際 InitializeCriticalSection。接下來,它獲得調用 InitializeCriticalSection 的代碼地址,并將其貼至 RTL_CRITICAL_SECTION_DEBUG 結構的備用字段之一。我們的代碼如何確定調用代碼的地址呢?x86 CALL 指令將返回地址置于堆棧中。CriticalSectionHelper 代碼知道該返回地址位于堆棧幀中一個已知的固定位置。
實際結果是:與 CriticalSectionHelper.lib 正確鏈接的任何 EXE 或 DLL 都將導入我們的 DLL (CriticalSectionHelper.DLL),并占用應用了備用字段的臨界區。這樣就使事情簡單了許多。現在我們的實用工具可以簡單地遍歷進程中的所有臨界區,并且只顯示具有正確填充的備用字段的臨界區信息。那么需要為這一實用工具付出什么代價呢?請稍等,還有更多的內容!
因為您的所有臨界區現在都包含對其進行初始化時的地址,實用工具可以通過提供其初始化地址來識別各個臨界區。原始代碼地址本身沒有那么有用。幸運的是,DBGHELP.DLL 使代碼地址向源文件、行號和函數名稱的轉換變得非常容易。即使一個臨界區中沒有您在其中的簽名,也可以將其地址提交給 DBGHELP.DLL。如果將其聲明為一個全局變量,并且如果符號可用,則您就可以在原始源代碼中確定臨界區的名稱。順便說明一下,如果通過設置 _NT_SYMBOL_PATH 環境變量,并設置 DbgHelp 以使用其 Symbol Server 下載功能,從而使 DbgHelp 發揮其效用,則會得到非常好的結果。
MyCriticalSections 實用工具
我們將所有這些思想結合起來,提出了 MyCriticalSections 程序。MyCriticalSections 是一個命令行程序,在不使用參數運行該程序時可以看到一些選項:
Syntax: MyCriticalSections <PID> [options]
Options:
/a = all critical sections
/e = show only entered critical sections
/v = verbose
唯一需要的參數是 Program ID 或 PID(十進制形式)。可以用多種方法獲得 PID,但最簡單的方法可能就是通過 Task Manager。在沒有其他選項時,MyCriticalSections 列出了來自代碼模塊的所有臨界區狀態,您已經將 CriticalSectionHelper.DLL 鏈接至這些代碼模塊。如果有可用于這一(些)模塊的符號,代碼將嘗試提供該臨界區的名稱,以及對其進行初始化的位置。
要查看 MyCriticalSections 是如何起作用的,請運行 Demo.EXE 程序,該程序包含在下載文件中。Demo.EXE 只是初始化兩個臨界區,并由一對線程進入這兩個臨界區。圖 3 顯示運行“MyCriticalSections 2040”的結果(其中 2040 為 Demo.EXE 的 PID)。
在該圖中,列出了兩個臨界區。在本例中,它們被命名為 csMain 和 yetAnotherCriticalSection。每個“Address:”行顯示了 CRITICAL_SECTION 的地址及其名稱。“Initialized in”行包含了在其中初始化 CRITICAL_SECTION 的函數名。代碼的“Initialized at”行顯示了源文件和初始化函數中的行號。
對于 csMain 臨界區,您將看到鎖定數為 0、遞歸數為 1,表示一個已經被一線程獲得的臨界區,并且沒有其他線程在等待該臨界區。因為從來沒有線程被阻止于該臨界區,所以 Entry Count 字段為 0。
現在來看 yetAnotherCriticalSection,會發現其遞歸數為 3。快速瀏覽 Demo 代碼可以看出:主線程調用 EnterCriticalSection 三次,所以事情的發生與預期一致。但是,還有一個第二線程試圖獲得該臨界區,并且已經被阻止。同樣,LockCount 字段也為 3。此輸出顯示有一個等待線程。
MyCriticalSections 擁有一些選項,使其對于更為勇敢的探索者非常有用。/v 開關顯示每個臨界區的更多信息。旋轉數與鎖定信號字段尤為重要。您經常會看到 NTDLL 和其他 DLL 擁有一些旋轉數非零的臨界區。如果一個線程在獲得臨界區的過程中曾被鎖定,則鎖定信號字段為非零值。/v 開關還顯示了 RTL_CRITICAL_SECTION_DEBUG 結構中備用字段的內容。
/a 開關顯示進程中的所有臨界區,即使其中沒有 CriticalSectionHelper.DLL 簽名也會顯示。如果使用 /a,則請做好有大量輸出的準備。真正的黑客希望同時使用 /a 和 /v,以顯示進程中全部內容的最多細節。使用 /a 的一個小小的好處是會看到 NTDLL 中的LdrpLoaderLock 臨界區。此臨界區在 DllMain 調用和其他一些重要時間內被占用。LdrpLoaderLock 是許多不太明顯、表面上難以解釋的死鎖的形成原因之一。(為使 MyCriticalSection 能夠正確標記 LdrpLoaderLock 實例,需要用于 NTDLL 的 PDB 文件可供使用。)
/e 開關使程序僅顯示當前被占用的臨界區。未使用 /a 開關時,只顯示代碼中被占用的臨界區(如備用字段中的簽名所指示)。采用 /a 開關時,將顯示進程中的全部被占用臨界區,而不考慮其來源。
那么,希望什么時候運行 MyCriticalSections 呢?一個很明確的時間是在程序被死鎖時。檢查被占用的臨界區,以查看是否有什么使您驚訝的事情。即使被死鎖的程序正運行于調試器的控制之下,也可以使用 MyCriticalSections。
另一種使用 MyCriticalSections 的時機是在對有大量多線程的程序進行性能調整時。在阻塞于調試器中的一個使用頻繁、非重入函數時,運行 MyCriticalSections,查看在該時刻占用了哪些臨界區。如果有很多線程都執行相同任務,就非常容易導致一種情形:一個線程的大部分時間被消耗在等待獲得一個使用頻繁的臨界區上。如果有多個使用頻繁的臨界區,這造成的后果就像花園的澆水軟管打了結一樣。解決一個爭用問題只是將問題轉移到下一個容易造成阻塞的臨界區。
一個查看哪些臨界區最容易導致爭用的好方法是在接近程序結尾處設置一個斷點。在遇到斷點時,運行 MyCriticalSections 并查找具有最大 Entry Count 值的臨界區。正是這些臨界區導致了大多數阻塞和線程轉換。
盡管 MyCriticalSections 運行于 Windows 2000 及更新版本,但您仍需要一個比較新的 DbgHelp.DLL 版本 - 5.1 版或更新版本。Windows XP 中提供這一版本。也可以由其他使用 DbgHelp 的工具中獲得該版本。例如,Debugging Tools For Windows 下載中通常擁有最新的 DbgHelp.DLL。
深入研究重要的臨界區例程
此最后一節是為那些希望理解臨界區實現內幕的勇敢讀者提供的。對 NTDLL 進行仔細研究后可以為這些例程及其支持子例程創建偽碼(見下載中的 NTDLL(CriticalSections).cpp)。以下 KERNEL32 API 組成臨界區的公共接口:
InitializeCriticalSection
InitializeCriticalSectionAndSpinCount
DeleteCriticalSection
TryEnterCriticalSection
EnterCriticalSection
LeaveCriticalSection
前兩個 API 只是分別圍繞 NTDLL API RtlInitializeCriticalSection 和 RtlInitializeCriticalSectionAndSpinCount 的瘦包裝。所有剩余例程都被提交給 NTDLL 中的函數。另外,對 RtlInitializeCriticalSection 的調用是另一個圍繞 RtlInitializeCriticalSectionAndSpinCount 調用的瘦包裝,其旋轉數的值為 0。使用臨界區的時候實際上是在幕后使用以下 NTDLL API:
RtlInitializeCriticalSectionAndSpinCount
RtlEnterCriticalSection
RtlTryEnterCriticalSection
RtlLeaveCriticalSection
RtlDeleteCriticalSection
在這一討論中,我們采用 Kernel32 名稱,因為大多數 Win32 程序員對它們更為熟悉。
InitializeCriticalSectionAndSpinCount 對臨界區的初始化非常簡單。RTL_CRITICAL_SECTION 結構中的字段被賦予其起始值。與此類似,分配 RTL_CRITICAL_SECTION_DEBUG 結構并對其進行初始化,將 RtlLogStackBackTraces 調用中的返回值賦予 CreatorBackTraceIndex,并建立到前面臨界區的鏈接。
順便說一聲,CreatorBackTraceIndex 一般接收到的值為 0。但是,如果有 Gflags 和 Umdh 實用工具,可以輸入以下命令:
Gflags /i MyProgram.exe +ust
Gflags /i MyProgram.exe /tracedb 24
這些命令使得 MyProgram 的“Image File Execution Options”下添加了注冊表項。在下一次執行 MyProgram 時會看到此字段接收到一個非 0 數值。有關更多信息,參閱知識庫文章 Q268343“Umdhtools.exe:How to Use Umdh.exe to Find Memory Leaks”。臨界區初始化中另一個需要注意的問題是:前 64 個 RTL_CRITICAL_SECTION_DEBUG 結構不是由進程堆中分配,而是來自位于 NTDLL 內的 .data 節的一個數組。
在完成臨界區的使用之后,對 DeleteCriticalSection(其命名不當,因為它只刪除 RTL_CRITICAL_SECTION_ DEBUG)的調用遍歷一個同樣可理解的路徑。如果由于線程在嘗試獲得臨界區時被阻止而創建了一個事件,將通過調用 ZwClose 來銷毀該事件。接下來,在通過 RtlCriticalSectionLock 獲得保護之后(NTDLL 以一個臨界區保護它自己的內部臨界區列表 — 您猜對了),將調試信息從鏈中清除,對該臨界區鏈表進行更新,以反映對該信息的清除操作。該內存由空值填充,并且如果其存儲區是由進程堆中獲得,則調用 RtlFreeHeap 將使得其內存被釋放。最后,以零填充 RTL_CRITICAL_SECTION。
有兩個 API 要獲得受臨界區保護的資源 — TryEnterCriticalSection 和 EnterCriticalSection。如果一個線程需要進入一個臨界區,但在等待被阻止資源變為可用的同時,可執行有用的工作,那么 TryEnterCriticalSection 正是您需要的 API。此例程測試此臨界區是否可用;如果該臨界區被占用,該代碼將返回值 FALSE,為該線程提供繼續執行另一任務的機會。否則,其作用只是相當于 EnterCriticalSection。
如果該線程在繼續進行之前確實需要擁有該資源,則使用 EnterCriticalSection。此時,取消用于多處理器計算機的 SpinCount 測試。這一例程與 TryEnterCriticalSection 類似,無論該臨界區是空閑的或已經被該線程所擁有,都調整對該臨界區的簿記。注意,最重要的 LockCount 遞增是由 x86“lock”前綴完成的,這一點非常重要。這確保了在某一時間內只有一個 CPU 可以修改該 LockCount 字段。(事實上,Win32 InterlockedIncrement API 只是一個具有相同鎖定前綴的 ADD 指令。)
如果調用線程無法立即獲得該臨界區,則調用 RtlpWaitForCriticalSection 將該線程置于等待狀態。在多處理器系統中,EnterCriticalSection 旋轉 SpinCount 所指定的次數,并在每次循環訪問中測試該臨界區的可用性。如果此臨界區在循環期間變為空閑,該線程獲得該臨界區,并繼續執行。
RtlpWaitForCriticalSection 可能是這里所給的所有過程中最為復雜、最為重要的一個。這并不值得大驚小怪,因為如果存在一個死鎖并涉及臨界區,則利用調試器進入該進程就可能顯示出 RtlpWaitForCriticalSection 內 ZwWaitForSingleObject 調用中的至少一個線程。
如偽碼中所顯示,在 RtlpWaitForCriticalSection 中有一點簿記工作,如遞增 EntryCount 和 ContentionCount 字段。但更重要的是:發出對 LockSemaphore 的等待,以及對等待結果的處理。默認情況是將一個空指針作為第三個參數傳遞給 ZwWaitForSingleObject 調用,請求該等待永遠不要超時。如果允許超時,將生成調試消息字符串,并再次開始等待。如果不能從等待中成功返回,就會產生中止該進程的錯誤。最后,在從 ZwWaitForSingleObject 調用中成功返回時,則執行從 RtlpWaitForCriticalSection 返回,該線程現在擁有該臨界區。
RtlpWaitForCriticalSection 必須認識到的一個臨界條件是該進程正在被關閉,并且正在等待加載程序鎖定 (LdrpLoaderLock) 臨界區。RtlpWaitForCriticalSection 一定不能 允許該線程被阻止,但是必須跳過該等待,并允許繼續進行關閉操作。
LeaveCriticalSection 不像 EnterCriticalSection 那樣復雜。如果在遞減 RecursionCount 之后,結果不為 0(意味著該線程仍然擁有該臨界區),則該例程將以 ERROR_SUCCESS 狀態返回。這就是為什么需要用適當數目的 Leave 調用來平衡 Enter 調用。如果該計數為 0,則 OwningThread 字段被清零,LockCount 被遞減。如果還有其他線程在等待,例如 LockCount 大于或等于 0,則調用 RtlpUnWaitCriticalSection。此幫助器例程創建 LockSemaphore(如果其尚未存在),并發出該信號提醒操作系統:該線程已經釋放該臨界區。作為通知的一部分,等待線程之一退出等待狀態,為運行做好準備。
最后要說明的一點是,MyCriticalSections 程序如何確定臨界區鏈的起始呢?如果有權訪問 NTDLL 的正確調試符號,則對該列表的查找和遍歷非常簡單。首先,定位符號 RtlCriticalSectionList,清空其內容(它指向第一個 RTL_CRITICAL_SECTION_DEBUG 結構),并開始遍歷。但是,并不是所有的系統都有調試符號,RtlCriticalSectionList 變量的地址會隨 Windows 的各個版本而發生變化。為了提供一種對所有版本都能正常工作的解決方案,我們設計了以下試探性方案。觀察啟動一個進程時所采取的步驟,會看到是以以下順序對 NTDLL 中的臨界區進行初始化的(這些名稱取自 NTDLL 的調試符號):
RtlCriticalSectionLock
DeferedCriticalSection (this is the actual spelling!)
LoaderLock
FastPebLock
RtlpCalloutEntryLock
PMCritSect
UMLogCritSect
RtlpProcessHeapsListLock
因為檢查進程環境塊 (PEB) 中偏移量 0xA0 處的地址就可以找到加載程序鎖,所以對該鏈起始位置的定位就變得比較簡單。我們讀取有關加載程序鎖的調試信息,然后沿著鏈向后遍歷兩個鏈接,使我們定位于 RtlCriticalSectionLock 項,在該點得到該鏈的第一個臨界區。有關其方法的說明,請參見圖 4。
圖 4 初始化順序
小結
幾乎所有的多線程程序均使用臨界區。您遲早都會遇到一個使代碼死鎖的臨界區,并且會難以確定是如何進入當前狀態的。如果能夠更深入地了解臨界區的工作原理,則這一情形的出現就不會像首次出現時那樣的令人沮喪。您可以研究一個看來非常含糊的臨界區,并確定是誰擁有它,以及其他有用細節。如果您愿意將我們的庫加入您的鏈接器行,則可以容易地獲得有關您程序臨界區使用的大量信息。通過利用臨界區結構中的一些未用字段,我們的代碼可以僅隔離并命名您的模塊所用的臨界區,并告知其準確狀態。
有魄力的讀者可以很容易地對我們的代碼進行擴展,以完成更為異乎尋常的工作。例如,采用與 InitializeCriticalSection 掛鉤相類似的方式截獲 EnterCriticalSection 和 LeaveCriticalSection,可以存儲最后一次成功獲得和釋放該臨界區的位置。與此類似,CritSect DLL 擁有一個易于調用的 API,用于枚舉您自己的代碼中的臨界區。利用 .NET Framework 中的 Windows 窗體,可以相對容易地創建一個 GUI 版本的 MyCriticalSections。對我們代碼進行擴展的可能性非常大,我們非常樂意看到其他人員所發現和創造的創新性辦法。
有關文章,請參閱:
Global Flag Reference:Create kernel mode stack trace database
GFlags Examples:Enlarging the User-Mode Stack Trace Database
Under the Hood:Reduce EXE and DLL Size with LIBCTINY.LIB
Matt Pietrek 是一位軟件架構師和作者。他就職于 Compuware/NuMega 實驗室,身份為 BoundsChecker 和“分布式分析器”產品的首席架構師。他已經創作了三本有關 Windows 系統編程的書籍,并是 MSDN Magazine 的特約編輯。他的 Web 站點 (http://www.wheaty.net) 有關于以前文章和專欄的 FAQ 和信息。
Jay Hilyard 是 Compuware/NuMega 實驗室的 BoundsChecker 小組的軟件工程師。他、他的妻子和他們的貓是新罕布什爾州的新居民。他的聯系方式為 RussOsterlund@adelphia.net 或 Web 站點 http://www.smidgeonsoft.com。
轉到原英文頁面
2009年1月8日
#
HOW TO: 查找問題的異常堆棧時出現的 UnhandledExceptionFilter 調用堆棧跟蹤中
http://support.microsoft.com/kb/313109/zh-cn
察看本文應用于的產品
本頁
展開全部 | 關閉全部
概要
沒有異常處理程序定義處理引發的異常時,將調用該 UnhandledExceptionFilter 函數。 通常,該函數會將異常傳遞給在 Ntdll.dll 為文...
沒有異常處理程序定義處理引發的異常時,將調用該 UnhandledExceptionFilter 函數。 通常,該函數會將異常傳遞給在 Ntdll.dll 為文件其中捕獲,并嘗試處理設置。
在進程的內存快照所在某些情況下,可以看到鎖定點保存到線程的線程的調用 UnhandledExceptionFilter 函數。 在這的種情況下您可以按照本文以確定導致此異常的 DLL。
回到頂端
使用 Windbg.exe 打開轉儲文件
- 下載并安裝調試程序。 要下載調試程序,請訪問下面的 Microsoft 網站:
Microsoft 調試工具
http://www.microsoft.com/whdc/devtools/ddk/default.mspx (http://www.microsoft.com/whdc/devtools/ddk/default.mspx)
- 打開安裝調試程序,文件夾,然后雙擊 Windbg.exe 啟動調試器。
- 在 文件 菜單上單擊 打開的崩潰轉儲 (或按 Ctrl+D),然后選擇要查看該轉儲文件。
回到頂端
使用 Windbg.exe 確定異常堆棧
- 在 Windbg.exe,打開進程的.dmp 文件。
- 請確保您符號路徑指向正確的位置。 有關如何執行此操作,請訪問下面的 Microsoft Web 站點:
如何獲得符號
http://www.microsoft.com/whdc/devtools/ddk/default.mspx (http://www.microsoft.com/whdc/devtools/ddk/default.mspx)
- 在命令提示符下鍵入 ~ * kb 以列出所有進程中的線程。
- 標識對函數調用的線程 Kernel32! UnhandledExceptionFilter 。 它類似于以下:
120 id: f0f0f0f0.a1c Suspend: 1 Teb 7ff72000 Unfrozen
ChildEBP RetAddr Args to Child
09a8f334 77eb9b46 0000244c 00000001 00000000 ntdll!ZwWaitForSingleObject+0xb [i386\usrstubs.asm @ 2004]
09a8f644 77ea7e7a 09a8f66c 77e861ae 09a8f674 KERNEL32!UnhandledExceptionFilter+0x2b5
[D:\nt\private\windows\base\client\thread.c @ 1753]
09a8ffec 00000000 787bf0b8 0216fe94 00000000 KERNEL32!BaseThreadStart+0x65 [D:\nt\private\windows\base\client\support.c @ 453]
- 切換到該線程 (在本例中,該線程是"~120s")。
- 在第一個參數的指定位置顯示內存內容 Kernel32! UnhandledExceptionFilter 通過 添加 第一個參數 。 此指向 EXCEPTION_POINTERS 結構
0:120> dd 09a8f66c
09a8f66c 09a8f738 09a8f754 09a8f698 77f8f45c
09a8f67c 09a8f738 09a8ffdc 09a8f754 09a8f710
09a8f68c 09a8ffdc 77f8f5b5 09a8ffdc 09a8f720
09a8f69c 77f8f3fa 09a8f738 09a8ffdc 09a8f754
09a8f6ac 09a8f710 77e8615b 09a8fad4 00000000
09a8f6bc 09a8f738 74a25336 09a8f6e0 09a8f910
09a8f6cc 01dc8ad8 0d788918 00000001 018d1f28
09a8f6dc 00000001 61746164 7073612e 09a8f71c
- 第一個 DWORD 代表異常記錄。 若要獲取有關異常的類型信息,請請在命令提示符處運行以下:
.exr first DWORD from step 6
0:120> .exr 09a8f738
ExceptionAddress: 78011f32 (MSVCRT!strnicmp+0x00000092)
ExceptionCode: c0000005
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000000
Parameter[1]: 00000000
Attempt to read from address 00000000
- 第二個 DWORD 是上下文記錄。 若要獲取上下文的信息,請在命令提示符處運行以下:
.cxr second DWORD from step 6
0:120> .cxr 09a8f754
eax=027470ff ebx=7803cb28 ecx=00000000 edx=00000000 esi=00000000 edi=09a8fad4
eip=78011f32 esp=09a8fa20 ebp=09a8fa2c iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286
MSVCRT!strnicmp+92:
78011f32 8a06 mov al,[esi]
- 運行 kv 命令獲得實際的異常的調用堆棧。 這有助于您識別可能未被處理正確的過程中實際問題
0:120> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
09a8fa2c 780119ab 09a8fad4 00000000 09a8faa8 MSVCRT!strnicmp+0x92
09a8fa40 7801197c 09a8fad4 00000000 6d7044fd MSVCRT!stricmp+0x3c
09a8fa80 6e5a6ef6 09a8fad4 2193d68d 00e5e298 MSVCRT!stricmp+0xd
09a8fa94 6d7043bf 09a8fad4 09a8faa8 0000001c IisRTL!CLKRHashTable::FindKey+0x59 (FPO: [2,0,1])
09a8faac 749fc22d 09a8fad4 01d553b0 0000001c ISATQ!CDirMonitor::FindEntry+0x1e
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\infocomm\atq\dirmon.cpp @ 884]
09a8fac4 749fd1cb 09a8fad4 09a8fb10 525c3a46 asp!RegisterASPDirMonitorEntry+0x6e
(FPO: [EBP 0x09a8fb08] [2,0,4]) [D:\nt\private\inet\iis\svcs\cmp\asp\aspdmon.cpp @ 534]
09a8fb08 749fcdd6 00000000 09a8fcbc 018d1f28 asp!CTemplateCacheManager::RegisterTemplateForChangeNotification+0x8a
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\cmp\asp\cachemgr.cpp @ 621]
09a8fb3c 74a08bfe 00000000 000000fa 74a30958 asp!CTemplateCacheManager::Load+0x382
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\cmp\asp\cachemgr.cpp @ 364]
09a8fc68 74a0d4c9 04c12518 018d1f28 09a8fcbc asp!LoadTemplate+0x42
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\cmp\asp\exec.cpp @ 1037]
09a8fcc0 74a2c3e5 00000000 0637ee38 09a8fd58 asp!CHitObj::ViperAsyncCallback+0x3e8
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\cmp\asp\hitobj.cpp @ 2414]
09a8fcd8 787c048a 00000000 77aa1b03 01e91ed8 asp!CViperAsyncRequest::OnCall+0x3f
(FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\cmp\asp\viperint.cpp @ 194]
09a8fce0 77aa1b03 01e91ed8 77a536d8 00000000 COMSVCS!STAActivityWorkHelper+0xa
(FPO: [1,0,0])
09a8fd24 77aa1927 000752f8 000864dc 787c0480 ole32!EnterForCallback+0x6a
(FPO: [Non-Fpo]) [D:\nt\private\ole32\com\dcomrem\crossctx.cxx @ 1759]
09a8fe50 77aa17ea 000864dc 787c0480 01e91ed8 ole32!SwitchForCallback+0x12b
(FPO: [Non-Fpo]) [D:\nt\private\ole32\com\dcomrem\crossctx.cxx @ 1644]
09a8fe78 77aa60c1 000864dc 787c0480 01e91ed8 ole32!PerformCallback+0x50
(FPO: [Non-Fpo]) [D:\nt\private\ole32\com\dcomrem\crossctx.cxx @ 1559]
09a8fed4 77aa5fa6 04f2b4c0 787c0480 01e91ed8 ole32!CObjectContext::InternalContextCallback+0xf5
(FPO: [Non-Fpo]) [D:\nt\private\ole32\com\dcomrem\context.cxx @ 3866]
09a8fef4 787bd3c3 04f2b4c0 787c0480 01e91ed8 ole32!CObjectContext::DoCallback+0x1a
(FPO: [Non-Fpo]) [D:\nt\private\ole32\com\dcomrem\context.cxx @ 3746]
09a8ff24 787bf373 0216fb3c 00000007 09a8ffec COMSVCS!STAActivityWork::DoWork+0x73
(FPO: [0,4,2])
09a8ffb4 77e8758a 0216fe94 0216fb3c 00000007 COMSVCS!STAThread::STAThreadWorker+0x2bb
(FPO: [EBP 0x09a8ffec] [1,31,4])
09a8ffec 00000000 787bf0b8 0216fe94 00000000 KERNEL32!BaseThreadStart+0x52
(FPO: [Non-Fpo]) [D:\nt\private\windows\base\client\support.c @ 451]
2008年12月25日
#
雖然網上關于boost編譯問題的文章一堆一堆的,但是到我使用的時候怎么就不行呢,我的編譯器是vc7.1,本來不想用boost,只是為了編譯asio的例子,引用了
#include <boost/date_time/posix_time/posix_time.hpp>
這個頭文件,就招來了一堆的問題
提示鏈接錯誤,
文件libboost_date_time-vc71-mt-sgd-1_37.lib找不到
然后就開始了漫長的boost庫編譯。
但是編譯完了,提示還是找不到,我暈。查了查sgd的意思
s:代表static
gd:代表調試版
網上的說法我照著做了,根本就不行,不知道是筆誤呢,還是bjam版本已經更新了。只好根據錯誤提示,在boost的Jam文件中來揣測編譯開關
在X:\boost_1_37_0\tools\build\v2\build目錄中的build-request.jam文件的248行有
feature toolset : gcc msvc borland : implicit ;等內容
哦,這個大概就是編譯開關了
下面是編譯出靜態鏈接的release版本
bjam toolset=msvc-7.1 runtime-link=static stage
下面是編譯出靜態鏈接的debug版本
bjam toolset=msvc-7.1 debug runtime-link=static stage
這個stage開關呢,可能就是把編譯出來的文件復制到目錄
X:\boost_1_37_0\stage\lib
其它的開關
rtti:on off
optimization: on off
等等,
2008年12月18日
#
最近在開發一個ActiveX視頻控件,需要有全屏功能,因為用到好幾層窗口,在全屏的時候費了很多周折,最后瞎湊總算湊好了,寫下來與大家共享。
讓應用程序全屏顯示其實思路很簡單:
1.先保存要全屏的窗口的父窗口
2.如果要全屏的窗口不是子窗口,設置其風格為WS_CHILD
3.設置窗口的父窗口為桌面(::GetDesktopWindow())
4.移動窗口至全屏,并將窗口設為總在最上HWND_TOPMOST
m_videoMgr是我控件里創建的視頻窗口,它的父窗口是控件窗口(CameraControl)控件本身的窗口不直接顯示,被這個m_videoMgr窗口完全覆蓋。在全屏的時候,如果直接更改CameraControl的父窗口,它的子窗口m_videoMgr窗口總是不能正確的設置為全屏,可能在控件測試容器里正常,但是到了IE里就不正常了。于是我改為更改m_videoMgr窗口的父窗口,保留控件本身窗口,但是改變窗口大小的時候,改變父窗口的大小,也許m_videoMgr窗口可能和控件本身窗口的大小有關聯,這樣成功的進行了全屏,而且不管是在IE和控件測試容器里都可以正常的全屏和恢復。
if(bFullScreen)
{
//獲取控件窗口的絕對位置
GetWindowRect(m_rcCtrlRect);
::SetParent(m_videoMgr.GetSafeHwnd(),::GetDesktopWindow());
int cx = ::GetSystemMetrics(SM_CXSCREEN);
int cy = ::GetSystemMetrics(SM_CYSCREEN);
MoveWindow(0, 0, cx, cy);
::SetWindowPos(m_videoMgr.GetSafeHwnd(),HWND_TOPMOST,0,0,cx,cy,SWP_FRAMECHANGED|SWP_DEFERERASE);
m_bFullScreen = TRUE;
}
else
{
::SetParent(m_videoMgr.GetSafeHwnd(),m_hWnd);
//MoveWindow使用相對窗口坐標,所以必須先轉化為相對坐標
CPoint LeftTop(m_rcCtrlRect.TopLeft());
CPoint BottomRight(m_rcCtrlRect.BottomRight());
::ScreenToClient(m_hWnd,&LeftTop);
::ScreenToClient(m_hWnd,&BottomRight);
::MoveWindow(m_hWnd,LeftTop.x,LeftTop.y,m_rcCtrlRect.Width(),m_rcCtrlRect.Height(),TRUE);
::SetWindowPos(m_videoMgr.GetSafeHwnd(),HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
m_bFullScreen = FALSE;
}
解決了上面的問題之后,又發現了一個新的問題,就是全屏以后想使用Esc鍵退出全屏時,根本不響應鍵盤消息,后來發現MFC也有控件不處理鍵盤消息的問題,想想可能跟控件的焦點有關系,于是在FullScreen的之前,加一個SetFocus()
if(FullScreen&&!IsFullScreen())
{
SetFocus();
OnFullScreen(TRUE);
}
然后發現鍵盤消息處理正常了,問題解決。