• 认识启动函数,找到用户入口


    Linux 0.13版本 1.5W行

    < >和"" 影响搜索顺序
    Linux 0.13版本 1.5W行

    代码规范

    华为C语言编程规范http://t.csdn.cn/h5OOZ


    检查意识

    build.bat脚本

    del *.obj
    del *.exe
    cl /c /W3 /WX /P hello.c /P:常用于多个包含文件出了问题的时候或复杂宏的时候就可以 使用P 生成.i文件审查该文件
    link hello.obj
    pause #暂停看结果
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于C语言的输入输出重定向

    1、一种是在程序中设置:使用freopen将输入输出重定向。

    C语言的标准输入输出为stdin和stdout,这两个变量的类型为FILE*类型,也就是说,标准输入输出操作,其本质还是文件操作。
    当需要重定向时,可以调用
    :::info
    #include <stdio.h>

    freopen(“d:\data_in.txt”,“r”,stdin); 将输入定向为文件d盘下的文件data_in.txt文件

    (linux下的路径表示有点差别:freopen(“/data_in.txt”,“r”,stdin);表示根目录下的data_in.txt文件。)

    freopen(“d:\data_out.txt”,“w”,stdout); 将输出定向到d盘下的data_out.txt目录。
    :::

    当调用该函数时,需要引用头文件stdio.h,在使用freopen()之后的标准输出或输入会重新定向,而之前的不会变。

    2、另一种是运行时重定向:

    在命令行输入: myprog.exe > X:\data_out.txt (在myprog.exe所在的文件夹下)

    可以将输出重定向到X盘下的data_out.txt文件中,这时程序中所有的输出都将重定向到该文件,除非在程序中使用了freopen()函数,如果是这样的话,程序中freopen()函数之前的输出重定向到data_out.txt文件,而freopen()之后的将定向到freopen()指定的文件中。
    :::info
    重定向输入的方式也类似:在命令行输入 myprog.exe < data_in.txt
    :::
    当程序中依次出现scanf()、getchar() 输入函数时,就会自动的依次从文件data_in.txt中读取对应长度的文本(以字节算)。需要注意的是文件的结束标记为EOF

    例如要读取文件中的所有文本可以写如下代码:

    while( getchar() != EOF)
    {   
        putchar();    
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以重定向到文件、ftp(文件服务器)、局域网、打印机

    重定向输出

    >
        hello > c:/test.txt
    
    • 1
    • 2

    重定向输入

    <
    
    • 1

    stdout stdin stderr(输出到标准设备、显示器、用来做日志)

    #include <stdio.h>//如果是标准库或者是官方库使用<>;自己编写的自定义库使用" "
    
    
    
    //<>和"" 影响搜索顺序
    //Linux 0.13版本 1.5W行
    //代码规范
    //检查意识
    
    
    int main(int argc,char*argv[])//main函数的返回值就是进程的返回值
    {
        //格式化输出到标准输出设备(stdout)
        
        int n = printf("hello\r\n"); //可变参数的函数 //返回值:成功输出的字节数,负数为失败
        printf("%d\r\n",n);
        // fprintf(stderr,"hello world!\n");
    
        return 0; //exit(0)->ExitProcess(0)
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    程序的真正入口

    main或WinMain函数需要有一个调用者,在它们被调用前,编译器其实已经做了很多事情,所以main或WinMain是“语法规定的用户入口”,而不是“应用程序入口”。

    入 口 代 码 其 实 并 不 是 main 或 WinMain 函 数 , 通 常 是 mainCRTStartup 、wmainCRTStartup 、 WinMainCRTStartup 或 wWinMainCRTStartup , 具 体 视 编 译 选 项 而 定 。 这才是应用程序的实际启动程序。它调用用户的main例程[w]main()或[w]WinMain执行C运行时库初始化。 其 中 mainCRTStartup 和 wmainCRTStartup 是控制台环境下多字节编码 和 Unicode 编 码 的 启 动 函 数 , 而 WinMainCRTStartup 和 wWinMainCRTStartup 则是Windows环境下多字节编码和 Unicode 编码的启动函数。在开发过程中,C++也允许程序员自己指定入口。

    mainCRTStartup的详解

    Get the full Win32 version 获取完整的Win32版本

    initialize heap 初始化堆

    GetCommandLineA函数:获取命令行参数信 息的首地址。

    _crtGetEnvironmentStringsA函数:获取环境变 量信息的首地址。

    _setargv函数:此函数根据GetCommandLineA 获取命令行参数信息的首地址并进行参数分析, 将分离出的参数的个数保存在全局变量_argc中, 将分析出的每个命令行参数的首地址存放在数组 中,并将这个字符指针数组的首地址保存在全局 变量_argv中。这样就得到了命令行参数的个数, 以及命令行参数信息。

    _setenvp函数:该函数就根据 _crtGetEnvironmentStringsA 函数来 获取环境变量信息的首地址,然后进行分析,就会得到的每条环境变量字符串的首地址并存放在字符指针数组中,然后将这个数组的首地址存放在全局变量env中。

    当调用main函数的时候,就会把_argc、_argv、env这 三个全局变量作为参数,以栈传参方式传递到 main函数中。
    initialize multi-thread 初始化多线程

    initialize lowio 初始化低级io

    do C data initialize 做C数据初始化

    call run time initializer 调用运行时初始化器

    
    //预编译宏
    #else/*_WINMAIN_*/
    #ifdef WPRFLAG
    //宽字符版控制台启动函数
    void wmainCRTStartup(
    #else/*WPRFLAG*/
    //多字节版控制台启动函数
    void mainCRTStartup(
    #endif/*WPRFLAG*/
    #endif/*_WINMAIN_*/
    void{
    //获取版本信息
    _osver=GetVersion();
    _winminor=(_osver>>8)&0x00FF;
    _winmajor=_osver&0x00FF;
    _winver=(_winmajor<<8+_winminor;
    _osver=(_osver>>16)&0x00FFFF//堆空间初始化过程,在此函数中,指定了程序中堆空间的起始地址
    //_MT是多线程标记
    #ifdef_MT
    if(!_heap_init(1))
    #else/*_MT*/
    if(!_heap_init(0))
    #endif/*_MT*/
    fast_error_exit(_RT_HEAPINIT);
    //初始化多线程环境
    #ifdef_MT
    if(!_mtinit())
    fast_error_exit(_RT_THREAD);
    #endif/*_MT*/
    __try{
    //宽字符处理代码略
    //多字节版获取命令行
    _acmdln=char*)GetCommandLineA();
    //多字节版获环境变量信息
    _aenvptr=char*)
    __crtGetEnvironmentStringsA();
    //多字节版获取命令行信息
    _setargv();
    //多字节版获取环境变量信息
    _setenvp();
    #endif/*WPRFLAG*/
    //初始化全局数据和浮点寄存器
    _cinit();
    //窗口程序处理代码略
    //宽字符处理代码略
    //获取环境变量信息
    _initenv=_environ;
    //调用main函数,传递命令行参数信息
    mainret=main(_argc,_argv,_environ);
    #endif/*WPRFLAG*/
    #endif/*_WINMAIN_*/
    //检查main函数返回值执行析构函数或atexit注册的函数指针,并结束程序
    exit(mainret);
    }
    //退出结束代码略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    main函数有如下 特征是:它有3个参数,分别为命令行参数个数、 命令行参数信息和环境变量信息,而且它是启动函数中唯一的具有3个参数的函数。

    在VC++6.0中,main函数被调用前要先调用的 函数如下:

    GetVersion()

    _heap_init()

    GetCommandLineA()

    _crtGetEnvironmentStringsA()

    _setargv()

    _setenvp()

    _cinit()

    这些函数调用结束后就会调用main函数。 根 据main函数调用的特征,会将3个参数_argc、_argv、env压入栈内作为函数的参数。

    extern "C" int mainCRTStartup()
    {
    return __scrt_common_main();
    }
    static __forceinline int __cdecl __scrt_common_main()
    {
    //初始化缓冲区溢出全局变量
    __security_init_cookie();
    return __scrt_common_main_seh();
    }
    static __declspec(noinline) int __cdecl
    __scrt_common_main_seh()
    {
    //用于初始化C语法中的全局数据
    if (_initterm_e(__xi_a, __xi_z) != 0)
    return 255;
    //用于初始化C++语法中的全局数据
    _initterm(__xc_a, __xc_z);
    //初始化线程局部存储变量
    _tls_callback_type const* const tls_init_callback =
    __scrt_get_dyn_tls_init_callback();
    if (*tls_init_callback != nullptr &&
    __scrt_is_nonwritable_in_current_image(tls_init_callback))
    {
    (*tls_init_callback)(nullptr, DLL_THREAD_ATTACH, nullptr);
    }
    //注册线程局部存储析构函数
    _tls_callback_type const * const tls_dtor_callback =
    __scrt_get_dyn_tls_dtor_callback();
    if (*tls_dtor_callback != nullptr &&
    __scrt_is_nonwritable_in_current_image(tls_dtor_callback))
    {
    _register_thread_local_exe_atexit_callback(*tls_dtor_callba
    ck);
    }
    //初始化完成调用main()函数
    int const main_result = invoke_main();
    //main()函数返回执行析构函数或atexit注册的函数指针,并结束程序
    if (!__scrt_is_managed_app())
    exit(main_result);
    }
    static int __cdecl invoke_main()
    {
    //调用main函数,传递命令行参数信息
    return main(__argc, __argv,
    _get_initial_narrow_environment());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    什么时候检查printf的返回值?(检查意识)

    1、只要数据出内存了,(磁盘、局域网、)就要考虑重定向,做一步检查一步

    使用winhex查看地址初始化的值以及地址的值被修改的数据

    测试代码:

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
    /*
        规范 写一行,规范一行
    
        比如对于学生总数而言
        int nStudentCount = 0;
        float fStudentCount = 0.0f; or flStudentCount;
        double dblStudentCount =0.0;
        char cStudentCount = '\0';
        short int snStudentCount = 0;
        int *pnStudentCount = NULL;
        void *pv;
    */
        //0x0018ff44
        //未初始化的局部变量上回使:用的这个地址的代码留下来的值
        int n = 0;
        //从标准输入设备格式化输入到指定的内存地址上
        //小尾方式: 低数据位存放在低地址处,高数据位存在高地址处
        //大尾方式: 高数据位存放在低地址处,低数据位存在高地址处
        //计算机设计初期的体系结构决定
        //0xc0000005内存访问异常
        printf("%08x\r\n",&n);
        system("pause");
    
        scanf("%d",&n);
        printf("%d\r\n",n);
        system("pause");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    首先先运行程序,注意要使程序不要主动退出,要加上 system(“pause”); 运行后如图所示

    使用winhex打开RAM 选择运行程序的进程、全部内存

    把刚才输出的地址输入进去,进行查找跳转

    显示如下

    继续运行程序输出值

    返回winhex查看可知,地址的值已被修改

    被修改为 E7030000

    原因:

    小尾方式: 低数据位存放在低地址处,高数据位存在高地址处
    大尾方式: 高数据位存放在低地址处,低数据位存在高地址处
    计算机设计初期的体系结构决定

    本博文参考《C++反汇编与逆向分析技术揭秘》

  • 相关阅读:
    工业互联网的概念、体系架构及关键技术
    贪心算法证明问题
    【第十一篇】- Git Gitee
    企业是否需要单独一套设备管理系统?
    GIS工具maptalks开发手册(二)03-02——示例之json格式添加绘制工具、渲染点、文字和多个面
    LVS+DR部署
    数的三次方根
    ElementUI增删改的实现及表单验证
    node工程中package.json文件作用是什么?里面的^尖括号和~波浪号是什么意思?
    算法进阶——字符串的排列
  • 原文地址:https://blog.csdn.net/weixin_50606278/article/details/125480463