GNU C 增加一个 atttribute 关键字用来声明一个函数、变量或类型的特殊属性。声明这个特殊属性有什么用呢?主要用途就是指导编译器在编译程序时进行特定方面的优化或代码检查。比如,我们可以通过使用属性声明指定某个变量的数据边界对齐方式。
__attribute__的使用非常简单,当我们定义一个函数、变量或类型时,直接在它们名字旁边添加下面的属性声明即可:
__atttribute__((ATTRIBUTE))
注意:attribute 后面是两对小括号,不能图方便只写一对,否则编译可能通不过。括号里面的 ATTRIBUTE 代表的就是要声明的属性。现在 attribute 支持十几种属性:
在这些属性中,aligned 和 packed 用来显式指定一个变量的存储边界对齐方式。一般来讲,我们定义一个变量,编译器会根据变量类型,按照默认的规则来给这个变量分配大小、按照默认的边界对齐方式分配一个地址。而使用 atttribute 这个属性声明,就相当于告诉编译器:按照我们指定的边界地址对齐去给这个变量分配存储空间。
char c2 __attribute__((aligned(8)) = 4;
int global_val __attribute__((section(".data")));
有些属性可能还有自己的参数。比如 aligned(8) 表示这个变量按8字节地址对齐,参数也要使用小括号括起来。如果属性的参数是一个字符串,小括号里的参数还要用双引号引起来。
当然,我们也可以对一个变量同时添加多个属性说明。在定义时,各个属性之间用逗号隔开就可以了。
char c1 __attribute__((packed,aligned(4)));
char c1 __attribute__((packed,aligned(4))) = 4;
__attribute__((packed,aligned(4))) char c1 = 4;
在上面的示例中,我们对一个变量添加2个属性声明,这两个属性都放在 atttribute(()) 的2对小括号里面,属性之间用逗号隔开。这里还有一个细节,就是属性声明要紧挨着变量,上面的三种定义方式都是没有问题的,但下面的定义方式在编译的时候可能就通不过。
char c2 = 4 __attribute__((packed,aligned(4)));
首先我们先讲一下 section 这个属性。使用atttribute 来声明一个 section 属性,主要用途是在程序编译时,将一个函数或变量放到指定的段,即 section 中。
程序的编译、链接过程
一个可执行目标文件,它主要由代码段、数据段、BSS 段构成。代码段主要存放编译生成的可执行指令代码,数据段和 BSS 段用来存放全局变量、未初始化的全局变量。代码段、数据段和 BSS 段构成了一个可执行文件的主要部分。
除了这三个段,可执行文件中还包含其它一些段。用编译器的专业术语讲,还会包含其它一些 section,比如只读数据段、符号表等等。我们可以使用下面的 readelf 命令,去查看一个可执行文件中各个 section 的信息。
deng@itcast:~/share$ gcc test.c -o test
deng@itcast:~/share$ readelf -S test
There are 31 section headers, starting at offset 0x3970:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000000338 00000338
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.gnu.build-i NOTE 0000000000000358 00000358
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 000003c8
00000000000000a8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 00000470
0000000000000082 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004f2 000004f2
000000000000000e 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000500 00000500
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000000520 00000520
00000000000000c0 0000000000000018 A 6 0 8
[11] .rela.plt RELA 00000000000005e0 000005e0
0000000000000018 0000000000000018 AI 6 24 8
[12] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 00001020
0000000000000020 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001040 00001040
0000000000000010 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001050 00001050
0000000000000010 0000000000000010 AX 0 0 16
[16] .text PROGBITS 0000000000001060 00001060
0000000000000185 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 00000000000011e8 000011e8
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 00002000
0000000000000010 0000000000000000 A 0 0 4
[19] .eh_frame_hdr PROGBITS 0000000000002010 00002010
0000000000000044 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002058 00002058
0000000000000108 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003db8 00002db8
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0
0000000000000008 0000000000000008 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003dc8 00002dc8
00000000000001f0 0000000000000010 WA 7 0 8
[24] .got PROGBITS 0000000000003fb8 00002fb8
0000000000000048 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000004010 00003010
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00003010
0000000000000024 0000000000000001 MS 0 0 1
[28] .symtab SYMTAB 0000000000000000 00003038
0000000000000618 0000000000000018 29 46 8
[29] .strtab STRTAB 0000000000000000 00003650
0000000000000202 0000000000000000 0 0 1
[30] .shstrtab STRTAB 0000000000000000 00003852
000000000000011a 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
deng@itcast:~/share$
在 Linux 环境下,使用 GCC 编译生成一个可执行文件 test,使用上面的 readelf 命令,就可以查看这个可执行文件中各个 section 的基本信息,比如大小、起始地址等等。在这些 section 中,其中 .text section 就是我们常说的代码段,.data section 是数据段,.bss section 是 BSS 段。
我们知道一段源程序代码在编译生成可执行文件的过程中,函数和变量是放在不同段中的。一般默认的规则如下。
段 | 组成 |
---|---|
代码段( .text) | 函数定义、程序语句 |
数据段( .data) | 初始化的全局变量、初始化的静态局部变量 |
BSS段( .bss) | 未初始化的全局变量、未初始化的静态局部变量 |
在下面的程序中,我们分别定义一个函数、一个全局变量和一个未初始化的全局变量。
#include
int num = 8;
int var;
void fun(void)
{
printf("hello fun\n");
}
int main(void)
{
fun();
printf("hello world\n");
return 0;
}
查看结果
deng@itcast:~/share$ gcc test.c -o test
deng@itcast:~/share$ readelf -S test
There are 31 section headers, starting at offset 0x39c0:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000000338 00000338
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.gnu.build-i NOTE 0000000000000358 00000358
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 000003c8
00000000000000a8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 00000470
0000000000000082 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004f2 000004f2
000000000000000e 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000500 00000500
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000000520 00000520
00000000000000c0 0000000000000018 A 6 0 8
[11] .rela.plt RELA 00000000000005e0 000005e0
0000000000000018 0000000000000018 AI 6 24 8
[12] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 00001020
0000000000000020 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001040 00001040
0000000000000010 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001050 00001050
0000000000000010 0000000000000010 AX 0 0 16
[16] .text PROGBITS 0000000000001060 00001060
0000000000000195 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 00000000000011f8 000011f8
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 00002000
000000000000001a 0000000000000000 A 0 0 4
[19] .eh_frame_hdr PROGBITS 000000000000201c 0000201c
000000000000004c 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002068 00002068
0000000000000128 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003db8 00002db8
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0
0000000000000008 0000000000000008 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003dc8 00002dc8
00000000000001f0 0000000000000010 WA 7 0 8
[24] .got PROGBITS 0000000000003fb8 00002fb8
0000000000000048 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
0000000000000014 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000004014 00003014
000000000000000c 0000000000000000 WA 0 0 4
[27] .comment PROGBITS 0000000000000000 00003014
0000000000000024 0000000000000001 MS 0 0 1
[28] .symtab SYMTAB 0000000000000000 00003038
0000000000000660 0000000000000018 29 46 8
[29] .strtab STRTAB 0000000000000000 00003698
000000000000020e 0000000000000000 0 0 1
[30] .shstrtab STRTAB 0000000000000000 000038a6
000000000000011a 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
deng@itcast:~/share$ readelf -s test
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 68 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000318 0 SECTION LOCAL DEFAULT 1
2: 0000000000000338 0 SECTION LOCAL DEFAULT 2
3: 0000000000000358 0 SECTION LOCAL DEFAULT 3
4: 000000000000037c 0 SECTION LOCAL DEFAULT 4
5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5
6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6
7: 0000000000000470 0 SECTION LOCAL DEFAULT 7
8: 00000000000004f2 0 SECTION LOCAL DEFAULT 8
9: 0000000000000500 0 SECTION LOCAL DEFAULT 9
10: 0000000000000520 0 SECTION LOCAL DEFAULT 10
11: 00000000000005e0 0 SECTION LOCAL DEFAULT 11
12: 0000000000001000 0 SECTION LOCAL DEFAULT 12
13: 0000000000001020 0 SECTION LOCAL DEFAULT 13
14: 0000000000001040 0 SECTION LOCAL DEFAULT 14
15: 0000000000001050 0 SECTION LOCAL DEFAULT 15
16: 0000000000001060 0 SECTION LOCAL DEFAULT 16
17: 00000000000011f8 0 SECTION LOCAL DEFAULT 17
18: 0000000000002000 0 SECTION LOCAL DEFAULT 18
19: 000000000000201c 0 SECTION LOCAL DEFAULT 19
20: 0000000000002068 0 SECTION LOCAL DEFAULT 20
21: 0000000000003db8 0 SECTION LOCAL DEFAULT 21
22: 0000000000003dc0 0 SECTION LOCAL DEFAULT 22
23: 0000000000003dc8 0 SECTION LOCAL DEFAULT 23
24: 0000000000003fb8 0 SECTION LOCAL DEFAULT 24
25: 0000000000004000 0 SECTION LOCAL DEFAULT 25
26: 0000000000004014 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
32: 0000000000004014 1 OBJECT LOCAL DEFAULT 26 completed.8059
33: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin
34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 000000000000218c 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
39: 0000000000000000 0 FILE LOCAL DEFAULT ABS
40: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end
41: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
42: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start
43: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
44: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
45: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init
46: 00000000000011f0 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini
47: 0000000000004010 4 OBJECT GLOBAL DEFAULT 25 num
48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
49: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
51: 0000000000004014 0 NOTYPE GLOBAL DEFAULT 25 _edata
52: 00000000000011f8 0 FUNC GLOBAL HIDDEN 17 _fini
53: 0000000000001149 23 FUNC GLOBAL DEFAULT 16 fun
54: 0000000000004018 4 OBJECT GLOBAL DEFAULT 26 var
55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
56: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
57: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
58: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
59: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
60: 0000000000001180 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init
61: 0000000000004020 0 NOTYPE GLOBAL DEFAULT 26 _end
62: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
63: 0000000000004014 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
64: 0000000000001160 32 FUNC GLOBAL DEFAULT 16 main
65: 0000000000004018 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
66: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
67: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
deng@itcast:~/share$
通过符号表和节头表 section header table 信息,我们可以看到,函数 print_star 被放在可执行文件中的 .text section,即代码段;初始化的全局变量 global_val 被放在了 .data section,即数据段;而未初始化的全局变量 uninit_val 则被放在了.bss section,即 BSS 段。
编译器在编译程序时,是以源文件为单位,将一个个源文件编译生成一个个目标文件。在编译过程中,编译器都会按照这个默认规则,将函数、变量分别放在不同的 section 中,最后将各个 section 组成一个目标文件。编译过程结束后,链接器接着会将各个目标文件组装合并、重定位,生成一个可执行文件。
链接器是如何将各个目标文件组装成一个可执行文件的呢?很简单,链接器首先会分别将各个目标文件的代码段整合,组装成一个大的代码段;将各个目标文件中的数据段整合,合并成一个大的数据段;接着将合并后的新代码段、数据段再合并为一个文件;最后经过重定位,就生成了一个可以运行的可执行文件了。
现在又有一个疑问来了,链接器在将各个不同的 section 段组装成一个可执行文件的过程中,各个 section 的顺序如何排放呢?比如代码段、数据段、BSS 段、符号表等,谁放在前面?谁放在后面?
链接器在链接过程中,会将不同的 section,按照链接脚本中指定的各个 section 的排放顺序,组装成一个可执行文件。一般在 Ubuntu 等 PC 版本的系统中,系统会有默认的链接脚本,不需要程序员操心。
deng@itcast:~/share$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.34
Copyright (C) 2020 Free Software Foundation, Inc.
这个程序是自由软件;您可以遵循GNU 通用公共授权版本 3 或
(您自行选择的) 稍后版本以再次散布它。
这个程序完全没有任何担保。
我们使用上面命令,就可以查看编译当前程序时,链接器使用的默认链接脚本。在嵌入式系统中,因为是交叉编译,所以软件源码一般会自带一个链接脚本。比如在 U-boot 源码的根目录下面,你会看到一个 u-boot.lds 的文件,这个文件就是编译 U-boot 时,链接器要使用的链接脚本。在 Linux 内核中,同样会有 vmlinux.lds 这样一个链接脚本。
在 GNU C 中,我们可以通过 attribute 的 section 属性,显式指定一个函数或变量,在编译时放到指定的 section 里面。通过上面的程序我们知道,未初始化的全局变量是放在 .data section 中的,即放在 BSS 段中。现在我们就可以通过 section 属性,把这个未初始化的全局变量放到数据段 .data 中。
使用示例
deng@itcast:~/share$ gcc test.c -o test
执行结果
deng@itcast:~/share$ readelf -s test
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 66 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000318 0 SECTION LOCAL DEFAULT 1
2: 0000000000000338 0 SECTION LOCAL DEFAULT 2
3: 0000000000000358 0 SECTION LOCAL DEFAULT 3
4: 000000000000037c 0 SECTION LOCAL DEFAULT 4
5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5
6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6
7: 0000000000000470 0 SECTION LOCAL DEFAULT 7
8: 00000000000004f2 0 SECTION LOCAL DEFAULT 8
9: 0000000000000500 0 SECTION LOCAL DEFAULT 9
10: 0000000000000520 0 SECTION LOCAL DEFAULT 10
11: 00000000000005e0 0 SECTION LOCAL DEFAULT 11
12: 0000000000001000 0 SECTION LOCAL DEFAULT 12
13: 0000000000001020 0 SECTION LOCAL DEFAULT 13
14: 0000000000001040 0 SECTION LOCAL DEFAULT 14
15: 0000000000001050 0 SECTION LOCAL DEFAULT 15
16: 0000000000001060 0 SECTION LOCAL DEFAULT 16
17: 00000000000011e8 0 SECTION LOCAL DEFAULT 17
18: 0000000000002000 0 SECTION LOCAL DEFAULT 18
19: 0000000000002010 0 SECTION LOCAL DEFAULT 19
20: 0000000000002058 0 SECTION LOCAL DEFAULT 20
21: 0000000000003db8 0 SECTION LOCAL DEFAULT 21
22: 0000000000003dc0 0 SECTION LOCAL DEFAULT 22
23: 0000000000003dc8 0 SECTION LOCAL DEFAULT 23
24: 0000000000003fb8 0 SECTION LOCAL DEFAULT 24
25: 0000000000004000 0 SECTION LOCAL DEFAULT 25
26: 0000000000004014 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
32: 0000000000004014 1 OBJECT LOCAL DEFAULT 26 completed.8059
33: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin
34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 000000000000215c 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
39: 0000000000000000 0 FILE LOCAL DEFAULT ABS
40: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end
41: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
42: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start
43: 0000000000002010 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
44: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
45: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init
46: 00000000000011e0 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini
47: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
48: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
50: 0000000000004014 0 NOTYPE GLOBAL DEFAULT 25 _edata
51: 00000000000011e8 0 FUNC GLOBAL HIDDEN 17 _fini
52: 0000000000004010 4 OBJECT GLOBAL DEFAULT 25 val
53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
54: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
55: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
56: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
57: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
58: 0000000000001170 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init
59: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end
60: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
61: 0000000000004014 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
62: 0000000000001149 27 FUNC GLOBAL DEFAULT 16 main
63: 0000000000004018 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
64: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
65: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
deng@itcast:~/share$
通过上面的 readelf 命令查看符号表,我们可以看到,val这个未初始化的全局变量,通过__attribute__((section(".data")))
属性声明,就被编译器放在了数据段 .data section 中。
有了 section 这个属性,我们接下来就可以试着分析,U-boot 在启动过程中,是如何将自身代码加载的 RAM 中的。
搞嵌入式的都知道 U-boot,U-boot 的用途主要是加载 Linux 内核镜像到内存、给内核传递启动参数、然后引导 Linux 操作系统启动。
U-boot 一般存储在 Nor flash 或 NAND Flash 上。无论从 Nor Flash 还是从 Nand Flash 启动,U-boot 其本身在启动过程中,也会从 Flash 存储介质上加载自身代码到内存,然后进行重定位,跳到内存 RAM 中去执行。这个功能一般叫做“自举。我们的主要任务是去看看 U-boot 是怎么完成自拷贝的,或者说它是怎样将自身代码从 Flash 拷贝到内存 RAM 中的。
在拷贝自身代码的过程中,一个主要的疑问就是,U-boot 是如何识别自身代码的?是如何知道从哪里拷贝代码的?是如何知道拷贝到哪里停止的?这个时候我们不得不说起 U-boot 源码中的一个零长度数组。
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
这两行代码定义在 U-boot-2016.09 中的 arch/arm/lib/section.c 文件中。在其它版本中可能路径不同或者没有定义,为了分析这个功能,建议大家可以下载 U-boot-2016.09 这个版本的U-boot源码。
这两行代码的作用是分别定义一个零长度数组,并告诉编译器要分别放在 .imagecopystart
和 .image_copy_end
这两个 section 中。
链接器在链接各个目标文件时,会按照链接脚本里各个 section 的排列顺序,将各个 section 组装成一个可执行文件。U-boot 的链接脚本 u-boot.lds 在 U-boot 源码的根目录下面。
OUTPUT_FORMAT("elf32-littlearm",
"elf32-littlearm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.data : {
*(.data*)
}
...
...
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
}
通过链接脚本我们可以看到,__image_copy_start 和 __image_copy_end 这两个 section,在链接的时候分别放在了代码段 .text 的前面、数据段 .data 的后面,作为 U-boot 拷贝自身代码的起始地址和结束地址。而在这两个 section 中,我们除了放2个零长度数组外,并没有再放其它变量。根据前面的学习我们知道,零长度数组是不占用存储空间的,所以上面定义的两个零长度数组,其实就分别代表了 U-boot 镜像要拷贝自身镜像的起始地址和结束地址。
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
无论 U-boot 自身镜像是存储在 Nor Flash,还是 Nand Flash 上,我们只要知道了这两个地址,就可以直接调用相关代码拷贝。
接着在 arch/arm/lib/relocate.S 中,ENTRY(relocate_code) 汇编代码主要完成代码拷贝的功能。
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
在这段汇编代码中,寄存器 R1、R2 分别表示要拷贝镜像的起始地址和结束地址,R0 表示要拷贝到 RAM 中的地址,R4 存放的是源地址和目的地址之间的偏移,在后面重定位过程中会用到这个偏移值。
ldr r1, =__image_copy_start
见上面指令,在汇编代码中,ARM的 ldr 指令立即寻址,直接对数组名进行引用,获取要拷贝镜像的首地址,并保存在 R1 寄存器中。数组名本身其实就代表一个地址。通过这种方式,U-boot 在嵌入式启动的初始阶段,就完成了自身代码的拷贝工作:从 Flash 上拷贝自身镜像到 RAM 中,然后再进行重定位,最后跳到 RAM 中执行。
参考:C语言嵌入式Linux高级编程