创建进程:CreateProcess
函数原型
1 2 3 4 5 6 7 8 9 10 11 |
BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandles, DWORD fdwCreate, PVOID pvEnvironment, PCTSTR pszCurDir, PSTARTUPINFO psiStartInfo, PROCESS_INFORMATIN ppiProcInfo); |
进程创建过程
- 一个线程调用这个CreateProcess时,系统将创建一个进程内核对象,初始使用计数为1。
- 进程内核对象不是这个进程本身,而是一个操作系统用来操作这个进程的一个小型数据结构。
- 然后,系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码及数据,加载到进程的地址空间。
- 然后,系统为新进程的主线程创建一个线程内核对象,初始使用计数为1。
- 这个线程内核对象也是一个小型数据结构,操作系统用它来管理这个线程。
- 这个主线程开始指向C/C++运行时的启动例程,而所谓启动例程是由链接器设为程序入口的。
- 最终会调到应用程序的WinMain、wWinMain、main或wmain函数这里。
- 如果CreateProcess成功创建了新进程和主线程,它就返回TRUE。
注意点一
- CreateProcess是在新进程完全初始化好之前就返回TRUE的。
- 这说明,操作系统尚未尝试定位所有必要的DLL,如果一个DLL没有找到或者不能正确初始化,进程就会终止。
- 而由于CreateProcess先于这些现象之前就返回了TRUE,所以父进程不会知道这些初始化问题。
参数一二
- pszApplicationName指的是新进程要使用的可执行文件的名称。
- pszCommandLine是要传给新进程的命令行字符串。
- pszCommandLine这个参数不能是常量字符串,因为CreateProcess内部需要修改, 如果它改不了的话会引发一个访问违规。
- 另外需要注意的是编译器开关/Gf和/GF可以消除重复的字符串,并判断是否将那些字符串放在了只读的区域。
/ZI开关里面包含了/GF。它允许使用“编辑并继续”调试功能。
- 另外需要注意的是编译器开关/Gf和/GF可以消除重复的字符串,并判断是否将那些字符串放在了只读的区域。
- CreateProcess会解析pszCommandLine的时候,会检查字符串的第一个token,如果没有扩展命,就会默认是.exe。如果文件名中包含了完整路径,系统会利用这个完整路径来查找可执行文件,而不会搜索目录。
CreateProcess搜索可执行文件的顺序如下:- 主调进程.exe文件所在的目录
- 主调进程的当前目录
- Windows系统目录
- Windows目录
- PATH环境变量中列出的目录
参数三四五
- 为了创建一个新进程,系统必须创建一个进程内核对象和一个线程内核对象。对于这些内核对象,父进程有机会将安全属性关联到这两个内核对象上。
- 所以我们可以用psaProcess和psaThread参数来为进程对象和线程对象指定安全性。如果给这两个参数传值为NULL,那么系统就会为这两个内核对象指定默认的安全描述符。
- 由于psaProcess和psaThread这两个参数使用SECURITY_ATTRIBUTES结构,这两个内核对象句柄可由父进程将来生成的任何子进程继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
假设 进程A调用CreateProcess创建了进程B。 给psaProcess传入的结构体里面,hInheritHandle被设为TRUE。 给psaThread传入的结构体里面,hInheritHandle被设为了FALSE。 那么 系统在创建进程B时,会同时创建一个进程内核对象和一个线程内核对象。 并在ppiProcInfo参数指向的一个结构体中,将句柄返回给了进程A。 这时 利用返回的这些句柄,进程A就可以操作新建的进程对象和线程对象。 再假设,这时 进程A调用CreateProcess创建了进程C。 而进程A可以通过hInheritHandles这个参数来决定它创建的进程是否可以操纵它的一些句柄。 如果,hInheritHandles被设置了TRUE,那进程C将继承进程A中所有可继承的句柄。 而上面进程B的进程对象的句柄是可继承的,主线程对象的句柄是不可继承的。 所以说 如果进程A调用CreateProcess创建进程C的时候 hInheritHandles设置的是TRUE,那么对进程C来说,它将继承进程A所有可继承的句柄,包括进程B的进程内核对象的句柄。 hInheritHandles设置的是FALSE,那么对进程C来说,它将不会继承进程A当前所用的任何一个句柄。 |
参数六
- 这个参数标识了影响新进程创建方式的标志。多个标志可以按位或起来,以便同时指定多个标志组合。
- DEBUG_PROCESS
- 向系统表明父进程希望调试子进程以及子进程将来生成的所有进程。
- 向系统表明,在任何一个子进程中发生特定是事件时,要通知父进程。
- DEBUG_ONLY_THIS_PROCESS
- 类似DEBUG_PROCESS
- 但是只有在关系最近的子进程发生了特定的事件时,才通知父进程。
- 如果子进程又生成了新进程,那么在新进程中发生了特定的事件时,调试器也不会得到通知。
- CREATE_SUSPENDED
- 系统在创建新进程的同时挂起其主进程。
- 这样一来,父进程就可以修改子进程地址空间中的内存,更改子进程的主线程的优先级,或者将进程执行任何代码之前,将此进程添加到一个作业中。
- 父进程修改好了子进程之后,可以调用ResumeThread函数来允许子进程执行代码。
- DETACHED_PROCESS
- 阻止一个基于CUI的进程访问其父进程的控制台窗口,并告诉系统将它的输出发到一个新的控制台窗口。
- 如果一个基于CUI的进程是由另一个基于CUI的进程创建的,那么在默认的情况下,新进程将使用父进程的控制台窗口。
- 通过指定这个标志,新进程如果需要将输出发生到一个新的控制台窗口,那它就必须调用AllocConsole函数来创建它自己的控制台。
- CREATE_NEW_CONSOLE
- 指示系统为新进程创建一个新的控制台窗口。
- CREATE_NEW_CONSOLE|DETACHED_PROCESS,这样用会出错。
- CREATE_NO_WINDOW
- 指示系统不用为应用程序创建任何控制台窗口。
- CREATE_NEW_PROCESS_GROUP
- 修改用户使用Ctrl+C或Ctrl+Break时获得通知的进程列表。
- 比如按下这些建时,有多个CUI程序正在运行,系统将通知一个进程组中的所有进程,告诉它们用户打算中断当前操作。
- 在创建一个新的CUI程序时,如果指定了这个标志,就会创建一个新的进程组。组中的一个进程处于活动状态时,一旦用户按下了这些组合键,那系统就会向这个进程组中的进程发出通知。
- CREATE_DEFAULT_ERROR_MODE
- 向系统表明新进程不会继承父进程所用的错误模式。
- CREATE_SEPARATE_WOW_VDM
- 只有在运行16位Windows程序时才有用。
- 它指示系统创建一个单独的虚拟DOS机,并在这个虚拟DOS机上运行16位的Windows程序。
- 因为默认的情况下,所有16位Windows应用程序都在一个共享的虚拟DOS机上运行。
- 在独立的VDM中运行的好处是,如果程序奔溃了,它只需要杀死这个VDM,而在其他VDM中运行的程序仍然能接收到输入。
- CREATE_SHARED_WOW_VDM
- 只有在运行16位Windows程序时才有用。
- 默认的情况,所有16位Windows应用程序都在一个共享的虚拟DOS机上运行。
- CREATE_UNICODE_ENVIRONMENT
- 告诉系统子进程的环境块应包含Unicode字符。
- 进程的环境块默认包含的是ASCI字符串。
- CREATE_FORCEDOS
- 强制系统运行一个嵌入在16位OS/2应用程序中的MS-DOS应用程序。
- CREATE_BREAKAWAY_FROM_JOB
- 运行一个作业中的进程生成一个和作业无关的进程。
- EXTENDED_STARTUPINFO_PRESENT
- 向操作系统表明传给psiStartInfo参数的是一个STARTUPINFOEX结构。
参数七
- 这个参数指向一块内存,包含新进程要使用的环境字符串。
- 多数情况,给这个参数的传值都是NULL,将导致子进程继承其父进程使用的一组环境字符串。
- 具体是CreateProcess函数调用了GetEnvironmentStrings函数获取了主进程正在使用的环境字符串数据块的地址。
- 对于GetEnvironmentStrings这个函数返回的地址,不再需要的时候,应该使用FreeEnvironmentStrings来释放掉。
参数八
- 允许父进程设置子进程的当前驱动器和目录。
- 如果这个参数位NULL,则新进程的工作目录与生成新进程的应用程序一样。
- 如果不为空,要传入一个指向用0为终止符的字符串的指针。
参数九
- 指向一个STARTUPINFO结构体或STARTUPINFOEX结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
typedef struct _STARTUPINFO { DWORD cb; PSTR lpReserved; PSTR lpDesktop; PSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttributes; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; PBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO; typedef struct _STARTUPINFOEX { STARTUPINFO StartupInfo; struct _PROC_THREAD_ATTRIBUTE_LIST * lpAttributeList; } STARTUPINFOEX, *LPSTARTUPINFOEX; |
- Windows在创建新进程的时候使用这个结构体。
如果没有把该结构体的内容清零,则成员将包含主调线程的栈上的垃圾数据。
如果把这种垃圾数据传给了CreateProcess,可能会导致新进程有时能创建,有时不能创建。
1 2 |
STARTUPINFO si = { sizeof(si) }; CreateProcess(..., &si, ...); |
参数十
- 该参数指向一个PROCESS_INFORMATIN结构体,CreateProcess函数在返回之前,会初始化这个结构体。
- 在CreateProcess函数返回之前,它会使用完全访问权限来打开进程对象和线程对象,并将各自的与进程相关联的句柄放入到结构体的hProcess和hThread里面。
- 当CreateProcess在内部打开这些对象时,每个对象的使用计数就变成了2。这意味着系统想要释放进程对象,进程必须终止(计数减1),父进程调用CloseHandle(计数减1)。
- 创建一个内核对象时,系统会为此对象分配一个独一无二的标识符,系统中没有别的进程内核对象会有相同的ID编号。
线程内核对象也一样。创建一个线程内核对象时,此对象会被分配一个独一无二的、系统级别的ID编号。
进程ID和线程ID分享同一个号码池。这意味着线程和进程不可能有相同的ID。 - 此外,一个对象被分配的ID绝对不会是0。Windows任务管理器将进程ID0和系统空闲进程相关联。
实际上并没有系统空闲进程这样的东西。任务管理器创建这个虚构进程的目的是将其作为空闲线程的占位符。
在没有别的线程正在运行时,系统就运行这个所谓空闲进程。
空闲进程中的线程数量始终等于计算机的CPU的数量。所以,它始终代表未被真实进程使用的CPU使用率。 - CreateProcess返回之前,会将这些ID填充到PROCESS_INFOMATION结构体中的dwProcessId和dwThreadId成员中。
如果应用程序要通过ID来追踪进程和线程,那么需要注意一点,就是进程和线程ID会被系统立即重用。 - 可以使用GetCurrentProcessId来获取当前进程的ID。
也可以通过使用GetCurrentThreadId来获取当前正在运行的线程的ID。
使用GetProcessId来获得指定句柄对应的一个进程的ID。
使用GetThreadId来获得指定句柄对应的一个线程的ID。
然后也可以通过调用GetProcessIdOfThread来获取其所在进程的ID。 - ToolHelp函数允许进程通过PROCESSENTRY32结构体来查询它的父进程。
此结构内部有一个 th32ParentProcessID成员,这个成员能返回父进程的ID。
但是需要注意的是,虽然系统确实会记住每个进程的父进程的ID,但由于ID会被立即重用,所以等我们获得父进程的ID的时候,这个ID标识很可能已经被重用了,可能是系统中允许的一个完全不同的进程了。
所以,如果需要和一个进程的创建者通信的话,最好使用更持久的通信机制,比如内核对象、窗口句柄等。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Soui一03/17
- ♥ COM组件_403/07
- ♥ Windows API11/11
- ♥ Windows 核心编程 _ 作业07/01
- ♥ Windows 核心编程 _ 创建&&终止线程07/02
- ♥ X86_64汇编学习记述四08/09