> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# common块探究
- URL: https://blog.hacking.pub/2017/05/14/commonkuai-tan-jiu/
- Published: 2017-05-14T06:52:47.000Z
- Updated: 2017-05-14T06:52:47.000Z
- Author: chainhelen
- Tags: c语言, 编译, bss, common, 强符号, 弱符号, gdb, g++, gcc, 逆向, 逆向工程

#### COMMON块

大部分教材，或者Blog都会讲到c系语言中`未初始化的全局变量或静态变量`的内存空间是在.bss段  
今天就来测试一下，gcc/g++的版本如下

```
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~$ gcc --version
gcc (Ubuntu 5.4.1-2ubuntu1~14.04) 5.4.1 20160904

chainhelen@iZwz92pw32r9w6beu4jlfpZ:~$ g++ --version
g++ (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5

```

代码如下

```
//main.c
#include <stdio.h>
int m;
intmain() {
    printf("hello world; m = %d\n", m);
    return 0;
}

//生成中间文件
gcc -c main.c -o main_gcc.o
g++ -c main.c -o main_g++.o

```

常看符号表

```
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_gcc.o 

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000004     4 OBJECT  GLOBAL DEFAULT  COM m
    10: 0000000000000000    34 FUNC    GLOBAL DEFAULT    1 main
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_g++.o   

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 m
    10: 0000000000000000    34 FUNC    GLOBAL DEFAULT    1 main
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

```

可以看见当作为c++的语言来编译，就直接是Ndx=4了，也就是bss段  
但是作为纯正的gcc编译c语言却不是，是一个COM的符号，这东西主要解决了什么问题？

**`一个弱符号定义在多个目标文件中，而它们的类型又不同（即大小不同）`**

显然，由于c++把`m`这个变量内存存在了 .bss section，也就是不允许其他文件再去定义这个变量`m`

#### 实验

接下来再添加个c系代码

```
//m.c
long long m = 12;

```

**1\. g++**

先验证c++的方式

```
//m.c生成中间文件
g++ -c m.c -o m_g++.o

//生成可执行文件
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ g++ main_g++.o m_g++.o -o main_m_g++
m_g++.o:(.data+0x0): multiple definition of `m'
main_g++.o:(.bss+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `m' changed from 4 in main_g++.o to 8 in m_g++.o
collect2: error: ld returned 1 exit status

```

报错，重复定义变量`m`

**2\. gcc的c编译器**

先验证gcc的方式

```
//m.c生成中间文件
gcc -c m.c -o m_gcc.o

//生成可执行文件
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc m_gcc.o main_gcc.o -o main_m_gcc
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_m_gcc 
hello world; m = 12

```

直接看看最终的变量`m`在可执行文件中 占用字节多大

```
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_m_gcc | grep ' m'  
    36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS m.c
    37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    60: 0000000000601040     8 OBJECT  GLOBAL DEFAULT   24 m
    62: 0000000000400536    34 FUNC    GLOBAL DEFAULT   13 main

```

8字节，也就是说 m.c中的 double定义 覆盖了 main.c中int定义  
看一下索引24是哪个段

```
readelf -S main_m_gcc 
...
[24] .data             PROGBITS         0000000000601030  00001030
       0000000000000018  0000000000000000  WA       0     0     8
...

```

符合m.c的`初始化的全局变量内存在.data段`
  
  
对比一下直接编译main.c生成 可执行文件 \`main\_gcc\` 

```
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc main_gcc.o -o main_gcc
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_gcc 
hello world; m = 0
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_gcc | grep ' m'       
    36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    59: 0000000000601044     4 OBJECT  GLOBAL DEFAULT   25 m
    61: 0000000000400536    34 FUNC    GLOBAL DEFAULT   13 main

chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -S main_gcc
...
  [25] .bss              NOBITS           0000000000601040  00001040
       0000000000000008  0000000000000000  WA       0     0     4
...

```

符合`未初始化的全局变量内存存放在.bss段`，只不过纯gcc的c语言编译器生成中间文件的时候，还是标记为COMMON符号，链接生成可执行文件才会把放在.bss段

#### 危险

在C语言中，函数和初始化的全局变量（包括显示初始化为0）是强符号，未初始化的全局变量是弱符号。  
举个危险的例子

```
//main.c
int m = 12;
int n = 11;
int main() {
    printf("m = %d\tn = %d\n", m, n);
    setzerom();
    printf("m = %d\tn = %d\n", m, n);
    return 0;
}

//m.c
long long m;
void setzerom() 
{
    m = 0;
}

chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc -w main.c m.c -o main_m_gcc
/usr/bin/ld: Warning: alignment 4 of symbol `m' in /tmp/ccgkd2y4.o is smaller than 8 in /tmp/cc7iEkVJ.o
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_m_gcc
m = 12  n = 11
m = 0   n = 0

```

正常来讲，没有动变量n的值，不应该出现它的值也变成 0 的现象  
在m.c中，变量m其实访问到了main.c中的n，因为它是8字节的long long 弱符号

在第一处print地方，加上断点，gdb调试可知原因

```
(gdb) p &m
$3 = (int *) 0x601040 <m>
(gdb) p &n
$4 = (int *) 0x601044 <n>
(gdb) x/8b 0x601040
0x601040 <m>:   0x0c    0x00    0x00    0x00    0x0b    0x00    0x00    0x00
(gdb) nm = 12   n = 11

(gdb) n
(gdb) x/8xb 0x601040
0x601040 <m>:   0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

```

---

但是如下代码却不会有事

```
//main.c
int m;
int n = 11;
int main() {
    printf("m = %d\tn = %d\n", m, n);
    setzerom();
    printf("m = %d\tn = %d\n", m, n);
    return 0;
}

//m.c
long long m = 12;
void setzerom() 
{
    m = 0;
}

```

#### 总结

在C语言中，函数和初始化的全局变量（包括显示初始化为0）是强符号，未初始化的全局变量是弱符号。  
对于它们，下列三条规则使用：  
① `同名的强符号只能有一个，否则编译器报"重复定义"错误`  
② `允许一个强符号和多个弱符号，但定义会选择强符号的`  
③ `当有多个弱符号相同时，链接器选择占用内存空间最大的那个`

对了，上面如果加上编译项 -fno-common 就会报错，就变成了c++的那种了

---

实际.bss段在可执行文件里面，是没有分配内存的，因为.bss段的数据都是零，所以干脆在`装载`的时候才在虚拟内存空间里分配