由于工作需要,还在用vs2005
这个老古董,虽然很不喜欢。
虽然很轻,但有两个原因不喜欢:
调试总要加载符号,不让加非加,慢的无语
时不时总是无缘无故无法启动
无法启动这个事已经无数次出现了,重装,重启,屏蔽Assist均是无效。
后来无意间点击了C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE\devenv.com
,可以启动了。
但这次这个方法也不行了,实在是忍无可忍。
决定干它。
上调试器,启动devenv.exe
。
看到崩溃原因 c00000fd
,就是栈溢出,看来应该是函数调用无限循环了。
不要问我为啥知道,因为之前遇到过。
看看栈,果然如此,user32->msdev!xxx->user32->msdev!xxx->...
0:000:x86> kn 10
# ChildEBP RetAddr
00 000a3048 76cb9cae ntdll_77660000!RtlActivateActivationContextUnsafeFast+0x18
01 000a3118 76cb9577 USER32!UserCallWinProcCheckWow+0x14e
02 000a3150 76cb771b USER32!CallWindowProcAorW+0x7f
03 000a3168 059dea67 USER32!CallWindowProcW+0x1b //5007ea67
04 000a3188 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x29
05 000a31a4 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a
06 000a31d0 76cb9e9a USER32!_InternalCallWinProc+0x2b
07 000a32b4 76cb9577 USER32!UserCallWinProcCheckWow+0x33a
08 000a32ec 76cb771b USER32!CallWindowProcAorW+0x7f
09 000a3304 059dea67 USER32!CallWindowProcW+0x1b
0a 000a3324 059dea34 msenv!CAutoCompletionManagerCL::TargetSubclassProc+0x29
0b 000a3340 76cc2edb msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC+0x2a
0c 000a336c 76cb9e9a USER32!_InternalCallWinProc+0x2b
0d 000a3450 76cb9577 USER32!UserCallWinProcCheckWow+0x33a
0e 000a3488 76cb771b USER32!CallWindowProcAorW+0x7f
0f 000a34a0 059dea67 USER32!CallWindowProcW+0x1b
...
打开IDA
,简单看看msdev
。
int __userpurge CAutoCompletionManagerCL::TargetSubclassProcSTATIC@<eax>(int a1@<esi>, HWND a2, unsigned int a3, unsigned int a4, unsigned int a5)
{
CAutoCompletionManagerCL *v5; // ecx
int result; // eax
if ( GetPropW(a2, L"MSENV_ACMgr") )
result = CAutoCompletionManagerCL::TargetSubclassProc(v5, a2, a4, a5, a1);
else
result = DefWindowProcAW(a2, a3, a4, a5);
return result;
}
text:5007EA3A 000 push ebp
.text:5007EA3B 004 mov ebp, esp
.text:5007EA3D 004 cmp ebx, 0Eh
.text:5007EA40 004 push edi
.text:5007EA41 008 jnz loc_500FEDCF
.text:5007EA47 008 mov dword ptr [esi+34h], 1
.text:5007EA4E
.text:5007EA4E loc_5007EA4E: ; CODE XREF: CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+803C6↓j
.text:5007EA4E ; CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+17B9CA↓j ...
.text:5007EA4E 008 mov eax, [esi+30h] //=0 不会再调用CallWindowProcAW
.text:5007EA51 008 xor edi, edi
.text:5007EA53 008 test eax, eax
.text:5007EA55 008 jz short loc_5007EA69
.text:5007EA57 008 push [ebp+arg_8]
.text:5007EA5A 00C push [ebp+arg_4]
.text:5007EA5D 010 push ebx
.text:5007EA5E 014 push [ebp+arg_0]
.text:5007EA61 018 push eax //调用原始函数
.text:5007EA62 01C call ?CallWindowProcAW@@YGJP6GJPAUHWND__@@IIJ@Z0IIJ@Z ; CallWindowProcAW(long (*)(HWND__ *,uint,uint,long),HWND__ *,uint,uint,long)
.text:5007EA67 008 mov edi, eax
.text:5007EA69
.text:5007EA69 loc_5007EA69: ; CODE XREF: CAutoCompletionManagerCL::TargetSubclassProc(HWND__ *,uint,uint,long)+1B↑j
.text:5007EA69 008 cmp ebx, 82h
.text:5007EA6F 008 jz loc_501B3877
.text:5007EA75
CAutoCompletionManagerCL::TargetSubclassProcSTATIC
应该是个wndproc
,本来逻辑应该是在内部CallWindowProcAW
调用原始wndproc
,但是CallWindowProcAW
又调用了CAutoCompletionManagerCL::TargetSubclassProcSTATIC
,这样死循环,一直到栈移除。
看看CAutoCompletionManagerCL::TargetSubclassProcSTATIC
是哪里设置的,找到了:
int __userpurge CAutoCompletionManagerCL::AttachToCombo@<eax>(CAutoCompletionManagerCL *this@<ecx>, int a2@<eax>, struct IMsoControl *a3, HWND a4)
{
result = CAutoCompletionManager::OnCreate(this); //创建控件
if ( result >= 0 && !result )
{
v11 = (HWND)*((_DWORD *)this + 10);
if ( v6 )
v7 = SetWindowLongW(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);
else
v7 = SetWindowLongA(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);
v13 = (HWND)*((_DWORD *)this + 10);
*((_DWORD *)this + 12) = v7; //+30 = 1
所以问题应该基本清晰了,SetWindowLongW(v11, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);
被重复设置了,*((_DWORD *)this + 12) = v7
,保存的值被覆盖成了CAutoCompletionManagerCL::TargetSubclassProcSTATIC
所以这样死循环,导致调用栈溢出
第一次进入msenv!CAutoCompletionManagerCL::AttachToCombo
设置SetWindowLongW
返回就已经是msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC
赋值之前看到原始地址:
msenv!CAutoCompletionManagerCL::AttachToCombo+0x60:
05910cb5 894330 mov dword ptr [ebx+30h],eax ds:002b:06e03228={msenv!_FakeEditProc (0596eab8)}
猜测CAutoCompletionManager::OnCreate
中就已经调用SetWindowLongW了
。
尝试使用条件断点看看能不能找到。
0:000:x86> u CAutoCompletionManagerCL::TargetSubclassProcSTATIC
msenv!CAutoCompletionManagerCL::TargetSubclassProcSTATIC:
05aeea06 55 push ebp
0:000:x86> bc 1
0:000:x86> bp USER32!SetWindowLongW ".if(poi(esp+c)==05aeea06 ){}.else{g};"
断下后,确认函数没问题,看看调用栈。
可以看到msenv!CAutoCompletionManagerCL::VerifyAttachment
在OnCreate
内部设置了一次。
USER32!SetWindowLongW:
76cb7730 8bff mov edi,edi
0:000:x86> dd esp
00194a2c 05c6a3cc 00093096 fffffffc 05aeea06
00194a3c 07747120 00000000 05aeec46 00000000
0:000:x86> kn
# ChildEBP RetAddr
00 00194a28 05c6a3cc USER32!SetWindowLongW
01 00194a40 05aeec46 msenv!CAutoCompletionManagerCL::VerifyAttachment+0x69
02 00194a50 05aee7ca msenv!CAutoCompletionManagerCL::VerifyAllAttachments+0x31
03 00194a70 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x3f
04 00194abc 05ae1ceb msenv!CMsoDropdownUser::GetText+0x12f
...
18 001961b0 76cc2edb msenv!TBWndWindowProc+0x65
19 001961dc 76cb9e9a USER32!_InternalCallWinProc+0x2b
1a 001962c0 76cb9a9a USER32!UserCallWinProcCheckWow+0x33a
...
49 00198424 05a90d54 msenv!CVsAutoCompletion::InitUI+0x251
4a 00198440 05a90c6b msenv!CAutoCompletionManager::OnCreate+0x58 //这里是创建
4b 00198448 05a90ae1 msenv!CAutoCompletionManagerCL::AttachToCombo+0x12
4c 00198468 05ae2021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x95
4d 001984b0 05a90a72 msenv!CMsoDropdownUser::GetText+0x12f
4e 001984c8 05a9096b msenv!TBCDD::ResetEditText+0x12
4f 0019c6d8 05b018aa msenv!TBCDD::FDraw+0x684
50 0019c84c 05b013cc msenv!TB::FDraw+0xbb7
51 0019c8c8 76cc2edb msenv!TBWndProc+0xfa
52 0019c8f4 76cb9e9a USER32!_InternalCallWinProc+0x2b
int __usercall CAutoCompletionManagerCL::VerifyAttachment@<eax>(int a1@<esi>)
{
v7 = *(HWND *)(a1 + 40);
if ( v4 )
v5 = SetWindowLongW(v7, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);
else
v5 = SetWindowLongA(v7, -4, (LONG)CAutoCompletionManagerCL::TargetSubclassProcSTATIC);
*(_DWORD *)(a1 + 48) = v5;
}
}
所以应该是msevn
逻辑出现了问题,应该加上控件创建成功后,才能SetWindowsLong
,或者直接在SetWindowLong
之前判断是否已经设置过。
我可以给他改改代码,但是挺麻烦的。
再看看是不是有什么可以控制的条件,有更简单的修改方法。
看看CAutoCompletionManagerCL::AttachToCombo
被谁调用了:
Breakpoint 0 hit
msenv!CAutoCompletionManagerCL::AttachToCombo:
03a10c59 53 push ebx
0:000:x86> kn
# ChildEBP RetAddr
00 00198448 03a10ae1 msenv!CAutoCompletionManagerCL::AttachToCombo
01 00198468 03a62021 msenv!CAutoCompletionManagerCL::UseAutocompletion+0x95
02 001984b0 03a10a72 msenv!CMsoDropdownUser::GetText+0x12f
03 001984c8 03a1096b msenv!TBCDD::ResetEditText+0x12
04 0019c6d8 03a818aa msenv!TBCDD::FDraw+0x684
05 0019c84c 03a813cc msenv!TB::FDraw+0xbb7
06 0019c8c8 76cc2edb msenv!TBWndProc+0xfa
看看CAutoCompletionManagerCL::UseAutocompletion
的逻辑:
int __userpurge CAutoCompletionManagerCL::UseAutocompletion@<eax>(CVSShellMenu *a1@<ecx>, struct IMsoControl *a2@<edi>, struct IMsoControl *a3)
{
v6 = 0;
if ( !CAutoCompletionManagerCL::ms_picLastUsed )
CAutoCompletionManagerCL::ms_picLastUsed = a3;
if ( CCmdWindow::ms_fEnableAutocompletion ) //这个状态为0,应该就不会出现了
{
v7 = CVSShellMenu::GetHwndByPic(a1, a2);
v10 = (struct IMsoControl *)v7;
if ( !v7 )
return 1;
CAutoCompletionManagerCL::VerifyAllAttachments(a3); //重复设置的点
if ( CAutoCompletionManagerCL::GetManager(a3, v7) )
return v6;
if ( CAutoCompletionManagerCL::GetManagerByTargetHwnd(v7) )
return 1;
v6 = ATL::CComObject<CAutoCompletionManagerCL>::CreateInstance(&hData);
if ( v6 >= 0 )
{
x = hData;
(*(void (__stdcall **)(HANDLE))(*(_DWORD *)hData + 4))(hData);
v4 = dword_5074D4FC;
if ( safe::CEnvArray<CAutoCompletionManagerCL *,CAutoCompletionManagerCL *>::SetSize() )
{
*((_DWORD *)CAutoCompletionManagerCL::ms_rgManagers + v4) = x;
v6 = CAutoCompletionManagerCL::AttachToCombo(x, (int)a3, v10, v9); //初始化
if ( v6 )
CAutoCompletionManagerCL::OnDestroy(v5);
}
else
{
v6 = -2147024882;
}
}
}
return v6;
}
猜测跟代码自动完成有关,这个CCmdWindow::ms_fEnableAutocompletion
变量好像可以弄弄,如果设置为假,就完全不会进入后面的逻辑。
现在看看CCmdWindow::ms_fEnableAutocompletion
在哪里设置的。
void __stdcall PrefInitPart2()
{
if ( GetUserOption(0, L"CommandWindowAutocompletion", Data, 4u, (unsigned int)v72) < 0 )
*(_DWORD *)Data = 1; //没配置默认为1
v32 = 0;
hKey = *(HKEY *)Data;
CCmdWindow::ms_fEnableAutocompletion = *(_DWORD *)Data; //设置状态
好像有戏,CommandWindowAutocompletion
是用户配置相关的。CommandWindowAutocompletion
未配置默认开启ms_fEnableAutocompletion
。
int __userpurge GetUserOption@<eax>(int a1@<eax>, LPCWSTR lpValueName, LPBYTE lpData, DWORD cbData, unsigned int a5)
{
hKey = 0;
if ( a1 )
VBRegOpenTwoKeysFromRoot(
HKEY_CURRENT_USER,
(const unsigned __int16 *)a1,
L"General",
(HKEY)&hKey,
v7,
v10,
(unsigned int)v11,
(HKEY *)hKey);
else
VBRegOpenKeyFromRoot(&hKey, HKEY_CURRENT_USER, L"General", (HKEY)0x20019, v7, (unsigned int)v10, v11);//a1=0
v5 = HrFromRegError(v8);
if ( v5 >= 0 )
{
RegQueryValueExW(hKey, lpValueName, 0, 0, lpData, &cbData);
v5 = HrFromRegError(v9);
if ( hKey )
RegCloseKey(hKey);
}
return v5;
}
int __userpurge VBRegOpenTwoKeysFromRoot@<eax>(HKEY a1@<ebx>, const unsigned __int16 *a2@<edi>, const unsigned __int16 *a3@<esi>, HKEY a4, const unsigned __int16 *a5, const unsigned __int16 *a6, unsigned int a7, HKEY *a8)
{
if ( !g_pMainParam || (v7 = (const wchar_t *)*((_DWORD *)g_pMainParam + 31)) == 0 || !*v7 )
v7 = L"Software\\Microsoft\\VisualStudio\\8.0";
}
0:000:x86> p
msenv!VBRegOpenKeyFromRoot+0xed:
05a54d9d ff1550109205 call dword ptr [msenv!_imp__RegOpenKeyExW (05921050)] ds:002b:05921050={AcLayers!NS_VirtualRegistry::APIHook_RegOpenKeyExW (68d83ce0)}
0:000:x86> dd esp
0019d300 80000001 0019d31c 00000000 00020019
0019d310 0019d314 0019d364 000001e4 006f0053
0019d320 00740066 00610077 00650072 004d005c
0019d330 00630069 006f0072 006f0073 00740066
0019d340 0056005c 00730069 00610075 0053006c
0019d350 00750074 00690064 005c006f 002e0038
0019d360 005c0030 00650047 0065006e 00610072
0019d370 0000006c 007c0cf0 baadf00d 007ed3e0
0:000:x86> du 0019d31c
0019d31c "Software\Microsoft\VisualStudio\"
0019d35c "8.0\General"
通过调试确认,最终找到配置位置:
计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\8.0\General
CommandWindowAutocompletion = 0;REG_DWORD
增加注册表CommandWindowAutocompletion
,设置为0,这样ms_fEnableAutocompletion
就是0,问题解决。
看名字这个有点像代码自动完成功能的,但不知道ms_fEnableAutocompletion
配置为0之后会不会影响该功能。
哈哈,经过验证没有影响,终于又可以正常使用这个老古董了。
(完)