65.9K
CodeProject 正在变化。 阅读更多。
Home

dbgfix:在以 Alternate Credentials 运行 VS7 进行 ASP.NET 调试时,用于正确启动 IE 的插件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.75/5 (8投票s)

2003年4月17日

CPOL

4分钟阅读

viewsIcon

111834

downloadIcon

267

此插件可在以 Alternate Credentials 运行 VS7 进行 ASP.NET 调试时,正确启动 IE。

引言

通常,我以普通非管理员用户身份登录我的机器。为了调试 ASP.NET 应用程序,需要成为 administrators 组的成员。因此,我需要以本地管理员身份启动 VS.NET。大部分情况运行良好,但存在一个恼人的问题:当我开始调试 Web 应用程序时,Internet Explorer 没有出现,所以我必须单独启动 Internet Explorer。

我编写了一个小宏,它处理 IDE 事件并在调试开始时启动 Internet Explorer。此解决方案唯一的问题是,在调试期间同时运行两个 ASP.NET 会话,单步执行代码会变得混乱(就像调试多线程应用程序时一样)。同时运行两个会话的事实表明 VS 确实启动了 IE。查看进程列表表明有一个额外的 IE 实例。

SessView

为什么 IE 主窗口不可见?使用 Keith BrownSessView 工具表明,不可见的 IE 是在一个单独的非交互式窗口站中启动的。在基于 Windows NT 的操作系统中,每个窗口都属于一个窗口站。只有一个名为 winsta0 的窗口站是交互式的。属于此窗口站的窗口可以与用户进行交互。父进程可以通过 CreateProcess 函数调用将窗口站分配给进程。CreateProcess 的第 9 个参数是 STARTUPINFO 类型。STARTUPINFO 有一个名为 lpDesktop 的参数,它指定分配给进程的窗口站(和桌面)的名称。工作原理如下:

  1. 如果 lpDesktopNULL,新创建的进程将继承调用进程的窗口站。
  2. 如果 lpDestop 是一个空字符串,系统会根据新创建进程的登录会话 ID 为该进程分配一个唯一的窗口站。该字符串的格式为 Service-0xX-XXX$
  3. 否则,系统将使用 lpDesktop 中提供的窗口站名称。

来自 SessView 的所有证据都表明,在从 VS.NET 启动 IE 时传递给 CreateProcesslpDestop 参数是一个空字符串。因此,一个简单的修复方法是拦截 devenv 进程上下文内的 CreateProcess 调用并修改此参数。

Detours

来自 Microsoft Research 的 Detours 库是我进行 API 拦截的首选。使用 Detours 可以大大简化 API 拦截。我建议每个人都看看这个库的源代码和文档。Detours 的工作原理是更改实际 API 开始处的处理器指令,以调用拦截器函数或 Detour。拦截器函数可以在需要时使用实际指令调用真实函数。唯一 remaining 的问题是将一些代码注入 devnev 进程。最简单的方法是编写一个 VS 插件。该插件可以使用 Detours 库来拦截 CreateProcess 调用。

插件

我给这个插件命名为 dbgfix。源代码中包含两个解决方案和两个项目。dbgfix2003.sln 适用于 VS.NET 2003 用户,dbgfix.sln 适用于 VS.NET 2002 用户。

要直接使用插件:

  1. 解压源代码 zip 文件。
  2. 通过调用 RegSvr32 dbgfix.dll 注册插件

如果您选择构建源代码,插件将在生成后步骤中注册。插件代码简要说明如下:

  1. OnConnection 事件中,插件使用 Detours 库挂钩 CreateProcessWCreateProcessA API。仅当 VS.NET 以主交互用户(从登录屏幕登录计算机的用户)的凭据运行时,才会执行此操作。
  2. OnDisconnection 事件中,插件通过移除对 CreateProcessWCreateProcessA API 的挂钩来清理。

OnConnection

