某人说 使用了 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);
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: