学习笔记之进程

来源:互联网 发布:淘宝客服售后处理 编辑:程序博客网 时间:2024/04/29 13:31

每一个进程都有两部分:

内核对象:操作系统用来管理进程。保存进程统计信息。

地址空间:包含所有可执行文件或dll模块的代码和数据。还有动态内存分配。

进程不执行任何代码,都必须让一个线程在它的上下文运行。由于进程的所有线程都在进程的地址空间执行代码。所有每个线程都有自己的一组CPU寄存器和它自己的堆栈。由于进程不执行任何代码,所有每一个进程至少有一个线程,我们称为主线程。当我创建进程时,系统会自动为我们创建一个主线程来执行进程地址空间的代码。

对于多线程来说,操作系统会为每个线程调度一些CPU时间,它会采用循环方式,为每个线程都分配时间片,从而照成所有线程“并发”运行的假象。在单核CPU下,线程不是真正的并发的,始终是一个个调用的。当有多个CPU时,才可能达到多个线程真正的并发运行。

程序运行流程

Windows应用程序必须有一个入口点函数,应用程序开始运行时,这个函数会被调用。

GUI程序:

int WinAPI _tWinMain(HINSTANCE hInstanceExe, HINSTANCE hPrevInstance,PTSTR pszCmdLine,int nCmdShow);

CUI程序:

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]);

操作系统实际并不调用我们所写的入口点函数。它会调用由C/C++运行库实现并在链接时使用-entry:命令行选项来设置的一个C/C++运行时启动函数。该函数将初始化C/C++运行库,还确保了在我们的代码开始执行之前,我们声明的任何全局和静态c++对象呗正确构造。

GUI程序:

入口点函数 WinMain   启动函数 WinMainCRTStartup

入口点函数 wWinMain  启动函数 wWinMainCRTStartup

CUI程序:

入口点函数 Main      启动函数 mainCRTStartup

入口点函数 Wmain    启动函数 wmainCRTSartup

启动函数的用途主要是:

1.       获取指向新进程的完整命令行的一个指针。

2.       获取指向新进程的环境变量的一个指针。

3.       初始化C/C++运行库的全局变量。

4.       初始化C运行库内存分配函数(malloc 和 calloc)和其他底层I/O例程使用的堆

5.       调用所有全局和静态C++类对象的构造函数。

6.       调用入口点函数

7.       调用C运行库函数exit,向其传递返回值(nMainRetVal)。

Exit执行下列任务:

调用_onexit函数调用所注册的任何一个函数。

调用所有全局和静态C++类对象的析构函数。

在DEBUG中,如果设置了_CRTDBG_LEAK_CHECK_DF标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄露报告。

调用操作系统的ExitProcess函数,向其传入nMainRetVal。

 

进程实例句柄

加载到进程地址空间的每一个可执行文件或者DLL文件都被赋予了一个独一无二的实例句柄。如:可执行文件的实例被当如_tWinMain函数的第一个参数传入。获取一个可执行文件或DLL文件被加载到地址空间的什么位置,可以使用

HMODULE GetModuleHandle(PCTSTR pszModule);

调用这个函数需要传入一个字符串,该字符串指定了已在主调进程的地址空间加载的一个可执行文件或DLL文件的名称。如果传入NULL,返回主调进程的可执行文件的基地址。如果我们的代码在一个DLL中,可以用两种方法来了解代码正在什么模块中运行。

1.       利用连接器提供的伪变量__ImageBase,它指向正在运行的模块的基地址

2.       调用GetModuleHandleEx,将GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS作为它的第一个参数,将当前函数的地址作为第二个参数。第三个参数指向一个HMODULE的指针。

进程的命令行

C运行库的启动代码开始执行一个应用程序时,会调用GetCommandLine函数可以获取进程的完整命令行,忽略了可执行文件的名称,然后将指向命令行剩余部分的一个指针传给命令行参数。应用程序可以利用

PWSTR* CommandLineToArgvW(PWSTR pszCmdLine,int* pNumArgs);

来将任何Unicode字符串分解,pNumArgs代表命令行中的个数的指针。返回值为Unicode字符串指针数组的地址。

进程的环境变量

每一个进程都一个相关联的环境块,这是进程地址空间内分配的一块内存,其中包含字符串,如:

=::=::\ 。。。

VarName1=VarValue1\0

VarName2=VarValue2\0

VarNameX=VarValueX\0

\0

字符串的第一部分是环境变量的名称,后跟一个等号,然后是希望赋给环境变量的值。块中除了=::=::\ 。。。字符串,还有一些其他字符串,以等号开始的,这些值不作为环境变量使用。要使用环境变量块,可以使用两种方法。

1.       调用GetEnvironmentStrings函数来获取完整的环境块,然后通过过滤出我们的环境变量。最后需要调用FreeEnvironmentStrings函数来释放它,因为GetEnvironmentStrings内部分配了内存块

