• C语言全局未初始化数据段分析


    前言:

     

            在分析C语言全局未初始化变量时,发现在目标文件中全局未初始化变量并不是直接放在bss段中。

            再后来发现在两个.c文件中定义同名的全局变量,链接时居然没有发生符号重定义错误。才知道C语言弱定义的概念。这在C++中是绝对不行的。

           后来搜索到一篇博文说:

           “全局未初始化变量没有被放到任何段,而是作为未定义的COMMON符号。这个和不同语言、编译器实现有关,有的编译器放到.bss 段,有的仅仅是预留一个COMMON符号,在链接的时候再在.bss段分配预留空间。编译单元内部可见的静态变量,比如在上述中加上static的 static int global_static_var则确实被放到了.bss,是因为这个仅仅是编译单元内部可见。”

     

            由于最近对gcc反汇编的兴趣,不禁有一种用反汇编工具分析这个问题的冲动。本文看起来很长,其实是图片占据了大量篇幅。所以阅读量很小。懂这个问题的话,两三句话就能说清楚。但是为了几下我分析的过程,还是决定用这个篇幅来叙述一下。

     

    零.几个名词解释:


             弱定义:全局未初始化变量的定义或声明或者局部静态未初始化变量定义。(这是我理解的,实际上我没找到关于弱定义权威的说明,难道是个人发明?)

             .data:全局初始化数据段。

             .bss:全局未初始化数据段。

             .rodata:只读数据段。



    一.分析的对象

    • 1.1全局未初始化变量
    • 1.2全局未初始化变量和另外一个文件有同名变量定义
    • 2.1静态全局未初始化变量
    • 2.2静态全局未初始化变量+有本地同名的初始变量定义
    • 3.静态局部未初始化变量
    • 上面的情况遇到const

        

    二.分析方法

     

    1.查看C源代码对应的汇编代码;

    2.用反汇编译工具objdump和ELF文件分析工具readelf依次分析目标文件和可执行文件,主要查看符号表中变量的位置。

    Ps:虽然我们用到了反汇编,但是只是查看变量的符号,还是比较简单的。汇编代码我们也只看变量定义部分,所以还是很简单。

     

    三.环境和工具

     

    环境为Ubuntu12.04 x64 (具体的环境信息见附录A)

    工具为gcc,objdump,readelf

    用gcc -S产生汇编代码(.s后缀名)。

    用gcc -c产生目标文件(.o后缀名)。

    用gcc产生最终的可执行文件(人为的将可执行文件设置为.out后缀名)。

    用objdump -t和readelf -s命令查看目标文件和可执行文件的符号表。

     

    四.具体分析过程

     

    1.1全局未初始化变量

    源代码文件:1.1.c

    汇编代码文件:1.1.s

    目标文件:1.1.o

    可执行文件:1.1.out

    /* 1.1.c */
    #include <stdio.h>
    int a;
    
    int main()
    {
        printf("%d
    ",a);
        return 0;
    }
    


    运行结果截图:


                                                                                                                                                                                     1.1.out运行结果.png

    可以看出全局未初始化变量a的值是0。

     再看汇编代码

    ;1.1.s中和变量a相关的部分
        .file    "1.1.c"
        .comm    a,4,4

    从汇编代码看出a只是一个common符号。


    对目标文件1.1.o进行反汇编

    objdump -t 1.1.o


                                                                         objdump -t 1.1.o.png

    从上图中看出变量a在目标文件中也只是一个COMMON符号,不属于任何段。

    再用readelf分析一下

    readelf -s 1.1.o


                                                 readelf -s 1.1.o.png

    可以看出a是全局(GLOBAL)的COMMON符号。

     

    对最终的可执行文件进行反汇编

    objdump -t 1.1.out


                                                                   objdump -t 1.1.out.png

    可以看出变量a在可执行目标文件中属于bss段

     

    readelf -s 1.1.out


                                                               readelf -s 1.1.out.png

    可以看出a是全局的(GLOBAL)

     

    1.1观察结果小结:全局未初始化变量a在编译和汇编阶段都不属于任何段,只是一个COMMON符号,在链接时链接器ye没发现其他同名的变量定义或声明,将其放入了bss段中。

     

    1.2全局未初始化变量+另外一个文件有同名变量定义

    分析的对象:1.1.c 、1.2.c;1.1.s、1.2.s;1.1.o、1.2.o;1.out

    1.1.c前面列出过,这里不再赘叙。

    /*1.2.c*/
    int a = 100;
    /*代码很简单,只是定义了一个全局变量*/

    运行结果结果截图


                                                                                                                                1.out运行结果.png

    1.1.s前面贴出过,这里不再赘叙。

    ;1.2.s中变量a相关的部分
    .file    "1.2.c"
        .globl    a
        .data
        .align 4
        .type    a, @object
        .size    a, 4
    a:
        .long    100

    可以看出在1.2.s中变量a属于全局初始化数据段(.data),初始化值为100.


    对目标文件进行反汇编分析

    1.1.o在前面分析过,这里不再赘叙。只叙述1.2.o的分析情况

    Objdump -t 1.2.o


                                                                                 obdump -t 1.2.o.png

    readelf -s 1.2.o


                                                readelf -s 1.2.o.png

    从上面两张图得出的结果和从1.2.s汇编代码中得出的结论一直,在1.2.o中a属于.data段。

     

    对可执行文件进行反汇编分析

    objdump -t 1.out


                                                                             objdump -t 1.out.png

    可以看出a在.data段

    readelf -s 1.out


                                                                              readelf -s 1.out.png

    可以看出a是全局的。

    1.2观察结果小结:全局未初始化变量a在1.1.o不属于任何段,但由于在1.2.o中有一个同名的全局初始化变量定义,所以在最终的执行文件中a也变成了全局初始化变量,属于bss段了。


     

    2.1.静态的全局未初始化变量

    分析对象:2.1.c、2.1.s、2.1.o、2.1.out

    由于静态变量不会和其他文件中的变量有什么关系,所以分析单个文件程序就够了。

    /*2.1.c*/
    #include <stdio.h>
    
    static int a; 
    
    int main()
    {
        printf("%d
    ",a);
        return 0;
    }

    运行结果:


                                                                                                                   2.1.out运行结果.png

    2.1.c对应的汇编代码:

    ;2.1.s中和a相关的部分
        .file    "2.1.c"
        .local    a
        .comm    a,4,4

    可以看出a是本地COMMON符号。


    对目标文件2.1.o进行反汇编

    objdump -t 2.1.o和readelf -s 2.1.o结果如下:


                                                                           objdump -t readelf -s 2.1.o.png

    从上图分析结果可以看出静态全局未初始化变量a作用域为本地(LOCAL),并在bss段中。

    2.1情况观察结果小结:因为静态变量链接时不会和其他文件中的变量产生联系,所以静态全局未初始化变量a在汇编阶段就能确定属于bss段。

     

    2.2静态全局未初始化变量+有本地同名的初始变量定义

        从2.1的分析结果可以看出,静态全局未初始化变量a在汇编阶段就能确定是在bss段中了。假如在变量a的同一个文件中还有一个a的同名初始化变量定义呢?按照弱定义的规则,这种情况a在汇编阶段就能确定在.data段中。下面验证一下:

    分析对象:2.2.c、2.2.s、2.2.o、2.2.out

    /* 2.2.c */
    #include <stdio.h>
    
    static int a;
    static int a = 100;
    
    int main()
    {
        printf("%d
    ",a);
        return 0;
    }
    


    运行结果截图:


                                                                                           2.2. out执行结果.png

    从运行结果看a的初始化值为100,说明static int a退化为声明,而static int a = 100才是变量a的定义。

    对应的汇编代码

        .file    "2.2.c"
        .data
        .align 4
        .type    a, @object
        .size    a, 4
    a:
        .long    100

    从汇编代码看出,在编译阶段就能确定a是在全局初始化数据段(.data)了。下面对目标文件和可执行文件的分析只是验证一下。

    对目标文件2.2.o进行反汇编分析

    objdump -t 2.2.o

    Readelf -s 2.2.o


                                                            对目标文件2.2.o进行分析.png

    可以看出a属于.data段。但作用域为本文件。

    对最终的可执行文件进行反汇编分析

    objdump -t 2.2.out


                                                                 objdump -t 2.1.out.png


    readelf -s  2.2.out


                                                readelf -s 2.1.out .png

    和对目标文件分析的结果一致。

    2.2情况分析小结:上面的分析结果验证了在编译阶段就确定了a属于全局初始化数据段(.data)。


    3.静态局部未初始化变量

    静态局部未初始化变量是否和静态全局未初始化变量一样呢?在编译、汇编、链接阶段属于.data段、还是.bss段呢?

    分析对象:3.c、3.s、3.o、3.out

    3.c
    #include <stdio.h>
    int main()
    {
        static int a;
    
        printf("%d
    ",a);
    
        return 0;
    }

    运行结果:


                                                                                                      3.out运行结果.png

    对应的汇编代码:

    ;3.s
        .file    "3.c"
        .section    .rodata
    .LC0:
        .string    "%d
    "
        .text
        .globl    main
        .type    main, @function
    main:
    .LFB0:
        .cfi_startproc
        pushq    %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    a.1804(%rip), %edx
        movl    $.LC0, %eax
        movl    %edx, %esi
        movq    %rax, %rdi
        movl    $0, %eax
        call    printf
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE0:
        .size    main, .-main
        .local    a.1804                                ;a在这里
        .comm    a.1804,4,4
        .ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
        .section    .note.GNU-stack,"",@progbits

    可以看出在汇编代码中a被改名为a.1084(局部变在编译阶段都会被改名的),而且是本地COMMON符号。

    对目标文件3.o进行反汇编分析:


                                                                                        3.o的反汇编分析.png

    可以看出a在汇编阶段就被放到bss段中了。

    对最终的可执行文件进行反汇编分析

    objdump -t 3.out


                                                                    objdump -t 3.out.png

    Readelf -s 3.out


                                                                                           readelf -s 3.out.png

    结果和目标文件中的分析情况一致。

     3情况分析小结:静态局部未出世化变量属于bss段,这在编译阶段就能确定。


    如果是:静态局部未初始化变量+本地有静态同名初始化变量 的情况呢?

    会不会和“2.2静态全局未初始化变量+有本地同名的初始变量定义”情况一样:static int a退化为声明,而static int a = 100才是定义?

    验证的结果是在同一函数内部,同一作用域里同时出现static int a和static int a = 100根本就通不过编译,出现重声明错误。看来只有静态全局变量可以这么定义和声明啊。

    4.上面所有情况遇到const:

              分析过程和上面相似,但是据观察:静态全局未初始化变量,在编译阶段就已确定属于.rodata段;const静态局部未初始化变量规律和上面3种情况相同,属于.bss段。(从汇编代码中看出)。

     

    最终结论:

           弱定义变量有可能属于bss段,也有可能属于.data段。如果是静态变量,在编译阶段就能确定;如果是全局非静态变量,在链接阶段确定。至于到底属于哪个段:如果在同一作用域内遇到其他属于.data的同名定义,则属于.data段;如果没遇到,就属于.bss段了。const变量比较特殊,const静态全局未初始化变量属于.rodata段。

     


    附录A:具体的环境信息

    $ cat /etc/issue
    Ubuntu 12.04.2 LTS   l

    $ uname -a
    Linux chao-AN408US 3.2.0-29-generic #46-Ubuntu SMP Fri Jul 27 17:03:23 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

    $ cat /pro/version
    Linux version 3.2.0-29-generic (buildd@allspice) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #46-Ubuntu SMP Fri Jul 27 17:03:23 UTC 2012

    $ gcc --version
    gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
    Copyright © 2011 Free Software Foundation, Inc.
    本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
    包括没有适销性和某一专用目的下的适用性担保。

     

    附录B:参考资料

    链接:Linux程序调试--查看二进制文件

    http://www.cnblogs.com/androidme/archive/2013/04/08/3008615.html

    Linux汇编教程

    http://blog.csdn.net/yalizhi123/article/details/5752268

    Linux进程地址空间详解(转载)

    http://blog.sina.com.cn/s/blog_7321be1101013aua.html

    书:《汇编语言程序设计》(美) Richard Blum 著

  • 相关阅读:
    放弃我最喜爱的网络,全面转入软件开发
    [推荐]制作div+css圆角
    一个页面多个超连接的实现
    C#基类和派生类
    代码不是重点, 领悟OO思想(一)
    C#params 有什么用?
    计算机族必喝的健康饮品
    今天再做上传图片时候遇到了一个JS 图片预览问题
    C#中split的5种方法
    [推荐]制作div+css圆角
  • 原文地址:https://www.cnblogs.com/james1207/p/3362277.html
Copyright © 2020-2023  润新知