jans2002的博客

专注 专心 专业

2010年1月25日 #

弱智的CMake安装程序,把我的Path环境变量全给冲了

CMake号称是很轻大跨平台的编译工具,于是想试用一下,没想到其Windows下的安装程序如此弱智,把我的环境变量配置的Path路径全给冲了,这样的垃圾工具还是少用为妙。既然号称跨平台,连Windows都支持的这么差,也不支持中文,想必其他平台的也好不到那里去。还是老老实实的写我的Makefile吧。

posted @ 2010-01-25 09:27 jans2002 阅读(1161) | 评论 (2)编辑 收藏

2009年10月14日 #

(转)怎么获取dll函数的参数类型

可以通过反汇编来知道接口函数的参数,建议使用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指令      
  来实现参数的传递的。这时可以看一下具体是什么东西被压入堆栈了,一般来说,如果参数是整数,一看就可以知道了,      
  如果是字符串的话也是比较简单的,只要到那个地址上面去看一下就可以了。      
  如果传递的结构的话,没有很方便的办法解决,就是读懂该汇编就可以了。对于以上的分析,本人只其到了抛砖引玉,      
  希望对大家有点用处。

posted @ 2009-10-14 08:37 jans2002 阅读(1365) | 评论 (0)编辑 收藏

2009年9月29日 #

(转)一个小技巧:SFP Overwrite(以c语言为实例)


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.名称:  sfptut3.jpg
查看次数: 22
文件大小:  11.3 KB
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
名称:  sfptut1.jpg
查看次数: 23
文件大小:  14.8 KB
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. 名称:  sfptut2.jpg
查看次数: 21
文件大小:  10.9 KB
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.
名称:  sfptut4.jpg
查看次数: 21
文件大小:  11.1 KB
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.

posted @ 2009-09-29 09:09 jans2002 阅读(337) | 评论 (0)编辑 收藏

2009年8月14日 #

菜鸟对AspectC++ Add-In for Microsoft® Visual C++®插件的破解过程

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了,这是改过的了)
保存到文件。
大功告成哈!!

身为菜鸟,能取得这样的结果,真是让人兴奋,谢谢各位阅读,还请各位高手不吝赐教。
下载安装程序及破解

posted @ 2009-08-14 17:59 jans2002 阅读(1405) | 评论 (2)编辑 收藏

UnDName工具,显示函数修饰名称工具

在一个使用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,修改,重新编译,问题解决。

posted @ 2009-08-14 17:51 jans2002 阅读(2876) | 评论 (0)编辑 收藏

2009年4月5日 #

简单的makefile例子

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

posted @ 2009-04-05 09:35 jans2002 阅读(434) | 评论 (0)编辑 收藏

2009年2月20日 #

解开 Windows 下的临界区中的代码死锁

解开 Windows 下的临界区中的代码死锁

Matt PietrekRuss 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 和事件链中其他参与者之间的关系。

fig01

图 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

fig04

图 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

转到原英文页面

posted @ 2009-02-20 10:03 jans2002 阅读(382) | 评论 (0)编辑 收藏

2009年1月8日 #

如何根据程序崩溃时的DMP文件使用WinDbg查找调用堆栈

 

HOW TO: 查找问题的异常堆栈时出现的 UnhandledExceptionFilter 调用堆栈跟踪中

http://support.microsoft.com/kb/313109/zh-cn

察看本文应用于的产品

本页

展开全部 | 关闭全部

概要

没有异常处理程序定义处理引发的异常时,将调用该 UnhandledExceptionFilter 函数。 通常,该函数会将异常传递给在 Ntdll.dll 为文...

没有异常处理程序定义处理引发的异常时,将调用该 UnhandledExceptionFilter 函数。 通常,该函数会将异常传递给在 Ntdll.dll 为文件其中捕获,并尝试处理设置。
在进程的内存快照所在某些情况下,可以看到锁定点保存到线程的线程的调用 UnhandledExceptionFilter 函数。 在这的种情况下您可以按照本文以确定导致此异常的 DLL。

回到顶端

使用 Windbg.exe 打开转储文件
  1. 下载并安装调试程序。 要下载调试程序,请访问下面的 Microsoft 网站:

    Microsoft 调试工具
    http://www.microsoft.com/whdc/devtools/ddk/default.mspx (http://www.microsoft.com/whdc/devtools/ddk/default.mspx)

  2. 打开安装调试程序,文件夹,然后双击 Windbg.exe 启动调试器。
  3. 文件 菜单上单击 打开的崩溃转储 (或按 Ctrl+D),然后选择要查看该转储文件。

回到顶端

使用 Windbg.exe 确定异常堆栈
  1. 在 Windbg.exe,打开进程的.dmp 文件。
  2. 请确保您符号路径指向正确的位置。 有关如何执行此操作,请访问下面的 Microsoft Web 站点:

    如何获得符号
    http://www.microsoft.com/whdc/devtools/ddk/default.mspx (http://www.microsoft.com/whdc/devtools/ddk/default.mspx)

  3. 在命令提示符下键入 ~ * kb 以列出所有进程中的线程。
  4. 标识对函数调用的线程 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]
    					
  5. 切换到该线程 (在本例中,该线程是"~120s")。
  6. 在第一个参数的指定位置显示内存内容 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 
    					
  7. 第一个 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 
    					
  8. 第二个 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] 
    					
  9. 运行 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]
    					

posted @ 2009-01-08 15:53 jans2002 阅读(10158) | 评论 (0)编辑 收藏

2008年12月25日 #

编译boost 1.37遇到的问题

虽然网上关于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

等等,

posted @ 2008-12-25 10:08 jans2002 阅读(784) | 评论 (1)编辑 收藏

2008年12月18日 #

复合窗口的ActiveX控件全屏及键盘消息处理问题

最近在开发一个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);		
	}
然后发现键盘消息处理正常了,问题解决。
 

posted @ 2008-12-18 10:29 jans2002 阅读(3102) | 评论 (3)编辑 收藏

仅列出标题  下一页