首页
社区
课程
招聘
[分享]experiment : test CreateProcessWithLogonW
发表于: 2013-7-31 15:50 10621

[分享]experiment : test CreateProcessWithLogonW

2013-7-31 15:50
10621
某人说 使用了 CreateProcessWithLogonW, 会导致句柄泄露.

做了个实验,验证是否由此问题.
测试工程源码下载 : srcTestCreateProcessWithLogon_2013_0731_1727.rar

他的应用是守护进程, 为了变于模拟被守护进程的结束, 写了2个测试程序.

测试程序1 : 模拟被守护程序,  启动后, 一秒钟后自动退出.

测试程序2:  模拟守护进程,当发现被守护进程退出后, 重新启动一份被守护进程.

测试环境: Win7X64 + vs2008

测试工具: procexp.exe (Process Explorer)

测试过程:

  * 将ProgTobeRun.exe 放到D盘下

  * 在任意位置运行srcBad.exe

  * 打开procexp.exe,  找到srcBad.exe, 右击属性, 查看性能页面, 看句柄数量.

测试结果:

  * 运行了几分钟, srcBad.exe 的句柄数量很稳定.

测试结论: 不存在句柄泄露情况.

测试过程抓图:


测试程序1代码:
// ProgTobeRun.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    HWND hMyWnd = ::GetConsoleWindow();

    /// 让每次生成的程序都在屏幕的固定位置, 便于观察主程序的屏幕打印结果
    ::MoveWindow(hMyWnd, 100, 100, 300, 100, TRUE);

    _tprintf(L"Entry ProgTobeRun[PID:0x%X,TID:0x%X]\r\n",
        GetCurrentProcessId(),
        GetCurrentThreadId());

    _tprintf(L"when Sleep(1000), I will be quit\r\n");
    ::Sleep(1000);

	return 0;
}


测试程序2代码:
修正了 Loka 同学指出的 CreateProcessWithLogonW API 参数6问题.

// srcBad.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <locale.h>

/// build on vs2008, unicode console

/// @note
/// 测试环境 Win7X64Sp1
/// administrator 用户登录
/// 新建一个用户 usr_test, 以用户usr_test登录一次

/// 如果CreateProcessWithLogonW 执行后出现错误 GetLastError = 0x422
/// 将系统中被禁用的服务全部恢复, 比如 :用过360safe的优化, 就会存在此问题 

/// CreateProcessWithLogonW Error Rc = 0x422
/// 无法启动服务,原因可能是已被禁用或与其相关联的设备没有启动。 

/// 用Administrator用户登录后, 创建的标准用户
/// 已经用此用户登录过一次
/// 然后用Administrator用户登录, 运行此测试程序

/// 进程句柄测试结果
/// 经过 Process Explorer 测试, 此程序的句柄数, 一直维持在57, 59, 61
/// 不会出现句柄泄漏的情况
#define G_LOGIN_NAME    L"usr_test"
#define G_LOGIN_PWD     L"1"
#define G_PROG_NAME     L"d:\\ProgTobeRun.exe"
#define G_LEN_CMD_LINE  1024    ///< 命令行最大长度

/// 计算wchar_t 数组的size, 以wchar_t为单位 
#define SIZE_OF_WCHAR_ARRAY(x) (sizeof(x) / sizeof(wchar_t))

/// @fn     DisplayError
/// @brief  显示GetLastError的错误信息
/// @param  LPWSTR pszTip, 提示消息 
void DisplayError(LPWSTR pszTip);

/// @fn     CreateProcessWithLoginEx
/// @brief  使用指定的用户名, 口令, 启动一个进程
/// @param  wchar_t * pcLoginUsr
///         本机存在的用户名
/// @param  wchar_t * pcLoginPwd
///         pcLoginUsr 对应的口令
/// @param  wchar_t * pcPePathName
///         要启动的PE文件全路径 e.g. d:\\ProgTobeRun.exe
/// @param  DWORD &   dwSubPID,
///         返回执行完的子进程 PID
/// @param  DWORD &   dwSubTID
///         返回执行完的子进程 TID
/// @param  BOOL      bWaitProcessEnd
///         是否等待 pcPePathName 结束
/// @return BOOL
/// @retval TRUE, 成功
/// @retval 其他, 失败
BOOL CreateProcessWithLoginEx(wchar_t * pcLoginUsr,
                              wchar_t * pcLoginPwd, 
                              wchar_t * pcPePathName,
                              BOOL      bWaitProcessEnd,
                              DWORD &   dwSubPID,
                              DWORD &   dwSubTID);

