00. 目录

文章目录

  • 00. 目录
  • 01. 扩展关键字: attribute
  • 02. 属性声明: section
  • 03. 属性在Uboot中应用
  • 04. 附录

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 的信息。

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  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000000318  00000318000000000000001c  0000000000000000   A       0     0     1[ 2] .note.gnu.propert NOTE             0000000000000338  000003380000000000000020  0000000000000000   A       0     0     8[ 3] .note.gnu.build-i NOTE             0000000000000358  000003580000000000000024  0000000000000000   A       0     0     4[ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c0000000000000020  0000000000000000   A       0     0     4[ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a00000000000000024  0000000000000000   A       6     0     8[ 6] .dynsym           DYNSYM           00000000000003c8  000003c800000000000000a8  0000000000000018   A       7     1     8[ 7] .dynstr           STRTAB           0000000000000470  000004700000000000000082  0000000000000000   A       0     0     1[ 8] .gnu.version      VERSYM           00000000000004f2  000004f2000000000000000e  0000000000000002   A       6     0     2[ 9] .gnu.version_r    VERNEED          0000000000000500  000005000000000000000020  0000000000000000   A       7     1     8[10] .rela.dyn         RELA             0000000000000520  0000052000000000000000c0  0000000000000018   A       6     0     8[11] .rela.plt         RELA             00000000000005e0  000005e00000000000000018  0000000000000018  AI       6    24     8[12] .init             PROGBITS         0000000000001000  00001000000000000000001b  0000000000000000  AX       0     0     4[13] .plt              PROGBITS         0000000000001020  000010200000000000000020  0000000000000010  AX       0     0     16[14] .plt.got          PROGBITS         0000000000001040  000010400000000000000010  0000000000000010  AX       0     0     16[15] .plt.sec          PROGBITS         0000000000001050  000010500000000000000010  0000000000000010  AX       0     0     16[16] .text             PROGBITS         0000000000001060  000010600000000000000185  0000000000000000  AX       0     0     16[17] .fini             PROGBITS         00000000000011e8  000011e8000000000000000d  0000000000000000  AX       0     0     4[18] .rodata           PROGBITS         0000000000002000  000020000000000000000010  0000000000000000   A       0     0     4[19] .eh_frame_hdr     PROGBITS         0000000000002010  000020100000000000000044  0000000000000000   A       0     0     4[20] .eh_frame         PROGBITS         0000000000002058  000020580000000000000108  0000000000000000   A       0     0     8[21] .init_array       INIT_ARRAY       0000000000003db8  00002db80000000000000008  0000000000000008  WA       0     0     8[22] .fini_array       FINI_ARRAY       0000000000003dc0  00002dc00000000000000008  0000000000000008  WA       0     0     8[23] .dynamic          DYNAMIC          0000000000003dc8  00002dc800000000000001f0  0000000000000010  WA       7     0     8[24] .got              PROGBITS         0000000000003fb8  00002fb80000000000000048  0000000000000008  WA       0     0     8[25] .data             PROGBITS         0000000000004000  000030000000000000000010  0000000000000000  WA       0     0     8[26] .bss              NOBITS           0000000000004010  000030100000000000000008  0000000000000000  WA       0     0     1[27] .comment          PROGBITS         0000000000000000  000030100000000000000024  0000000000000001  MS       0     0     1[28] .symtab           SYMTAB           0000000000000000  000030380000000000000618  0000000000000018          29    46     8[29] .strtab           STRTAB           0000000000000000  000036500000000000000202  0000000000000000           0     0     1[30] .shstrtab         STRTAB           0000000000000000  00003852000000000000011a  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 <stdio.h>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  000000000000000000000000  0000000000000000           0     0     0[ 1] .interp           PROGBITS         0000000000000318  00000318000000000000001c  0000000000000000   A       0     0     1[ 2] .note.gnu.propert NOTE             0000000000000338  000003380000000000000020  0000000000000000   A       0     0     8[ 3] .note.gnu.build-i NOTE             0000000000000358  000003580000000000000024  0000000000000000   A       0     0     4[ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c0000000000000020  0000000000000000   A       0     0     4[ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a00000000000000024  0000000000000000   A       6     0     8[ 6] .dynsym           DYNSYM           00000000000003c8  000003c800000000000000a8  0000000000000018   A       7     1     8[ 7] .dynstr           STRTAB           0000000000000470  000004700000000000000082  0000000000000000   A       0     0     1[ 8] .gnu.version      VERSYM           00000000000004f2  000004f2000000000000000e  0000000000000002   A       6     0     2[ 9] .gnu.version_r    VERNEED          0000000000000500  000005000000000000000020  0000000000000000   A       7     1     8[10] .rela.dyn         RELA             0000000000000520  0000052000000000000000c0  0000000000000018   A       6     0     8[11] .rela.plt         RELA             00000000000005e0  000005e00000000000000018  0000000000000018  AI       6    24     8[12] .init             PROGBITS         0000000000001000  00001000000000000000001b  0000000000000000  AX       0     0     4[13] .plt              PROGBITS         0000000000001020  000010200000000000000020  0000000000000010  AX       0     0     16[14] .plt.got          PROGBITS         0000000000001040  000010400000000000000010  0000000000000010  AX       0     0     16[15] .plt.sec          PROGBITS         0000000000001050  000010500000000000000010  0000000000000010  AX       0     0     16[16] .text             PROGBITS         0000000000001060  000010600000000000000195  0000000000000000  AX       0     0     16[17] .fini             PROGBITS         00000000000011f8  000011f8000000000000000d  0000000000000000  AX       0     0     4[18] .rodata           PROGBITS         0000000000002000  00002000000000000000001a  0000000000000000   A       0     0     4[19] .eh_frame_hdr     PROGBITS         000000000000201c  0000201c000000000000004c  0000000000000000   A       0     0     4[20] .eh_frame         PROGBITS         0000000000002068  000020680000000000000128  0000000000000000   A       0     0     8[21] .init_array       INIT_ARRAY       0000000000003db8  00002db80000000000000008  0000000000000008  WA       0     0     8[22] .fini_array       FINI_ARRAY       0000000000003dc0  00002dc00000000000000008  0000000000000008  WA       0     0     8[23] .dynamic          DYNAMIC          0000000000003dc8  00002dc800000000000001f0  0000000000000010  WA       7     0     8[24] .got              PROGBITS         0000000000003fb8  00002fb80000000000000048  0000000000000008  WA       0     0     8[25] .data             PROGBITS         0000000000004000  000030000000000000000014  0000000000000000  WA       0     0     8[26] .bss              NOBITS           0000000000004014  00003014000000000000000c  0000000000000000  WA       0     0     4[27] .comment          PROGBITS         0000000000000000  000030140000000000000024  0000000000000001  MS       0     0     1[28] .symtab           SYMTAB           0000000000000000  000030380000000000000660  0000000000000018          29    46     8[29] .strtab           STRTAB           0000000000000000  00003698000000000000020e  0000000000000000           0     0     1[30] .shstrtab         STRTAB           0000000000000000  000038a6000000000000011a  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 testSymbol table '.dynsym' contains 7 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab2: 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_registerTMCloneTable6: 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 Name0: 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.c29: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones30: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones31: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux32: 0000000000004014     1 OBJECT  LOCAL  DEFAULT   26 completed.805933: 0000000000003dc0     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtors_aux_fin34: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 frame_dummy35: 0000000000003db8     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_init_array_36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c38: 000000000000218c     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__39: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 40: 0000000000003dc0     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_end41: 0000000000003dc8     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC42: 0000000000003db8     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_start43: 000000000000201c     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR44: 0000000000003fb8     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_45: 0000000000001000     0 FUNC    LOCAL  DEFAULT   12 _init46: 00000000000011f0     5 FUNC    GLOBAL DEFAULT   16 __libc_csu_fini47: 0000000000004010     4 OBJECT  GLOBAL DEFAULT   25 num48: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab49: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.551: 0000000000004014     0 NOTYPE  GLOBAL DEFAULT   25 _edata52: 00000000000011f8     0 FUNC    GLOBAL HIDDEN    17 _fini53: 0000000000001149    23 FUNC    GLOBAL DEFAULT   16 fun54: 0000000000004018     4 OBJECT  GLOBAL DEFAULT   26 var55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_56: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start57: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__58: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle59: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used60: 0000000000001180   101 FUNC    GLOBAL DEFAULT   16 __libc_csu_init61: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   26 _end62: 0000000000001060    47 FUNC    GLOBAL DEFAULT   16 _start63: 0000000000004014     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start64: 0000000000001160    32 FUNC    GLOBAL DEFAULT   16 main65: 0000000000004018     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__66: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable67: 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 testSymbol table '.dynsym' contains 7 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab2: 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_registerTMCloneTable6: 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 Name0: 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.c29: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones30: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones31: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux32: 0000000000004014     1 OBJECT  LOCAL  DEFAULT   26 completed.805933: 0000000000003dc0     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtors_aux_fin34: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 frame_dummy35: 0000000000003db8     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_init_array_36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.c37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c38: 000000000000215c     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__39: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 40: 0000000000003dc0     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_end41: 0000000000003dc8     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC42: 0000000000003db8     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_start43: 0000000000002010     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR44: 0000000000003fb8     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_45: 0000000000001000     0 FUNC    LOCAL  DEFAULT   12 _init46: 00000000000011e0     5 FUNC    GLOBAL DEFAULT   16 __libc_csu_fini47: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab48: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.550: 0000000000004014     0 NOTYPE  GLOBAL DEFAULT   25 _edata51: 00000000000011e8     0 FUNC    GLOBAL HIDDEN    17 _fini52: 0000000000004010     4 OBJECT  GLOBAL DEFAULT   25 val53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_54: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start55: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__56: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle57: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used58: 0000000000001170   101 FUNC    GLOBAL DEFAULT   16 __libc_csu_init59: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   26 _end60: 0000000000001060    47 FUNC    GLOBAL DEFAULT   16 _start61: 0000000000004014     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start62: 0000000000001149    27 FUNC    GLOBAL DEFAULT   16 main63: 0000000000004018     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__64: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable65: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.2
deng@itcast:~/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高级编程

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

  1. Linux·C语言高级编程·attribute和section详解

    目录 01. 扩展关键字: attribute 02. 属性声明: section 03. 属性在Uboot中应用 01. 扩展关键字: attribute GNU C 增加一个 atttribute ...

  2. c语言高级程序设计第五版PDF,C语言高级编程.pdf

    C语言高级编程 概述 由几个测试程序说开去 预编译与宏 高级预编译介绍 宏的高级用法 变量 变量分类详细解析 我的变量去哪儿了? 大小端对变量的影响 内存与指针 常见内存使用错误大观 指针,又是指针! ...

  3. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  4. 鼠标绘图 c语言,c语言高级编程技术教程 图形显示方式与鼠标输入.doc

    c语言高级编程技术教程 图形显示方式与鼠标输入 c语言高级编程技术教程 图形显示方式和鼠标输入 图形显示方式和鼠标输入 问题的提出编写程序,使用鼠标进行如下操作:按住鼠标器的任意键并移动,十字光 标将 ...

  5. 高级编程中C语言属于,c语言高级编程

    c语言高级编程 C高级编程 责任编辑:admin 更新日期:2005-8-6 深入了解C语言(函数的参数传递和函数使用参数的方法) tangl_99(原作) 关键字 C语言,汇编,代码生成,编译器 C ...

  6. 《go语言圣经》+《Mastering.GO-cn》+《go语言高级编程》PDF下载

    公众号[爱吃橙子的搬砖小徐]开通啦,后续将会同步更新,欢迎订阅 回复[java面试]获得两套面试宝典 回复[golang]获得go语言学习三部曲 <go语言圣经>+<Masterin ...

  7. matlab高级教程教材,MATLAB语言高级编程 PDF_IT教程网

    资源名称:MATLAB语言高级编程 PDF 本书共分8章,主要介绍了matlab的概述.matlab安装与工作桌面:matlab的编程基础,包括matlab的变量.matlab的运算符.矩阵的创建及运 ...

  8. 【嵌入式】C语言高级编程-强符号和弱符号(09)

    00. 目录 文章目录 00. 目录 01. weak属性 02. 变量强符号和弱符号 03. 函数强符号和弱符号 04. 弱符号的作用 05. alias属性 06. 附录 01. weak属性 G ...

  9. 【嵌入式】C语言高级编程-内联函数(10)

    00. 目录 文章目录 00. 目录 01. 属性声明 02. 内联函数概述 03. 内联函数与宏 04. 编译器对内联函数的处理 05. static修饰内联函数 06. 附录 01. 属性声明 a ...

最新文章

  1. Linux运行cat进程,linux下如何使用某个用户启动某个进程?
  2. unity实战 实现鼠标选择对象前置显示
  3. Arrays.copyOf()、Arrays.copyOfRange()与System.arraycopy()用法
  4. 一个基于用户的API限流策略 Rate Limit
  5. 戴尔集群监控与管理系统_监控与管理
  6. 【BZOJ】1649: [Usaco2006 Dec]Cow Roller Coaster(dp)
  7. 【2019牛客暑期多校训练营(第二场) - H】Second Large Rectangle(单调栈,全1子矩阵变形)
  8. java案例代码19--二分查找排序
  9. 2020年天津市二级分类土地利用数据(矢量)
  10. 【matlab】 GMSK的调制与解调【附详尽注释】
  11. Github 汉化插件教程
  12. Speedoffice(Excel)怎么把边框线条加粗
  13. Jmeter取样器设置
  14. 计算机rankeq函数,Excel中的rank函数与rank.eq函数有什么区别
  15. JavaWeb宿舍管理系统环境搭建运行教程
  16. 广东英语高考怎么计算机,2019广东高考英语听说考试大纲出炉!附三大题型得分套路!...
  17. 打印机乱码故障解决办法
  18. 法里昂第一大学一座大楼楼顶爆炸起火 致至少3人伤
  19. 更改C盘用户目录下的用户名(真实有效)
  20. 语法分析--自上而下分析的基本问题

热门文章

  1. STATA如何查看数据集中的变量
  2. C# 利用委托事件进行窗体间的传值
  3. bookdown - 撰写和发表自己的网络书籍/文档
  4. web安全---xxs攻击
  5. 【FME-HOW-TO系列】18 从点生成等高线数据
  6. 2021年学什么挣钱(学什么容易挣钱)
  7. 看别人不顺眼,是自己修养不够!!!
  8. 魔百和M301HCW强制升级刷机包(附教程)
  9. 数据存储(超全,超详细,零基础也都学的会)
  10. Unity UGUI 效果 之 UI 元素 多边形UI (例如雷达图,圆形,不规则多边形 UI等)显示 的简单实现的几种方法整理