不管是桌面双击应用程序(ShellExecute*)还是代码中调用创建进程API, 最终一般都会调用NT Native API CreateProcess.
CreateProcess内部调用到Kernel NtCreateProcess例程,内部比较复杂。
- BOOL CreateProcessA(
- [in, optional] LPCSTR lpApplicationName,
- [in, out, optional] LPSTR lpCommandLine,
- [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
- [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
- [in] BOOL bInheritHandles,
- [in] DWORD dwCreationFlags,
- [in, optional] LPVOID lpEnvironment,
- [in, optional] LPCSTR lpCurrentDirectory,
- [in] LPSTARTUPINFOA lpStartupInfo,
- [out] LPPROCESS_INFORMATION lpProcessInformation
- );
-
- from: https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
大体上,即为:
a. 分配EPROCESS内存, 创建进程/线程Kernel对象,初始化进程基本信息,比如PCB、ID、优先级、进程名称等。
b. 设置线程启动地址,把线程移到就绪列表。
具体请参阅WRK1.2 create.c, 这里不再赘述,下面会继续讨论API参数和Kernel内部比较特别的地方。
ps\create.c
Q: CreateProcess包含参数"LPCSTR lpApplicationName", 为什么到Kenrel系统调用就没了?
A: 因为在userspace DLL已经调用了NtOpenFile打开过可执行映像,返回了SectionHandle.
Q: 如果ParentProcess为空,最终会返回非法参数错误?
A: 是的. 对于用户空间创建进程,必须有父进程。只有Kernel才有权限创建没有Parent的进程.
1 创建EPROCESS
2 获取可执行镜像Section指针
3 拷贝父进程句柄表
4 初始化PCB(KPROCESS)
5 设定进程优先级
6 初始化进程空间
7 设置进程PID
8 如果父进程在Job中,此进程也加入到对应Job
9 创建PEB
10 将此进程加入到全局进程列表中
11 把当前进程handle加入到当前进程句柄表中
12 设置进程创建时间并返回进程句柄
如上即为Windows创建进程的基本过程,忽略了一些参数检测判断和Security检查等旁枝细节,以后的博客中会介绍到。