int _tmain(int argc, _TCHAR* argv[])
{
    BOOL    bRc             =   FALSE;
    BOOL    bWaitProcessEnd =   TRUE;
    DWORD   dwSubPID        =   0;
    DWORD   dwSubTID        =   0;
    wchar_t cPePathName[G_LEN_CMD_LINE] = {0};

    setlocale(LC_CTYPE, ".936");    //< 控制台为中文输出

    while(true)
    {
        ::Sleep(100);   ///< 每次创建一个进程后, 休息一下

        /// 执行完 CreateProcessWithLoginEx 后, cPePathName 有可能被改掉
        _tcscpy_s(cPePathName, SIZE_OF_WCHAR_ARRAY(cPePathName), G_PROG_NAME);
        bRc = CreateProcessWithLoginEx( G_LOGIN_NAME, 
                                        G_LOGIN_PWD,
                                        cPePathName,
                                        TRUE,
                                        dwSubPID,
                                        dwSubTID);
        if (bRc)
        {
            _tprintf(L"Process[PID:0x%X, TID:0x%X] execute OK\r\n",
                dwSubPID,
                dwSubPID);
        }
        else
        {
            /// 只要系统服务都恢复启动了, 就不会出现0x422错误
            DisplayError(L"CreateProcessWithLogonW");
        }
    }

    getwchar();
	return 0;
}

BOOL CreateProcessWithLoginEx(wchar_t * pcLoginUsr, 
                              wchar_t * pcLoginPwd, 
                              wchar_t * pcPePathName,
                              BOOL      bWaitProcessEnd,
                              DWORD &   dwSubPID,
                              DWORD &   dwSubTID)
{
    BOOL                    bRc         =   FALSE;
    DWORD                   dwLenName   =   0;

    wchar_t cComputerName[MAX_PATH];

    STARTUPINFO             si;
    PROCESS_INFORMATION     pi;

    ::ZeroMemory(&si, sizeof(STARTUPINFO));

    si.wShowWindow = SW_SHOW;   
    si.lpDesktop = NULL;  
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;

    ::ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 
    dwLenName = SIZE_OF_WCHAR_ARRAY(cComputerName);
    if (FALSE == ::GetComputerNameW(cComputerName, &dwLenName))
        DisplayError(L"GetComputerNameW");

    /**
    备注 : lpCommandLine 传进来时, 应该是一个最大1024长度的Buffer, 不能是常量串

    The command line to be executed. The maximum length of this string is 
    1024 characters. If lpApplicationName is NULL, the module name portion of 
    lpCommandLine is limited to MAX_PATH characters.

    CreateProcessWithLogonW 参数6 lpCommandLine 
    The function can modify the contents of this string. Therefore, 
    this parameter cannot be a pointer to read-only memory (such as a const 
    variable or a literal string). If this parameter is a constant string, the 
    function may cause an access violation.
    */
    bRc = CreateProcessWithLogonW(
        pcLoginUsr,
        cComputerName,
        pcLoginPwd,
        NULL,
        NULL,
        pcPePathName,
        CREATE_DEFAULT_ERROR_MODE,
        NULL,
        NULL,
        &si,
        &pi);

    if (bRc)
    {
        dwSubPID = pi.dwProcessId;
        dwSubTID = pi.dwThreadId;

        if (bWaitProcessEnd)
            ::WaitForSingleObject(pi.hProcess,INFINITE);

        ::CloseHandle(pi.hProcess);
        ::CloseHandle(pi.hThread);
    }  

    return bRc;
}

void DisplayError(LPWSTR pszTip)
{
    LPVOID lpvMessageBuffer;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, GetLastError(), 
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
        (LPWSTR)&lpvMessageBuffer, 0, NULL);

    /// now display this string
    _tprintf(L"ERROR: API        = %s.\n", pszTip);
    _tprintf(L"       error code = 0x%X.\n", GetLastError());
    _tprintf(L"       message    = %s.\n", (LPWSTR)lpvMessageBuffer);

    /// Free the buffer allocated by the system
    LocalFree(lpvMessageBuffer);
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (19)
雪    币: 38
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
你是不是听不懂中文的?我说要在任务管理器的性能选项卡下的系统句柄啊?不是用processExplorer看进程的句柄啊?进程句柄没异常,系统总句柄猛增,导致这个函数不可用啊!至少你应该看到系统的内存使用不断增加,有增无减,一天时间吧,你的系统就跑不动了。
2013-7-31 15:57
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
3
你说这个进程引起的句柄狂涨,看任务管理器中的句柄有毛用??
你怎么能确定一定是这个进程引起的?

如果是你的子进程引起的呢?

你讨论的时候,就开放了你的守护进程源码。
那我们就讨论你的守护进程源码引起的问题。

你不是说 CreateProcessWithLogonW 会引起句柄泄漏么?
我已经证明给你了,CreateProcessWithLogonW 不会引起句柄泄漏。

你想说明啥问题?
2013-7-31 16:00
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
4
就你开放给大家的守护进程代码看,是不会引起所谓什么泄漏问题的。
你要不将你工作上的源码放出来,大家给你调试??
2013-7-31 16:01
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
5
现在最简单测试是不是你的被守护进程出了问题。

