进程是程序运行的一个实例,由以下两部分组成:
进程是惰性的,进程要做任何事都需要通过线程在其上下文环境中执行来实现。当一个进程创建后,操作系统也同时为其创建一个主线程(primary thread),主线程又会创建其他线程。当进程中所有的线程都停止时,操作系统也同时销毁该进程。
Windows中可能的进程入口函数如下:
WinMain/wWinMain是窗口进程程序的入口,相应的C运行时库入口微WinMainCRTStartup/wWinMainStartup;同理,main/wmain是控制台进程程序的入口,相应的C运行时库入口为mainCRTStartup/wmainCRTStartup。
通过编译器的连接开关/SubSystem:Console和/SubSystem:Winodws可以设定程序使用哪个子系统,事实上可以不设置这个值,Visual Studio的编译器会根据我们所提供入口函数来确定到底链接到哪个子系统。程序运行的时候,Windows操作系统的加载程序(loader)会检查执行文件镜像中的文件头,从而获得该子系统值。
Windows加载程序到进程后,即相应的CRT启动函数,这些启动函数的用途可总结如下:
如果是控制台程序可以将入口函数
_tmain(int argc, TCHAR* argv[])
换成
_tmain(int argc, TCHAR* argv[], TCHAR* env[])
Windows会这样调用
int nMainRetVal = _tmain(argc, argv, envp);
第三个参数指向进程环境变量。
入口函数返回后,CRT启动函数将调用CRT函数exit,并将(nMainRetVal)作为参数传给它,exit函数执行以下任务:
// 连接器伪变量,指示正在运行的代码所在文件(exe/dll)被加载到应用程序总哪个位置
EXTERN_C const IMAGE_DOS_HEADER __ImageBase;
void DumpModule()
{
//返回进程可执行文件的实例句柄
HMODULE hApp=GetModuleHandle(NULL);
TCHAR szModuleName[MAX_PATH]={};
GetModuleFileName(hApp, szModuleName, MAX_PATH);
_tprintf(_T("GetModuleHandle(NULL) returned -> address:0x%x with name: %s\r\n"), hApp, szModuleName);
//当前执行代码所在文件的实例句柄
HINSTANCE hCurrentModule=(HINSTANCE)&__ImageBase;
GetModuleFileName(hCurrentModule, szModuleName, MAX_PATH);
_tprintf(_T("__ImageBase returned -> address:0x%x with name: %s\r\n"), hCurrentModule, szModuleName);
HMODULE hCurrentExecutingModule=NULL;
//获得DumpModule函数所在文件的实例句柄
DWORD r = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCTSTR)DumpModule, &hCurrentExecutingModule);
GetModuleFileName(hCurrentExecutingModule, szModuleName, MAX_PATH);
_tprintf(_T("GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS) returned -> address:0x%x with name: %s\r\n"),
hCurrentExecutingModule, szModuleName);
}
前一个进程的实例
wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
);
hPrevInstance为前一个实例的句柄,但在Win32始终为NULL,其存在只是兼容Win16。Win16时代所有进程共享同一进程空间,为了指示程序的不同实例知道某些事已经被前一个实例做了,如注册Window Class,所以需要前一实例句柄;但Win32开始,每个进程有自己独享的进程空间,因而不再需要hPrevInstance,但为了让老代码还能运行,因此保留该参数,并永远设为NULL。
分解命令行参数代码
Int nNumArgs;
PTSTR *ppArgv = CommandLineArgv(GetCommandLine(), &nNumArgs);
//use the arguments
If(*ppArgv[1] == _T('x')){
…
}
//free heap
HeapFree(GetProcessHeap(), 0, ppArgv);
GetEnvironmentStrings和FreeEnvironmentStrings来操作进程环境变量
PTSTR pEnvBlock = GetEnviromentStrings();
// 注意处理=::=::以及空格
FreeEnvironmentStrings(pEnvBlock);
其他操作环境变量的函数
GetEnvironmentVariable
ExpandEnvironmentStrings
SetEnvironmentVariable
通常进程可以在任何CPU上执行,然而也可以强制其在可用的CPU某个子集上执行,称为进程的关联性(processor affinity),子进程将继承父进程关联性。
GetCurrentDirectory
SetCurrentDirctory
GetVersion返回MS-DOS和Windows版本号,该函数的Windows版本好高字节和低字节反了
GetVersionEx返回更加详细的信息
VerifyVersionInfo可以测试系统详细的版本信息
pszApplicationName: 如果不为NULL,则使用该参数,但必须包含完整的路径和扩展名;
pszCommandLine:如果pszApplicationName为NULL,则使用该参数,可以不包含扩展名,默认为.exe,如果不包含路径,CreateProcess将在以下目录查找:
PsaProcess, psaThread, bInheritHandles可指定进程的安全描述参数和是否继承父进程所拥有的可继承内核对象。
fdwCreate包含了创建进程的标志位。
PvEnvironment包含了传给新进程的环境变量,如果为NULL则从父进程继承。
pszCUrDir允许父进程设置子进程当前驱动器和目录。
PsiStartInfo必须初始化各个成员为0,并设置好结构体大小,否则可能导致创建进程失败,即STARTUPINFO si = {sizeof(si)};
ppiProcInfo为系统返回给我们的参数,包含了被创建进程和其主线程的信息。
终止进程有四种方式:
进程终止时会做以下动作:
CreateProcess创建子进程后,可以通过Proces_Information来操作子进程,如果不需要的话,应立即CloseHandle来关闭子进程主线程(hThread)和子进程本身(hProcess)的句柄。WaitForSingleObject(hProcess, INFINITE)可以等待子进程结束。
Windows Vista起引入了UAC(user account control),每次进程需要访问一个受保护或者具有更高系统完整性级别的资源时候,都需要用户的确认(即弹出UAC对话框,这点被证明很招人烦,微软在后续的Windows版本也大大降低了弹窗的频率)。即使一个用户是以管理员身份登录(大部分Windows用户都是这么干的),默认情况下,启动的进程也只是拥有一个被筛选过的安全令牌。
自动提升权限:程序可以嵌入清单资源(RT_MANIFEST)并在trustinfo段声明requestedExecutionLevel信息
手动提升权限:调用ShellExecuteEx设置lpVerb = "runas",lpFile="可执行文件"参数来申请提升权限,当然这需要用户的确认
权限上下文:
下面的函数可以用来获取权限上下文:
两套枚举系统中进程的API:
进程令牌还包含有进程的系统完整性级别,每个内核对象也有其相应的系统完整性级别,有了这两个信息,Windows系统就能在一个进程访问一个内核对象的时候比较其完整性级别,完整性级别低的进程无法修改和删除完整性高的对象,至于是否能读,取决于和对象相关的ACE(access control entry)的资源策略设置。
由于完整性级别比较发生在ACL(access control list)之前,所以如果其完整性级别较要访问的资源低,即使进程拥有访问资源的权限,访问也会被拒绝。例如,一个进程从网上下载和执行代码,这种设计就尤为重要。
GetProcessIntegrityLevel可以获得进程的完整新级别。