VC 在调用main函数之前的操作


title: VC 在调用main函数之前的操作 tags: [VC++, 反汇编, C++实现原理] date: 2018-09-16 10:36:23 categories: VC++反汇编分析 keywords: VC++, 反汇编, C++实现原理, main函数调用, VC 运行环境初始化 --- 在C/C++语言中规定,程序是从main函数开始,也就是C/C++语言中以main函数作为程序的入口,但是操作系统是如何加载这个main函数的呢,程序真正的入口是否是main函数呢?本文主要围绕这个主题,通过逆向的方式来探讨这个问题。本文的所有环境都是在xp上的,IDE主要使用IDA 与 VC++ 6.0。为何不选更高版本的编译器,为何不在Windows 7或者更高版本的Windows上实验呢?我觉得主要是VC6更能体现程序的原始行为,想一些更高版本的VS 它可能会做一些优化与检查,从而造成反汇编生成的代码过于复杂不利于学习,当逆向的功力更深之后肯定得去分析新版本VS 生成的代码,至于现在,我的水平不够只能看看VC6 生成的代码 首先通过VC 6编写这么一个简单的程序

#include <stdio.h>
#include <windows.h>
#include <tchar.h>

int main()
{
    wchar_t str[] = L"hello world";
    size_t s = wcslen(str);
    return 0;
}

通过单步调试,打开VC6 的调用堆栈界面,发现在调用main函数之前还调用了mainCRTStartup 函数:

在VC6 的反汇编窗口中好像不太好找到mainCRTStartup函数的代码,因此在这里改用IDA pro来打开生成的exe,在IDA的 export窗口中双击 mainCRTStartup 函数,代码就会跳转到函数对应的位置。

它的代码比较长,刚开始也是进行函数的堆栈初始化操作,这个初始化主要是保存原始的ebp,保存重要寄存器的值,并且改变ESP的指针值初始化函数堆栈,这些就不详细说明了,感兴趣的可以去看看我之前写的关于函数反汇编分析的内容: C函数原理

在初始化完成之后,它有这样的汇编代码

.text:004010EA                 push    offset __except_handler3
.text:004010EF                 mov     eax, large fs:0
.text:004010F5                 push    eax
.text:004010F6                 mov     large fs:0, esp

这段代码主要是用来注册主线程的的异常处理函数的,为什么它这里的4行代码就可以设置线程的异常处理函数呢?这得从SEH的结构说起。

每个线程都有自己的SEH链,当发生异常的时候会调用链中存储的处理函数,然后根据处理函数的返回来确定是继续运行原先的代码,还是停止程序还是继续将异常传递下去。这个链表信息保存在每个线程的NT_TIB结构中,这个结构每个线程都有,用来记录当前线程的相关内容,以便在进行线程切换的时候做数据备份和恢复。当然不是所有的线程数据都保存在这个结构中,它只保留部分。该结构的定义如下:

typedef struct _NT_TIB
{
     PEXCEPTION_REGISTRATION_RECORD ExceptionList;
     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;
     union
     {
          PVOID FiberData;
          ULONG Version;
     };
     PVOID ArbitraryUserPointer;
     PNT_TIB Self;
} NT_TIB, *PNT_TIB;

这个结构的第一个参数是一个异常处理链的链表头指针,链表结构的定义如下:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
     PEXCEPTION_REGISTRATION_RECORD Next;
     PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

这个结构很简单的定义了一个链表,第一个成员是指向下一个节点的指针,第二个参数是一个异常处理函数的指针,当发生异常的时候会去调用这个函数。而这个链表的头指针被存到fs寄存器中

知道了这点之后再来看这段代码,首先将异常函数入栈,然后将之前的链表头指针入栈,这样就组成了一个EXCEPTION_REGISTRATION_RECORD结构的节点而这个节点的指针现在就是ESP中保存的值,之后再将链表的头指针更新,也就是最后一句对fs的重新赋值,这是一个典型的使用头插法新增链表节点的操作。通过这样的几句代码就向主线程中注入了一个新的异常处理函数。

之后就是进行各种初始化的操作,调用GetVersion 获取版本号,调用 __heap_init 函数初始化C运行时的堆栈,这个函数后面有一个 esp + 4的操作,这里可以看出这个函数是由调用者来做堆栈平衡的,也就是说它并不是Windows提供的api函数(API函数一般都是stdcall的方式调用,并且命名采用驼峰的方式命名)。调用GetCommandLineA函数获取命令行参数,调用 GetEnvironmentStringsA 函数获取系统环境变量,最后有这么几句话:

.text:004011B0                 mov     edx, __environ
.text:004011B6                 push    edx             ; envp
.text:004011B7                 mov     eax, ___argv
.text:004011BC                 push    eax             ; argv
.text:004011BD                 mov     ecx, ___argc
.text:004011C3                 push    ecx             ; argc
.text:004011C4                 call    _main_0

这段代码将环境变量、命令行参数和参数个数作为参数传入main函数中。 在C语言中规定了main函数的三种形式,但是从这段代码上看,不管使用哪种形式,这三个参数都会被传入,程序员使用哪种形式的main函数并不影响在VC环境在调用main函数时的传参。只是我们代码中不使用这些变量罢了。

到此,这篇博文简单的介绍了下在调用main函数之前执行的相关操作,这些汇编代码其实很容易理解,只是在注册异常的代码有点难懂。最后总结一下在调用main函数之前的相关操作

  1. 注册异常处理函数
  2. 调用GetVersion 获取版本信息
  3. 调用函数 __heap_init初始化堆栈
  4. 调用 __ioinit函数初始化啊IO环境,这个函数主要在初始化控制台信息,在未调用这个函数之前是不能进行printf的
  5. 调用 GetCommandLineA函数获取命令行参数
  6. 调用 GetEnvironmentStringsA 函数获取环境变量
  7. 调用main函数

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java反射在JVM的实现

反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活...

24430
来自专栏PHP在线

PHP7标量类型声明RFC

一、总结 该RFC建议添加4种新的标量类型声明:int,float,string和bool,这些类型声明将会和PHP原来的机制保持一致的用法。RFC 更推荐给每...

39850
来自专栏Python专栏

Python | 19个优雅的Python编程技巧

http://lovesoo.org/pythonic-python-programming.html

35130
来自专栏李鹏的专栏

Java 虚拟机的内存结构

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。

27110
来自专栏Java职业技术分享

可能是把Java内存区域讲的最清楚的一篇文章

对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不...

14000
来自专栏微信终端开发团队的专栏

C# 内存管理机制及 WP 内存泄漏定位方法

C#内存管理机制及WP内存泄漏定位方法 一、C#的内存管理机制 1. 托管资源与非托管资源 什么是托管资源?托管资源通俗的理解就是,把资源交给.net去管理,这...

45570
来自专栏企鹅号快讯

PHP中被忽略的性能优化利器:生成器

如果是做Python或者其他语言的小伙伴,对于生成器应该不陌生。但很多PHP开发者或许都不知道生成器这个功能,可能是因为生成器是PHP5.5.0才引入的功能,也...

399140
来自专栏技术墨客

JVM与字节码——类的方法区模型 原

这是一段平凡得不能再平凡的Java代码,稍微有点编程语言入门知识的人都能理解它表达的意思:

7920
来自专栏猿人谷

总结c++ primer中的notes

C++ Primer, Fourth Edition (中英文)下载地址:http://download.csdn.net/detail/ace_fei/416...

21990
来自专栏linux驱动个人学习

Linux 内存池【转】

19050

扫码关注云+社区

领取腾讯云代金券