(1)
之前在调试exe时感觉很奇怪,为什么Entry Point并非直接进入到main函数。
举例来说,如果将一段空的C代码build为exe:
void main(){ }
编译环境为:VC6 release。
再将该exe文件进行反汇编,那么从EP开始的代码部分大概形如:
ASM代码
push ebp
……
一段汇编代码
……
call Main函数
……
另一段汇编代码
……
retn
也就是说, 在执行一个exe文件时,总是要先运行一些指令,才能够开始调用Main函数。同样,当main函数执行完毕后,还需要运行一些指令完成收尾。为了弄清楚main函数调用前这些代码以及main函数执行后的代码,需要从CRT(C
RunTime
,C的运行时库)开始研究。
(2)
Visual Studio自带了CRT的源码,VC6中CRT位于“VC98\CRT\SRC”目录。CRT 中的 crt0.c 文件规定了一整套C程序固定的执行流程。在 crt0.c 开头的注释部分有如下描述:
This the actual startup routine for apps. It calls the user's main routine [w]main() or [w]WinMain after performing C Run-Time Library initialization.
大概意思是,当C Run-Time Library 完成了初始化工作之后,才开始执行用户自定义的main
函数、WinMain
函数。
crt0.c 为了规定C程序执行的流程,定义了函数mainCRTStartup
和WinMainCRTStartup
,这两个函数也被称作启动函数
。它们的作用在函数注释中已经写的很清楚:
This routine does the C runtime initialization, calls main(), and then exits.
mainCRTStartup函数大概形如:
void mainCRTStartup(void){
int mainret;
……
__try {
……
mainret = main(__argc, __argv, _environ); //在这里调用用户写的main函数
exit(mainret);
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
_exit( GetExceptionCode() );
}
}
当Windows系统执行一个C程序时,真正首先执行的是(win)mainCRTStartup函数。mainCRTStartup首先进行了一系列准备工作,例如heap的初始化、IO的初始化、获得命令行参数等等。当所有的准备工作都完成之后,再去调用用户自定义的main函数。最后,执行exit函数退出程序。因此对于exe,(win)mainCRTStartup函数才是真正的Entry point。
另外,crt0.c 中还有相似的函数:wWinMainCRTStartup、wmainCRTStartup,它们是Unicode版本程序的EP,这里可以暂时不用去管。windows为了照顾Unicode程序,很多API都提供了两种版本,一种是针对ANSI字符,还有一种是针对Unicode字符。
这四个函数是放在一起定义的,crt0.c 中的源码如下:
#ifdef _WINMAIN_ /* _WINMAIN_被定义时,表示GUI程序 */
#ifdef WPRFLAG /* WPRFLAG被定义时,表示Unicode字符 */
void wWinMainCRTStartup(
#else
void WinMainCRTStartup(
#endif
#else /* 下面为CUI程序 */
#ifdef WPRFLAG
void wmainCRTStartup(
#else
void mainCRTStartup(
#endif
#endif
void){
……
}
可以根据上面的源代码总结如下:
mainCRTStartup |
Console apps |
ANSI |
wmainCRTStartup |
Console apps |
Unicode |
WinMainCRTStartup |
Windows apps |
ANSI |
wWinMainCRTStartup |
Windows apps |
Unicode |
(3)
来具体看一下(win)mainCRTStartup函数。下面将mainCRTStartup函数的主要语句摘录了出来,这里去除了一些条件编译的代码,忽略了windows apps(_WINMAIN_)、Unicode版本的程序(WPRFLAG)、多线程(_MT),仅仅分析Console apps。
int mainret;
// 获取Win32的版本
_osver = GetVersion();
_winminor = (_osver >> 8) & 0x00FF ;
_winmajor = _osver & 0x00FF ;
_winver = (_winmajor << 8) + _winminor;
_osver = (_osver >> 16) & 0x00FFFF ;
// 创建了一个属于该进程的私有堆
if ( !_heap_init(0) )
fast_error_exit(_RT_HEAPINIT);
__try {
// 初始化低级IO
_ioinit();
// 获取命令行缓冲区指针
_acmdln = (char *)GetCommandLineA();
// 获取环境变量指针
_aenvptr = (char *)__crtGetEnvironmentStringsA();
// 设置argv参数
_setargv();
// 设置环境变量
_setenvp();
// 初始化C数据
_cinit();
__initenv = _environ;
// 调用main函数
mainret = main(__argc, __argv, _environ);
exit(mainret);
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
_exit( GetExceptionCode() );
}
从这段代码可以大体窥视出 C 程序的启动流程:
1.获取WIN32平台的版本
2.调用_heap_init函数创建一个私有堆
3.初始化低级IO
4.获取命令行缓冲区、环境变量的指针
5.设置命令行参数与环境变量
6.初始化C数据
7.调用main函数
8.将main函数的调用结果传入exit 退出程序
分享到:
相关推荐
请注意,改变Entry Point可能会影响到C++运行时库的初始化和清理过程,因此在进行此类更改时需要确保你的代码能够正确处理这些变化。如果自定义的入口点没有调用`mainCRTStartup`或`WinMainCRTStartup`,可能会导致...
3. 加载完成和执行main函数:加载完成后,将PC(EIP)设定指向Entry point(即符号_start处),最终执行main函数,以启动程序执行。 ELF头信息 ELF头信息是可执行文件的元数据,用于描述可执行文件的结构和组织...
这将允许你从命令行或其他应用程序接口调用`read_smbios_uuid`函数,从而获取系统的UUID信息。 总的来说,理解SMBIOS和UUID在系统管理中的作用,以及如何使用C语言来处理它们,是系统级编程和硬件诊断的重要技能。...
2. **主入口点(Main Entry Point)** `main`通常指的是程序的主要执行入口。在`fn-args-main`中,`main`可能是库的核心功能,负责接收用户输入,处理参数,然后启动相应的功能。这个主入口点可能会调用一系列内部...
OSE 的全局变量与初始化OSE 的 Main 函数就调用一个函数start_OSE。在 start_OSE 函数中首先调用 odo_config_start_handler1 对系统的硬件进行初始化。再调用 odo_init_os 进行 OSE 操作系统初始化。odo_init_os 这...
通常,程序入口函数(entry point)是整个系统开始运行的地方,它负责调用不同模块,初始化系统,以及处理命令行参数。在Python中,`if __name__ == '__main__':` 是定义程序入口的常见方式,这样可以确保只有在直接...
IAT Hook是一种代码注入技术,用于修改目标程序的IAT,将函数调用重定向到自定义的处理函数,而不是原始的函数实现。这通常用于调试、监控、安全检测或恶意软件行为。例如,一个常见的应用是防止病毒通过替换系统API...
显式调用则是通过运行时动态加载 DLL,并手动调用其中的函数。 - **加载 DLL**:使用 `LoadLibrary` 函数加载 DLL 文件。 - **获取函数地址**:使用 `GetProcAddress` 函数获取导出函数的地址。 - **释放 DLL**:...
3. **服务初始化函数(Service Entry Point)**:在创建服务时,操作系统会调用这个函数,通常是`WinMain()`或`DllMain()`。 4. **服务注册(Registering the Service)**:通过`CreateService()`函数将服务添加到...
4. **堆栈段(Stack Segment)**:用于存储函数调用时的参数、返回地址等临时数据。 5. **程序入口点(Entry Point)**:程序开始执行的地方。 6. **DOS中断调用(DOS Integers)**:DOS提供了一系列中断调用来执行...
3. **启动函数调用**:在`vtst_rt_my_first_app_launch`函数中调用实际的启动函数,以显示应用。 #### 三、定义应用类(Step3: Define the App Class: VappMyFirstApp) 定义应用的具体类,以便更好地管理和控制...
1. **Windows子系统设置错误**:当你试图运行一个原本应该作为Windows应用程序的项目,但链接器设置为使用控制台子系统时,会遇到`unresolved external symbol _main`的错误。解决方法是进入项目的属性设置,选择...
可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12 数组大小 13 1.23 能否声明和传入数组大小一致的局部数组,或者由...
- 需要在.pro文件中添加`QT += widgets`,以便能够在main函数中引入`QApplication`。 - **实验二:纯代码方式编写Hello World**:通过纯代码的方式构建QT程序,了解不同构建方法的区别。 - **实验三:多窗口程序**...
o 5.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? o 5.9 我怎样把一个 int 变量转换为 char * 型?我试了类型转换, 但是不行。 * 6. 空 (null) 指针 o 6.1 臭名昭著的空指针到底是什么? o 6.2 ...
对于未经过封装的程序而言,OEP通常是程序的主函数(main函数)的地址。在Molebox封装的情况下,OEP被修改,因此需要通过特定的技术手段来恢复它。 ### 3. 脚本解析 #### 脚本流程概述 脚本首先检查当前环境是否...
同时,可能还需要调整入口点(Entry Point)以便程序运行时调用对话框代码。这通常涉及到对PE文件的二进制级操作,需要谨慎处理,避免破坏原始文件的完整性。 在提供的压缩包文件6d17ec4037f449ee902613fec0d6cc12...
-128 到 127 -32,768 到 32,767 -2,147,483,648 2,147,483,647 -9,223,372,036,854,775,808 到 第1页 C#(WINFORM)学习 long val2 = 34L; 到 9,223,372,036,854,775,807 byte ushort 8 位无符号整型 16 位无符号...
printf("Mount Point: %s\n", mnt_entry->mnt_dir); printf("File System Type: %s\n", mnt_entry->mnt_type); // 使用statfs()获取更详细的文件系统信息 // ... } endmntent(mnt_file); return 0; } ``` ...