在Windows上模拟进程替换的理想方法是什么?

时间:2018-07-05 07:00:12

标签: windows winapi optimization process

因此,在feature request I filed against Node.js中,我正在寻找一种将当前Node进程替换为其他进程的方法。在Linux和朋友(实际上是任何POSIX兼容系统)中,这很容易:使用execve和朋友并称其为一日。但是很明显,这在Windows上不起作用,因为它只有CreateProcessexecve和朋友委托给complete with async behavior的用户)。它不像people haven't wanted to do similar,导致numerous duplicate questions on this site。 (这不是重复的,因为它在给定限制的情况下明确地寻求解决方法,而不仅仅是要求直接替换。)

流程替换有几个方面必须解决:

  1. 所有控制台I / O流都必须转发到新进程。
  2. 所有信号都需要透明地转发到新流程。
  3. 必须销毁旧过程中的数据,并回收尽可能多的资源。
  4. 所有先前存在的线程和子进程均应销毁。
  5. 除了打开的文件描述符和命名管道/等以外,所有已有的句柄都应销毁。
  6. 理想情况下,创建旧进程后,应将旧进程的内存保持在最低水平。
  7. 对于我的特定用例,保留进程ID并不重要。

对于我的特殊情况,有一些约束条件:

  1. 我可以控制初始流程的启动以及“流程替换”功能的位置。
  2. 我可以通过插件以任意堆栈偏移量加载任意本机代码。
    • 含义:我什至无法梦想跟踪malloc调用,句柄,线程操作或进程操作以跟踪并释放所有这些操作,因为DLL重写并不完全可行。
  3. 我无法控制何时调用“流程替换”。可以通过附加组件来调用它,它可以通过FFI的解释代码甚至可以递归地通过另一个附加组件来调用。甚至可以在附加初始化期间调用它。
    • 含义:即使我完美地装备了自己的身体,我也无法知道堆栈中的内容。并且重写其所有callpush都是不切实际的,并且由于明显的原因,这将是缓慢的。

所以,这就是我在想的主旨:使用类似于假蹦床的东西。

  1. 静态分配以下内容:
    1. 用于堆栈指针的单个指针。
    2. MAX_PATH + 1表示应用程序路径的字符+ '\0'
    3. MAX_PATH + 1表示当前工作目录路径+ '\0'的字符。
    4. 参数+ '\0'的32768个字符。
    5. 环境的32768个字符+ '\0'
  2. 在进入时,将全局堆栈指针引用设置为堆栈指针。
  3. 关于“替换”:
    1. 进行相关的进程清理并锁定/释放所有可能的内容。
    2. 将堆栈指针设置为存储的原始全局指针。
    3. 终止每个子线程。
    4. 杀死每个子进程。
    5. 免费each open handle
    6. 如果可能的话(即不在UWP程序中),For each heapdestroy it(如果不是default heap或临时堆(如果存在)的话)。
    7. 如果可能,请关闭each open handle
    8. 如果可能,请walk默认堆,并free与之关联的每个段。
    9. 使用静态分配的文件/参数/环境/等创建新进程。没有创建新窗口。
    10. 代理所有将来收到的信号,异常等,而无需以某种方式对此过程进行修改。 The standard signals are easy,但例外情况不多。
    11. 等待过程结束。
    12. 返回the process's exit code

这里的想法是使用基于过程的蹦床,并在启动新创建的蹦床时将当前过程的大小降至绝对最小。

但是在我不太熟悉Windows的地方,我可能在这里犯了很多错误。另外,上述内容似乎极其效率低下,并且在某种程度上来说,这对于内核可以释放一些内存页面,取消分配一堆内存句柄并为下一个内存分配一些内存而感到非常错误。过程。

因此,总而言之,在限制最少的Windows上模拟进程替换的理想方法是什么?

2 个答案:

答案 0 :(得分:0)

Windows有execve()和朋友,请参阅:

https://docs.microsoft.com/en-gb/cpp/c-runtime-library/reference/execve-wexecve

据我所知(除了Windows没有信号的事实),这些信号与POSIX版本具有相同的语义,所以您为什么还要寻找其他东西?

-或-

(可能更好,因为它可以让您更好地控制当前进程中打开的句柄是否应由正在生成的进程继承):

  1. CreateProcess (...);
  2. ExitProcess (...);

如果生成的过程需要海拔,则可能需要用ShellExecute()替换步骤1。

通过这种方式进行操作可让您访问“裸机” Windows API。 别无他法。您可能用来生成新进程的任何其他API(包括exec系列)都建立在CreateProcess()之上。

您所有的“蹦床”东西都……很奇怪。请不要去那里,它永远不会工作(无论它是什么)

答案 1 :(得分:0)

鉴于我不理解实际要求的内容,我肯定会以“执行者”这样的眼光看待“执行者”,尽管如此,我仍然会看待这种情绪。通过问自己这个问题:

如果进程-a被杀死并被几乎相同的进程-b取代-会注意到谁或什么?

任何拥有进程ID或进程句柄的东西都一定会注意到。这可以通过编写包装器应用程序来处理,该应用程序将加载第一个节点进程,并在受到提示时将其杀死并加载下一个进程。外部观察者会看到包装过程的句柄,并且id不变。

显然,这将切断馈入节点应用程序的stdin和stdout流。但是同样,包装器进程可以通过将相同的可继承句柄集传递给通过正确填充传递到STARTUPINFO的{​​{1}}结构而启动的每个节点进程来解决此问题。

Windows不支持信号,而MS C运行时伪造的信号均处理内部错误,唯一的错误是通过ctrl-C关闭了一个交互式控制台窗口,活动的Node.js应用程序可以确定此错误仍然可以获取-或可以从包装器传递,因为使用这种方法实际上不会在交互式控制台上运行节点应用程序。

除此之外,其他所有内容似乎都是Node.js应用程序的内部细节,因此不应影响任何第三方应用程序通过其stdin / stdout流与其认为是单节点应用程序的通信。