让我们检查一下插件的 OnConnection 方法的代码。

  1. 查找 VS.NET 是否以主交互用户的身份运行。对于每个窗口站,系统都会关联一个用户。可以通过在 winsta0 的窗口站句柄上调用 GetUserObjectInformation 来获取用户的 SID。
    //Winsta0 is associated with the primary interactive user
    HWINSTA hwinsta = OpenWindowStation(TEXT("winsta0"), 
                               FALSE, WINSTA_READATTRIBUTES);
    
    //Get the sid associated with winsta0
    DWORD dwLength = 0;
    
    if (!GetUserObjectInformation(hwinsta, UOI_USER_SID, 
                                       NULL, 0, &dwLength))
    {
        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            AtlThrowLastWin32();
    }
    
    CAutoVectorPtr<BYTE> spSid(new BYTE[dwLength]);
    
    if (spSid == NULL)
        AtlThrow(E_OUTOFMEMORY);
    
    if (!GetUserObjectInformation(hwinsta, UOI_USER_SID, 
                                spSid, dwLength, &dwLength))
        AtlThrowLastWin32();
  2. 下一步是查找启动 devenv 的用户的登录 SID。这可以从进程令牌中获取。
    //Get the sid associated with the process token
    CAccessToken token;
    
    if (!token.GetProcessToken(TOKEN_QUERY))
        AtlThrowLastWin32();
    
    CSid sidLogon;
    
    if (!token.GetLogonSid(&sidLogon))
        AtlThrowLastWin32();
    
    m_bRunningAsPrimary = (sidLogon == *(SID*)((BYTE*)spSid));
  3. 最后,如果用户的登录 SID 与 winsta0 用户关联的 SID 不同,我们就调用 Detours 库。
    if (!m_bRunningAsPrimary)
    {
        //This means that devnv is running in a different user 
        //account than the primary interactive user
        DetourFunctionWithTrampoline((PBYTE)Real_CreateProcessW, 
                                      (PBYTE)CreateProcessW_Detour);
        DetourFunctionWithTrampoline((PBYTE)Real_CreateProcessA, 
                                      (PBYTE)CreateProcessA_Detour);
    }

    第一个 DetourFunctionWithTrampoline 调用会修改 CreateProcessW 地址处的前几个指令,并调用函数 CreateProcessW_Detour。仍然可以使用 Real_CreateProcessW 调用实际的 CreateProcess 函数。有关 Detours 工作原理的完整讨论,请参阅 Detour 网站CreateProcessW_Detour 函数的外观如下。

    BOOL WINAPI CreateProcessW_Detour(LPCWSTR lpApplicationName, 
       LPWSTR  lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, 
       LPSECURITY_ATTRIBUTES lpThreadAttributes,BOOL bInheritHandles, 
       DWORD dwCreationFlags, LPVOID lpEnvironment, 
       LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, 
       LPPROCESS_INFORMATION lpProcessInformation)
    {
       if (lpStartupInfo->lpDesktop != NULL)
       {
           //Check for empty string
           if (*lpStartupInfo->lpDesktop == 0)
               lpStartupInfo->lpDesktop = NULL;
       }
    
       return Real_CreateProcessW(lpApplicationName, lpCommandLine, 
              lpProcessAttributes, lpThreadAttributes, 
              bInheritHandles, dwCreationFlags, 
              lpEnvironment, lpCurrentDirectory, 
              lpStartupInfo, lpProcessInformation);
    }

    基本上,如果 lpDesktop 是一个空字符串,在调用实际的 CreateProcessW 之前,它会被转换为 NULL。这使得被调用的进程继承与 VS.NET 相同的窗口站,从而解决了问题。

结论

这个简单的插件解决了 IE 启动问题。感谢 Microsoft Research 提供 Detours 库的源代码。请注意,本文的源代码中只包含 detours.libdetours.h。如果您对 Detours 的完整源代码感兴趣,可以从 Detours 网站下载。

© . All rights reserved.