讨论一下使用mingw生成的针对Windows下的可执行文件的运行逻辑
前言
在本文中,我将使用x86汇编和简易的小小程序片段,其中将会使用最新的C++ 23标准进行代码编写。所以当你开始阅读这篇短文时,希望你有理解C++ 23,内存,指针和标准Win32 API的基本概念的能力。
代码段
#include <iostream>
int main() {
auto string = "Hello, World! ";
auto integer = 114514;
std::cout << string << integer << std::endl;
system("pause");
return 0;
}
在这段简单的代码中,很明显这只是一个利用了C++新特性的HelloWorld程序,不难看出它最终会在命令提示符打出”Hello, World! 114514”这个字符串和整数并且传输pause指令给命令提示符,让其暂停运行并显示“按任意键继续”的提示。
提示:在编译时候记得加上-static给CMAKE_EXE_LINKER_FLAGS,这样才可以让编译出的可执行文件独立运行
入口点
当你将编译出的可执行文件拖入IDA的窗口之后,当IDA自动分析完成整个程序的互相调用,会给出一张十分复杂的调用图(如下图)。
你可能会很好奇为什么,只是一个简单的Hello,World程序而已为什么会存在这么多的调用,并没有在代码中体现出来哇?
放大其中任意一个代码块(如下图)你会发现,他们做的那么多事情,无非就是在准备运行现场,错误处理等现代编程语言自带的功能,在现如今的编程中,这些以往繁琐的操作,都将会被自动处理。
在这个简单的程序中,入口点是main函数,我们手工构建了一个主函数。在IDA自动生成的调用图中,可以看到许多Win32 API函数被调用。这些函数包括获取当前进程的句柄、获取控制台窗口的句柄、向标准输出打印信息、调用系统命令行窗口实现以实现等待用户按键等。这些函数是必要的,因为它们提供了程序运行所必需的环境和支持。
main?WinMainCRTStartup和mainCRTStartup?
WinMainCRTStartup和mainCRTStartup是CRT(C Runtime)库提供的启动代码。它们都是程序入口点,但是它们的参数和返回值类型不同,适用于不同的应用程序类型以及适配了不同平台的实现。
由上图可知mainCRTStartup是用于控制台应用程序的入口点,它不带任何参数执行。mainCRTStartup会进行一些必要的初始化工作,然后调用main函数。在main函数执行完毕后,它会自动退出程序,并返回main函数的返回值作为程序的退出码。
在Win的部分中,由于当前程序只有命令行界面,故Win部分的实际实现也与标准入口点相同,其中混合部分Windows特有处理。
反编译的伪代码
在IDA中,使用F5可以反编译出伪代码,看以上的伪代码思路要比直接看汇编效率高出很多,非常方便的可以看出这是一个典型的入口点函数,执行的操作也很清晰明了。
总结
在这篇文中,可以大概理解到一个纯命令行程序在Windows下的简易执行逻辑。
1.初始化C/C++运行时环境,包括初始化全局变量、静态变量等。
2.调用MainCRTStartup/WinMainCRTStartup函数,该函数初始化Win32 API环境,并调用Main函数。
3.设置标准输入、标准输出和标准错误流,以便程序可以向控制台输出信息。
4.初始化异常处理机制,包括设置SEH(结构化异常处理)机制*Windows特有。
总之,在现代的程序环境下,大部分与系统底层交互的部分,都存在完备的已封装功能,我们只需要直接调用即可。然而,在一定程度上了解这些内容也是有些必要,对底层更为了解,也更好的优化自己的作品。