2.       这种方式是CUI专用,通过应用程序main入口点函数所接受的TCHAR* env[]参数来实现。

环境变量操作

DWORD GetEnvironmentVariable(PCTSTR pszName, PTSTR pszValue, DWORD cchValue);

获取一个环境变量是否存在。PszName指向预期的变量名称,pszVallue指向保存变量值的缓冲区,cchValue指出缓冲区大小(字符数表示)。

DWORD ExpandEnvironmentStrings(PTCSTR pszSrc, PTSTR pszDst, DWORD chSize);

调用这个函数可以使用环境变量来填充字符串中“可替代的字符串”。pszSrc参数是包含“可替换环境变量字符串”(“%环境变量名%”)的一个字符串的地址。pszDst用于接收扩展字符串的一个缓冲区的地址。chSize参数是这个缓冲区的最大大小(字符数表示)。

BOOL SetEnvironmentVariable(PCTSTR pszName, PCTSTR pszValue);

可以添加、删除、修改一个环境变量的值。此函数将pszName所标识的一个变量设为pszValue所标识的值,如果有此变量就做修改,无此变量就做添加,pszValue为NULL就做删除。

进程信息

错误模式

与每个进程都关联了一组标识,这些标识的作用是让系统知道进程如何响应严重错误。进程可以调用UINT SetErrorMode(UINT fuErrorMode)函数来告诉系统如何处理这些错误

进程当前所在驱动器和目录

每一个进程都有一个进程的当前驱动器和目录,当使用一个相对路径的文件时,系统就会在进程的当前驱动器和目录中去找。线程可以使用GetCurrentDirectory和SetCurrentDirectory函数来获取和设置进程当前驱动器和目录。

系统跟踪记录进程的当前驱动器和目录,但是没有记录每个驱动的当前目录。不过,利用操作系统提供的支持,可以处理多个驱动器的当前目录。

子进程要进程父进程的环境块,父进程必须在生成子进程之前,创建这些驱动器号环境变量,并将它们添加到环境变量块中。否则子进程的当前目录默认为每个驱动器的根目录。GetFullPathName可以获得某个驱动器的当前目录。

系统版本

可以调用BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation)函数来获取系统的版本。

调用VerifyVersionInfo函数,它能比较主机系统版本和应用程序要求的版本。

创建进程

BOOL CreateProcess(

  LPCWSTR pszImageName,

  LPCWSTR pszCmdLine,

  LPSECURITY_ATTRIBUTES psaProcess,

  LPSECURITY_ATTRIBUTES psaThread,

  BOOL fInheritHandles,

  DWORD fdwCreate,

  LPVOID pvEnvironment,

  LPWSTR pszCurDir,

  LPSTARTUPINFOW psiStartInfo,

  LPPROCESS_INFORMATIONpProcInfo

);

调用此函数可以创建一个进程,系统会为这个进程创建一个主线程。具体信息参考MSDN。

终止进程

终止进程有4种方式:

1.       主线程的入口点函数返回

会进行一些清理操作:C++对象正确析构销毁;释放线程栈使用内存;将进程退出代码作为入口点函数返回值;递减进程内核对象使用计数

2.       进程中的一个线程调用ExitProcess函数

3.       任意进程中的线程调用TerminateProcess函数

4.       进程中的所有线程都“自然死亡”

当进程终止时,系统会进行如下清理:终止进程中遗留的任何线程;释放进程分配的所有用户对象和GDI对象,关闭所有内核对象,计数为0就销毁;进程退出代码从STILL_ACTIVE变为传给ExitProcess或TerminateProcess的代码;进程内核对象状态变成已触发状态;进程内核对象使用计数减1;

进程权限

对于应用程序开发影响最大的技术在于用户控制权限(UAC),当我们应用程序权限不够时,需要手动提升进程权限。我们需要调用

WINSHELLAPI BOOL WINAPI ShellExecuteEx(LPSHELLEXECUTEINFOlpExecInfo

);函数来手动提升进程的权限,lpExecInfo->lpVerb必须被设为“runas”,lpExecInfo->lpFile必须设为用来提升权限后启动的一个可执行文件的路径。

判断应用程序是否以提升权限方式启动,是否正在使用筛选的令牌运行。

1.    获取进程令牌。OpenProcessToken

2.    获取进程的提升类型。GetTokenInformation

3.    创建一个特定用户组的安全描述符CreateWellKnowSid

4.    判断进程是否以管理员运行IsUserAnAdmin;获取未筛选的令牌GetTokenMembership,判断是否包含一个管理员CheckTokenMembership。

枚举系统中进程

Process32Firt和Process32Next函数。

EnumProcess函数。

原创粉丝点击