将 ProgTobeRun.exe 放到你硬盘上,由你原来的守护进程 守护 ProgTobeRun.exe, 看是否会出现系统句柄狂涨的问题。

如果是, 去调试你的被守护进程。
里面是否出现句柄打开没管, 据你描述的现象。 问题出在你的被守护进程的概率很大。

我中文一直很好,以后再也不会回答你的问题~~
服了~~
2013-7-31 16:05
0
雪    币: 38
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
系统跑不动换句通俗的说法就是电脑卡死了
2013-7-31 16:06
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
7
实际上按MSDN的说明这样创建的进程是有个数限制的,XP下最多只能创建MAXIMUM_WAIT_OBJECTS*4=256个,WIN 7不知道是多少,如果被创建的进程不能及时退出,句柄没被销毁,死循环到最后就无法再创建新进程了。楼主的代码风格好多了,原来那兄弟放着GetComputerNameW不用却要用MultiByteToWideChar来转换。
2013-7-31 16:07
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
8
我Show给你的这段代码,没有资源泄漏, 跑多长时间都不会有问题。

你的被守护进程有句柄泄漏问题,自己查去吧。
2013-7-31 16:08
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
9
他的应用是守护进程的用法,只有被守护进程退出了,才会重新开一份被守护进程。
如果他的被守护进程没有内存泄漏,句柄泄漏。 是不会出现卡死的现象。
2013-7-31 16:10
0
雪    币: 38
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
好吧,你们赢了,waitForSingleObject等它返回后再循环不意味着那进程已经退出了,那进程如果奇迹般退出了也不意味着系统会回收我忘记delete掉的资源了
2013-7-31 16:13
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
11
是这样的,这个函数本身没有任何问题,应该是他的那个被启动进程或子进程造成的泄漏,楼主真有耐心,另外还是罗嗦一句,第六个参数的用法还是不妥,认真看一下MSDN的说明就明白了。
2013-7-31 16:16
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
12
运行这2个测试程序,我没看到我的开发机系统内存在不断增加。
因为这2个测试程序没有内存泄漏,资源泄漏。

我中文很好啊,

你从昨天到今天,就这么一个小问题, 你折腾到现在都没解决.
你表述的很清楚么?有几个同学回答你的问题啊?
是不是所有同学都像我一样所谓的中文不好呢?

你咋没解决呢??
你中文好么?
2013-7-31 16:17
0
雪    币: 38
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
好吧,你们这对夫妇赢了。
我身为一个程序员,能做到的就这些,微软的设计是天衣无缝的,完美的!perfect!
2013-7-31 16:23
0
雪    币: 45
活跃值: (55)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
火药味很重
2013-7-31 16:25
0
雪    币: 38
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
随便提醒一句多余的,while(true)使用CreateProcessAsUser来启动我那个被守护进程,系统的句柄没有猛增,360的小球里显示反而越运行内存使用百分比越小
2013-7-31 16:26
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
16
应该是你嬴了,我和楼主素不相识,只是早上上看雪看到贴子好奇参与讨论,即然你认为自己的程序没问题那就行,不多说了。
2013-7-31 16:30
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
17
你作为程序员,拜托你说话专业点,什么 360的小球

讨论问题时, 要用 procexp 之类的工具,直接定位到被调试的程序,用具体的数据说话。
e.g. 我的被守护进程被 procexp 指示, 发现了XX问题
2013-7-31 16:30
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
18
你用的是C语言, 不是JAVA.
你很幽默啊.
2013-7-31 16:36
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
19
已经按照您的指点修正完了, 感谢~

    /**
    备注 : lpCommandLine 传进来时, 应该是一个最大1024长度的Buffer, 不能是常量串

    The command line to be executed. The maximum length of this string is 
    1024 characters. If lpApplicationName is NULL, the module name portion of 
    lpCommandLine is limited to MAX_PATH characters.

    CreateProcessWithLogonW 参数6 lpCommandLine 
    The function can modify the contents of this string. Therefore, 
    this parameter cannot be a pointer to read-only memory (such as a const 
    variable or a literal string). If this parameter is a constant string, the 
    function may cause an access violation.
    */
    bRc = CreateProcessWithLogonW(
        pcLoginUsr,
        cComputerName,
        pcLoginPwd,
        NULL,
        NULL,
        pcPePathName,
        CREATE_DEFAULT_ERROR_MODE,
        NULL,
        NULL,
        &si,
        &pi);
2013-7-31 17:36
0
雪    币: 94
活跃值: (475)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
感谢楼主,为了别人的一个问题这么敬业。我觉得那位同志不管别人说的对与否,哪怕不知道说一句谢谢都,也应该耐心的和人家沟通嘛,或者至少不要冷嘲热讽嘛
2013-8-2 10:50
0
游客
登录 | 注册 方可回帖
返回
//