• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2021-06-25 21:55 Aet 隐藏边栏 |   抢沙发  8 
文章评分 2 次,平均分 5.0

创建进程:CreateProcess

函数原型

进程创建过程

  1. 一个线程调用这个CreateProcess时,系统将创建一个进程内核对象,初始使用计数为1。
  2. 进程内核对象不是这个进程本身,而是一个操作系统用来操作这个进程的一个小型数据结构。
  3. 然后,系统为新进程创建一个虚拟地址空间,并将可执行文件(和所有必要的DLL)的代码及数据,加载到进程的地址空间。
  4. 然后,系统为新进程的主线程创建一个线程内核对象,初始使用计数为1。
  5. 这个线程内核对象也是一个小型数据结构,操作系统用它来管理这个线程。
  6. 这个主线程开始指向C/C++运行时的启动例程,而所谓启动例程是由链接器设为程序入口的。
  7. 最终会调到应用程序的WinMain、wWinMain、main或wmain函数这里。
  8. 如果CreateProcess成功创建了新进程和主线程,它就返回TRUE。

注意点一

  1. CreateProcess是在新进程完全初始化好之前就返回TRUE的。
  2. 这说明,操作系统尚未尝试定位所有必要的DLL,如果一个DLL没有找到或者不能正确初始化,进程就会终止。
  3. 而由于CreateProcess先于这些现象之前就返回了TRUE,所以父进程不会知道这些初始化问题。

参数一二

  1. pszApplicationName指的是新进程要使用的可执行文件的名称。
  2. pszCommandLine是要传给新进程的命令行字符串。
  3. pszCommandLine这个参数不能是常量字符串,因为CreateProcess内部需要修改, 如果它改不了的话会引发一个访问违规。
    1. 另外需要注意的是编译器开关/Gf和/GF可以消除重复的字符串,并判断是否将那些字符串放在了只读的区域。
      /ZI开关里面包含了/GF。它允许使用“编辑并继续”调试功能。
  4. CreateProcess会解析pszCommandLine的时候,会检查字符串的第一个token,如果没有扩展命,就会默认是.exe。如果文件名中包含了完整路径,系统会利用这个完整路径来查找可执行文件,而不会搜索目录。
    CreateProcess搜索可执行文件的顺序如下:

    1. 主调进程.exe文件所在的目录
    2. 主调进程的当前目录
    3. Windows系统目录
    4. Windows目录
    5. PATH环境变量中列出的目录

参数三四五

  1. 为了创建一个新进程,系统必须创建一个进程内核对象和一个线程内核对象。对于这些内核对象,父进程有机会将安全属性关联到这两个内核对象上。
  2. 所以我们可以用psaProcess和psaThread参数来为进程对象和线程对象指定安全性。如果给这两个参数传值为NULL,那么系统就会为这两个内核对象指定默认的安全描述符。
  3. 由于psaProcess和psaThread这两个参数使用SECURITY_ATTRIBUTES结构,这两个内核对象句柄可由父进程将来生成的任何子进程继承。

参数六

  1. 这个参数标识了影响新进程创建方式的标志。多个标志可以按位或起来,以便同时指定多个标志组合。
  2. DEBUG_PROCESS
    1. 向系统表明父进程希望调试子进程以及子进程将来生成的所有进程。
    2. 向系统表明,在任何一个子进程中发生特定是事件时,要通知父进程。
  3. DEBUG_ONLY_THIS_PROCESS
    1. 类似DEBUG_PROCESS
    2. 但是只有在关系最近的子进程发生了特定的事件时,才通知父进程。
    3. 如果子进程又生成了新进程,那么在新进程中发生了特定的事件时,调试器也不会得到通知。
  4. CREATE_SUSPENDED
    1. 系统在创建新进程的同时挂起其主进程。
    2. 这样一来,父进程就可以修改子进程地址空间中的内存,更改子进程的主线程的优先级,或者将进程执行任何代码之前,将此进程添加到一个作业中。
    3. 父进程修改好了子进程之后,可以调用ResumeThread函数来允许子进程执行代码。
  5. DETACHED_PROCESS
    1. 阻止一个基于CUI的进程访问其父进程的控制台窗口,并告诉系统将它的输出发到一个新的控制台窗口。
    2. 如果一个基于CUI的进程是由另一个基于CUI的进程创建的,那么在默认的情况下,新进程将使用父进程的控制台窗口。
    3. 通过指定这个标志,新进程如果需要将输出发生到一个新的控制台窗口,那它就必须调用AllocConsole函数来创建它自己的控制台。
  6. CREATE_NEW_CONSOLE
    1. 指示系统为新进程创建一个新的控制台窗口。
    2. CREATE_NEW_CONSOLE|DETACHED_PROCESS,这样用会出错。
  7. CREATE_NO_WINDOW
    1. 指示系统不用为应用程序创建任何控制台窗口。
  8. CREATE_NEW_PROCESS_GROUP
    1. 修改用户使用Ctrl+C或Ctrl+Break时获得通知的进程列表。
    2. 比如按下这些建时,有多个CUI程序正在运行,系统将通知一个进程组中的所有进程,告诉它们用户打算中断当前操作。
    3. 在创建一个新的CUI程序时,如果指定了这个标志,就会创建一个新的进程组。组中的一个进程处于活动状态时,一旦用户按下了这些组合键,那系统就会向这个进程组中的进程发出通知。
  9. CREATE_DEFAULT_ERROR_MODE
    1. 向系统表明新进程不会继承父进程所用的错误模式。
  10. CREATE_SEPARATE_WOW_VDM
    1. 只有在运行16位Windows程序时才有用。
    2. 它指示系统创建一个单独的虚拟DOS机,并在这个虚拟DOS机上运行16位的Windows程序。
    3. 因为默认的情况下,所有16位Windows应用程序都在一个共享的虚拟DOS机上运行。
    4. 在独立的VDM中运行的好处是,如果程序奔溃了,它只需要杀死这个VDM,而在其他VDM中运行的程序仍然能接收到输入。
  11. CREATE_SHARED_WOW_VDM
    1. 只有在运行16位Windows程序时才有用。
    2. 默认的情况,所有16位Windows应用程序都在一个共享的虚拟DOS机上运行。
  12. CREATE_UNICODE_ENVIRONMENT
    1. 告诉系统子进程的环境块应包含Unicode字符。
    2. 进程的环境块默认包含的是ASCI字符串。
  13. CREATE_FORCEDOS
    1. 强制系统运行一个嵌入在16位OS/2应用程序中的MS-DOS应用程序。
  14. CREATE_BREAKAWAY_FROM_JOB
    1. 运行一个作业中的进程生成一个和作业无关的进程。
  15. EXTENDED_STARTUPINFO_PRESENT
    1. 向操作系统表明传给psiStartInfo参数的是一个STARTUPINFOEX结构。

