在 Windows 中以编程方式调用 "main" 函数
Call "main" function programmatically in Windows
我有一个第三方控制台应用程序。我需要 运行 它来自我的应用程序,但我不能 运行 它作为一个单独的进程(因为我需要使用它的依赖项:手动填充 Import tables,设置挂钩等)。所以我可能应该手动调用此 executable 的 main
函数。以下是我尝试执行此操作的方法:
- 使用
auto hMod = LoadLibrary("console_app.exe")
加载此 EXE
- 手动填写此 exe 的导入 table
- 获取此 EXE 的入口点并调用它
最后一步卡住了。
这是我尝试调用入口点的方式:
void runMain(HINSTANCE hInst)
{
typedef BOOL(WINAPI *PfnMain)(int, char*[]);
auto imageNtHeaders = ImageNtHeader(hInst);
auto pfnMain = (PfnMain)(DWORD_PTR)(imageNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)hInst);
char* args[] = { R"(<console_app_path>)", R"(arg1)", R"(arg2)" };
pfnMain(3, args);
}
有效。但它的工作方式就好像 没有 个参数。
我哪里错了?我如何 运行 executable inside 我的带参数的进程?谢谢。
更新:
我调查了我的特定第三方 exe 如何获取 cmd 参数并发现:
- 根本不导入
GetCommandLine
也不调用它
- 在
call _initterm
调用 argc
和 argv
后,参数可通过 cs:argc
和 cs:argv
获得(见下图)
- 我传递给我的主控制台应用程序的 CMD 参数被传输到
子 EXE 也是。
请您解释一下 _initterm
实际做什么以及 CMD 参数实际存储在哪里?
您正在调用应用程序的入口点,而不是 int main(int, char**)
。现在您可能已经读到 C++ 程序的入口点是 int main(int, char**)
但这只是 C++ 的观点。
Win32视角不同;入口点是 int (*)(void);
。 Visual Studio 链接器查找 int mainCRTStartup(void);
并使用它,除非您使用 /ENTRY
指定另一个入口点。 mainCRTStartup
的默认实现在调用main(argc,argv)
之前调用GetCommandLine()
填充argv[]
。 mainCRTStartup
中还有其他您可能希望发生的事情:运行 全局 ctors,初始化 CRT 状态,...
当然,这是假设另一个程序是用 Visual C++ 编译的,但无论它是用什么语言编写的,它都必须调用 GetCommandLine
。
现在,对于您的问题,这里有一个有趣的观察结果:GetCommandLine()
returns 一个 可写 指针。您可以覆盖现有的命令行。当然,如果 你 控制导入表,你决定 GetCommandLine
意味着什么。 (请记住,像往常一样有 A 和 W 变体)。
一个警告:MSVCRT 未设计为初始化两次,无论是静态版本还是 DLL 版本。所以实际上你不能使用它,那会很疼。
[编辑]
您的更新显示对 _initterm
的调用。正如我已经暗示的那样,这是一个 MSVCRT 函数。具体来说,
/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
...
/*
* routine in DLL to do initialization (in this case, C++ constructors)
*/
extern int __cdecl _initterm_e(_PIFV *, _PIFV *);
extern void __cdecl _initterm(_PVFV *, _PVFV *);
MSVCRT DLL 代表 EXE 调用 GetCommandLine()
。
可执行文件的入口点 (EP
) 没有参数 - 所以你不能直接用参数调用它。
普通应用程序通过解析命令行获取参数。 [w]mainCRTStartup
执行此操作 - 如果您有链接到 c/c++ 运行time 的控制台应用程序 - 这是真实的 EP
.
因此,如果您 Fill Import table of this exe manually
- 为 GetCommandLineA
and GetCommandLineW
函数设置异常 - 将其重定向到自我实现和 return 您的自定义命令行。
但如果应用程序使用 非静态 链接 CRT
它可以从 msvcrt.dll
导入 __getmainargs
or __wgetmainargs
or even _acmdln
, or _wcmdln
- 所以任务已经变得复杂。
并且您假设 relocs 在 EXE
中存在,您不处理 TLS
如果它存在,您不处理应用程序清单、可能的 dl 重定向等。
but I cannot run it as a separate process
这不是真的。您可以而且必须 运行 它作为单独的过程 - 这是最好的解决方案。
通过 CreateProcess
使用 CREATE_SUSPENDED
标志执行您的应用程序。在这里你可以自由轻松地设置你需要的任何命令行。您不需要手动且不完全正确加载 EXE
但系统会为您完成此任务。
创建进程后,您需要使用 QueueUserAPC
(but not CreateRemoteThread
!!) and finally call ResumeThread
将自身 DLL
注入其中
因此,您的 DLL
将在第一个 EXE
线程中加载并执行,就在应用程序 EP
之前 - 在这里您可以完成所有需要的任务
我有一个第三方控制台应用程序。我需要 运行 它来自我的应用程序,但我不能 运行 它作为一个单独的进程(因为我需要使用它的依赖项:手动填充 Import tables,设置挂钩等)。所以我可能应该手动调用此 executable 的 main
函数。以下是我尝试执行此操作的方法:
- 使用
auto hMod = LoadLibrary("console_app.exe")
加载此 EXE
- 手动填写此 exe 的导入 table
- 获取此 EXE 的入口点并调用它
最后一步卡住了。
这是我尝试调用入口点的方式:
void runMain(HINSTANCE hInst)
{
typedef BOOL(WINAPI *PfnMain)(int, char*[]);
auto imageNtHeaders = ImageNtHeader(hInst);
auto pfnMain = (PfnMain)(DWORD_PTR)(imageNtHeaders->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)hInst);
char* args[] = { R"(<console_app_path>)", R"(arg1)", R"(arg2)" };
pfnMain(3, args);
}
有效。但它的工作方式就好像 没有 个参数。
我哪里错了?我如何 运行 executable inside 我的带参数的进程?谢谢。
更新:
我调查了我的特定第三方 exe 如何获取 cmd 参数并发现:
- 根本不导入
GetCommandLine
也不调用它 - 在
call _initterm
调用argc
和argv
后,参数可通过cs:argc
和cs:argv
获得(见下图) - 我传递给我的主控制台应用程序的 CMD 参数被传输到 子 EXE 也是。
请您解释一下 _initterm
实际做什么以及 CMD 参数实际存储在哪里?
您正在调用应用程序的入口点,而不是 int main(int, char**)
。现在您可能已经读到 C++ 程序的入口点是 int main(int, char**)
但这只是 C++ 的观点。
Win32视角不同;入口点是 int (*)(void);
。 Visual Studio 链接器查找 int mainCRTStartup(void);
并使用它,除非您使用 /ENTRY
指定另一个入口点。 mainCRTStartup
的默认实现在调用main(argc,argv)
之前调用GetCommandLine()
填充argv[]
。 mainCRTStartup
中还有其他您可能希望发生的事情:运行 全局 ctors,初始化 CRT 状态,...
当然,这是假设另一个程序是用 Visual C++ 编译的,但无论它是用什么语言编写的,它都必须调用 GetCommandLine
。
现在,对于您的问题,这里有一个有趣的观察结果:GetCommandLine()
returns 一个 可写 指针。您可以覆盖现有的命令行。当然,如果 你 控制导入表,你决定 GetCommandLine
意味着什么。 (请记住,像往常一样有 A 和 W 变体)。
一个警告:MSVCRT 未设计为初始化两次,无论是静态版本还是 DLL 版本。所以实际上你不能使用它,那会很疼。
[编辑]
您的更新显示对 _initterm
的调用。正如我已经暗示的那样,这是一个 MSVCRT 函数。具体来说,
/***
*crtexe.c - Initialization for console EXE using CRT DLL
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
...
/*
* routine in DLL to do initialization (in this case, C++ constructors)
*/
extern int __cdecl _initterm_e(_PIFV *, _PIFV *);
extern void __cdecl _initterm(_PVFV *, _PVFV *);
MSVCRT DLL 代表 EXE 调用 GetCommandLine()
。
可执行文件的入口点 (EP
) 没有参数 - 所以你不能直接用参数调用它。
普通应用程序通过解析命令行获取参数。 [w]mainCRTStartup
执行此操作 - 如果您有链接到 c/c++ 运行time 的控制台应用程序 - 这是真实的 EP
.
因此,如果您 Fill Import table of this exe manually
- 为 GetCommandLineA
and GetCommandLineW
函数设置异常 - 将其重定向到自我实现和 return 您的自定义命令行。
但如果应用程序使用 非静态 链接 CRT
它可以从 msvcrt.dll
导入 __getmainargs
or __wgetmainargs
or even _acmdln
, or _wcmdln
- 所以任务已经变得复杂。
并且您假设 relocs 在 EXE
中存在,您不处理 TLS
如果它存在,您不处理应用程序清单、可能的 dl 重定向等。
but I cannot run it as a separate process
这不是真的。您可以而且必须 运行 它作为单独的过程 - 这是最好的解决方案。
通过 CreateProcess
使用 CREATE_SUSPENDED
标志执行您的应用程序。在这里你可以自由轻松地设置你需要的任何命令行。您不需要手动且不完全正确加载 EXE
但系统会为您完成此任务。
创建进程后,您需要使用 QueueUserAPC
(but not CreateRemoteThread
!!) and finally call ResumeThread
DLL
注入其中
因此,您的 DLL
将在第一个 EXE
线程中加载并执行,就在应用程序 EP
之前 - 在这里您可以完成所有需要的任务