bss 节区存放「uninitialized data」 ,由程序代码的角度来看,就是「未初始化的变量」。我们直接以一段 code 来说明,让大家更清楚这样的概念。
#include <stdio.h> int foo; int bar; int main(void) { int *ptr; printf(".bss section starts at p/n", &foo); printf("foo is %d./n", foo); ptr = &foo; *ptr = 12345; printf("foo is %d./n", foo); printf(".bss section starts at p/n", &foo); return 0; }这段 code 相当简单,但是隐含几个重要的观念,条列说明如下:
1. foo 是一个变量 ,在程序代码里没有被初始化(uninitialized),所以程序执行时(process),foo 变量会被摆在「.bss section」。 2. 同理,bar 变数也是。 3. foo 是第一个 uninitialized data,所以他的 virtual address,形同 .bss section 的开始地址(process virtual address)。
程序要实验的 项目如下:
1. 观念 3. 的应用,我们印出 .bss section 的 start address。 2. foo 是全域 变数,未初始化时的值是 0(zero)。 3. 用 '*ptr' 指向 .bss section 的 start address,此地址等于 foo 变量的值。 4. 把 .bss section 启始地址处内存的值(value)改成 12345(透 过 ptr 指标)。
没搞错的话, foo 变数的值就会变成 12345。
以下是执行结果:
# ./bss .bss section starts at 0x8049588 foo is 0. foo is 12345. .bss section starts at 0x8049588很特别的一个 section,值得深入研究。
.bss 节区「linking view」上不占档案空间。这点可以用 readelf 来做 ELF linking view 端的印证:
# readelf -e bss|more (bss 是我们的范例执行文件)
...
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480f 4 0000f 4 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4
[ 3] .hash HASH 08048128 000128 000028 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048150 000150 000050 10 A 5 1 4
[ 5] .dynstr STRTAB 080481a 0 0001a 0 00004c 00 A 0 0 1
[ 6] .gnu.version VERSYM 080481ec 0001ec 00000a 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 080481f 8 0001f 8 000020 00 A 5 1 4
[ 8] .rel.dyn REL 08048218 000218 000008 08 A 4 0 4
[ 9] .rel.plt REL 08048220 000220 000010 08 A 4 b 4
[10] .init PROGBITS 08048230 000230 000017 00 AX 0 0 4
[11] .plt PROGBITS 08048248 000248 000030 04 AX 0 0 4
[12] .text PROGBITS 08048278 000278 0001b8 00 AX 0 0 4
[13] .fini PROGBITS 08048430 000430 00001b 00 AX 0 0 4
[14] .rodata PROGBITS 0804844c 00044c 000031 00 A 0 0 4
[15] .eh_frame PROGBITS 08048480 000480 000004 00 A 0 0 4
[16] .data PROGBITS 08049484 000484 00000c 00 WA 0 0 4
[17] .dynamic DYNAMIC 08049490 000490 0000c 8 08 WA 5 0 4
[18] .ctors PROGBITS 08049558 000558 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049560 000560 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049568 000568 000004 00 WA 0 0 4
[21] .got PROGBITS 0804956c 00056c 000018 04 WA 0 0 4
[22] .bss NOBITS 08049584 000584 00000c 00 WA 0 0 4
[23] .comment PROGBITS 00000000 000584 000132 00 0 0 1
...
重点的部份我用粗体字标示出来了:.bss section 与 .comment section 在档案里的 offset 是相同的。不过,用「他人」的工具来印可能会有一些盲点存在,比如说,我们可能不是很明白「Off」真正的意义;建议使用我们自行撰写的 ELF 读文件程序 loader- 0.5.c (下载)来做,因为这是自己写的工具,能保证一些盲点都能得到证明。以下是用 loader-0.5.c 印出来的画面:
# ./loader bss
ELF Identification
Class: 32-bit objects
Machine: Intel 80386
Name Size FileOff
[00] .interp 19 244
[01] .note.ABI-tag 32 264
[02] .hash 40 296
[03] .dynsym 80 336
[04] .dynstr 76 416
[05] .gnu.version 10 492
[06] .gnu.version_r 32 504
[07] .rel.dyn 8 536
[08] .rel.plt 16 544
[09] .init 23 560
[10] .plt 48 584
[11] .text 440 632
[12] .fini 27 1072
[13] .rodata 49 1100
[14] .eh_frame 4 1152
[15] .data 12 1156
[16] .dynamic 200 1168
[17] .ctors 8 1368
[18] .dtors 8 1376
[19] .jcr 4 1384
[20] .got 24 1388
[21] .bss 12 1412
[22] .comment 306 1412
了解 ELF 并自己撰写工具,此过程让我们了解到「Offset」指的是「确实是该 section 在档案里的启始读取位置」。这代表,无论程序里有多少 uninitialized data,都是不占用额外的档案空间的。
画面中的节区大小
「Size」代表该 section 的实体大小(in bytes),以 .bss section 来说,.bss section 的大小是 12 bytes。很不幸的是,这个大小并非表示 .bss section 占用的「档案大小」,而是「内存大小」;这可能会是一个使用工具时,因为画面的「字义」所不小心产生的盲点。所以如果把 .bss section 的 Offset 加上他的 Size,并不会等于 .comment section 的 Offset。
所谓的「Size」,包含由 objdump 与 readelf 所打印出来的画面,或者说,「纪载在 section header entry」里的 size 信息,是表示「该 section 的物理内存大小」。
.bss section 的长度计算方式
.bss 的大小计算方式为(IA32 平台):
4 bytes + sizeof(所有的 uninitialized data)
这代表 .bss section 在内存所会占用的长度。以先前的例子来说,计算式会是:
4 + sizeof(foo) + sizeof(bar) = 4 + 4 + 4 = 12 (bytes)
所以,.bss section 的「size」field 就是 12。
.bss section 的结构
.bss section 的空间结构类似于 stack,所以前一则日记讲述的「foo 是第一个 uninitialized data,所以他的 virtual address,形同 .bss section 的开始地址(process virtual address)。」观念,并非全然正确。
目前已经了解到:.bss section 在 linking view 时是不占档案长度的,在 execution view 时,根据其长度来占用内存大小。
关于 .bss section 的结构,其实一张图就够了。直接切入重点吧!
前言
先重新编译 bss.c 范例:
# gcc -g -o bss bss.c # ./bss .bss section starts at 0x8049588 foo is 0. foo is 12345. .bss section starts at 0x8049588
依照先前日记的说明,.bss section 的长度为 12 bytes。无论程序是否有 uninitialized data,process 一定会有 .bss section,并且 .bss section 的长度至少为 4 bytes(IA32),第一笔资料是 "completed.1",该笔数据纪录 .bss section 的起启地址。
另外,在「linker script」里,定义了一个叫 "__bss_start" 的符号,此符号才是纪录 .bss section 的真正起始地址。不过,在此先不讨论这个部份。
Process 的 .bss section 结构
Process 的 .bss section 占用的内存大小,是根据 .bss section 的长度,在执行时期为每一笔 uninitialized data保留下来的。其结构如下图所示,我们以 bss.c 的范例来说明。
图 .bss section 结构
由图可以知道,范例的 .bss section 其 start address 为 0x8049584,这是直接查询 .bss section 里的 "completed.1" 符号得知的。'completed.1' 的 address 可利用 nm 查询:
# nm -v bss|grep 'completed.1' 08049584 b completed.1
由此了解,.bss section 真正的 start address 应该是 'completed.1';不过,若把第一笔资料的 start address 当做 .bss section 的 start address 其实也无妨,或者说这是 .bss section「放 data」的 start address。
利用 gdb 来观察
# gdb ./bss ... (gdb) disassemble 0x8049588 Dump of assembler code for function foo: 0x08049588 <foo+0>: add %al,(