参数七

  1. 这个参数指向一块内存,包含新进程要使用的环境字符串。
  2. 多数情况,给这个参数的传值都是NULL,将导致子进程继承其父进程使用的一组环境字符串。
    1. 具体是CreateProcess函数调用了GetEnvironmentStrings函数获取了主进程正在使用的环境字符串数据块的地址。
  3. 对于GetEnvironmentStrings这个函数返回的地址,不再需要的时候,应该使用FreeEnvironmentStrings来释放掉。

参数八

  1. 允许父进程设置子进程的当前驱动器和目录。
  2. 如果这个参数位NULL,则新进程的工作目录与生成新进程的应用程序一样。
  3. 如果不为空,要传入一个指向用0为终止符的字符串的指针。

参数九

  1. 指向一个STARTUPINFO结构体或STARTUPINFOEX结构体。

  1. Windows在创建新进程的时候使用这个结构体。
    如果没有把该结构体的内容清零,则成员将包含主调线程的栈上的垃圾数据。
    如果把这种垃圾数据传给了CreateProcess,可能会导致新进程有时能创建,有时不能创建。

参数十

  1. 该参数指向一个PROCESS_INFORMATIN结构体,CreateProcess函数在返回之前,会初始化这个结构体。
  2. 在CreateProcess函数返回之前,它会使用完全访问权限来打开进程对象和线程对象,并将各自的与进程相关联的句柄放入到结构体的hProcess和hThread里面。
  3. 当CreateProcess在内部打开这些对象时,每个对象的使用计数就变成了2。这意味着系统想要释放进程对象,进程必须终止(计数减1),父进程调用CloseHandle(计数减1)。
  4. 创建一个内核对象时,系统会为此对象分配一个独一无二的标识符,系统中没有别的进程内核对象会有相同的ID编号。
    线程内核对象也一样。创建一个线程内核对象时,此对象会被分配一个独一无二的、系统级别的ID编号。
    进程ID和线程ID分享同一个号码池。这意味着线程和进程不可能有相同的ID。
  5. 此外,一个对象被分配的ID绝对不会是0。Windows任务管理器将进程ID0和系统空闲进程相关联。
    实际上并没有系统空闲进程这样的东西。任务管理器创建这个虚构进程的目的是将其作为空闲线程的占位符。
    在没有别的线程正在运行时,系统就运行这个所谓空闲进程。
    空闲进程中的线程数量始终等于计算机的CPU的数量。所以,它始终代表未被真实进程使用的CPU使用率。
  6. CreateProcess返回之前,会将这些ID填充到PROCESS_INFOMATION结构体中的dwProcessId和dwThreadId成员中。
    如果应用程序要通过ID来追踪进程和线程,那么需要注意一点,就是进程和线程ID会被系统立即重用。
  7. 可以使用GetCurrentProcessId来获取当前进程的ID。
    也可以通过使用GetCurrentThreadId来获取当前正在运行的线程的ID。
    使用GetProcessId来获得指定句柄对应的一个进程的ID。
    使用GetThreadId来获得指定句柄对应的一个线程的ID。
    然后也可以通过调用GetProcessIdOfThread来获取其所在进程的ID。
  8. ToolHelp函数允许进程通过PROCESSENTRY32结构体来查询它的父进程。
    此结构内部有一个 th32ParentProcessID成员,这个成员能返回父进程的ID。
    但是需要注意的是,虽然系统确实会记住每个进程的父进程的ID,但由于ID会被立即重用,所以等我们获得父进程的ID的时候,这个ID标识很可能已经被重用了,可能是系统中允许的一个完全不同的进程了。
    所以,如果需要和一个进程的创建者通信的话,最好使用更持久的通信机制,比如内核对象、窗口句柄等。

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2021-11-20
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享