欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【嵌入式】C语言高级编程-attribute和section(06)

程序员文章站 2022-07-02 08:31:20
...

00. 目录

01. 扩展关键字: attribute

GNU C 增加一个 atttribute 关键字用来声明一个函数、变量或类型的特殊属性。声明这个特殊属性有什么用呢?主要用途就是指导编译器在编译程序时进行特定方面的优化或代码检查。比如,我们可以通过使用属性声明指定某个变量的数据边界对齐方式。

__attribute__的使用非常简单,当我们定义一个函数、变量或类型时,直接在它们名字旁边添加下面的属性声明即可:

__atttribute__((ATTRIBUTE))

注意:attribute 后面是两对小括号,不能图方便只写一对,否则编译可能通不过。括号里面的 ATTRIBUTE 代表的就是要声明的属性。现在 attribute 支持十几种属性:

  • section
  • aligned
  • packed
  • format
  • weak
  • alias
  • noinline
  • always_inline
  • ……

在这些属性中,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)));

02. 属性声明: section

首先我们先讲一下 section 这个属性。使用atttribute 来声明一个 section 属性,主要用途是在程序编译时,将一个函数或变量放到指定的段,即 section 中。

程序的编译、链接过程

一个可执行目标文件,它主要由代码段、数据段、BSS 段构成。代码段主要存放编译生成的可执行指令代码,数据段和 BSS 段用来存放全局变量、未初始化的全局变量。代码段、数据段和 BSS 段构成了一个可执行文件的主要部分。

除了这三个段,可执行文件中还包含其它一些段。用编译器的专业术语讲,还会包含其它一些 section,比如只读数据段、符号表等等。我们可以使用下面的 readelf 命令,去查看一个可执行文件中各个 section 的信息。

[email protected]:~/share$ gcc test.c -o test 
[email protected]:~/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)
[email protected]:~/share$ 

在 Linux 环境下,使用 GCC 编译生成一个可执行文件 test,使用上面的 readelf 命令,就可以查看这个可执行文件中各个 section 的基本信息,比如大小、起始地址等等。在这些 section 中,其中 .text section 就是我们常说的代码段,.data section 是数据段,.bss section 是 BSS 段。

我们知道一段源程序代码在编译生成可执行文件的过程中,函数和变量是放在不同段中的。一般默认的规则如下。

组成
代码段( .text) 函数定义、程序语句
数据段( .data) 初始化的全局变量、初始化的静态局部变量
BSS段( .bss) 未初始化的全局变量、未初始化的静态局部变量

在下面的程序中,我们分别定义一个函数、一个全局变量和一个未初始化的全局变量。

#include <stdio.h>

int num = 8;
int var;

void fun(void)
{
    printf("hello fun\n");
}

int main(void)
{

    fun();

    printf("hello world\n");
    return 0;
}

查看结果

[email protected]:~/share$ gcc test.c  -o test
[email protected]:~/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)
  
[email protected]:~/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 [email protected]_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_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 [email protected]_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 [email protected]@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 [email protected]@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 [email protected]@GLIBC_2.2
[email protected]:~/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 版本的系统中,系统会有默认的链接脚本,不需要程序员操心。

[email protected]:~/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 中。

使用示例

[email protected]:~/share$ gcc test.c  -o test

执行结果

[email protected]:~/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 [email protected]_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_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 [email protected]_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 [email protected]@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 [email protected]@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 [email protected]@GLIBC_2.2
[email protected]:~/share$ 

通过上面的 readelf 命令查看符号表,我们可以看到,val这个未初始化的全局变量,通过__attribute__((section(".data"))) 属性声明,就被编译器放在了数据段 .data section 中。

03. 属性在Uboot中应用

有了 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 中执行。

04. 附录

参考:C语言嵌入式Linux高级编程