__attribute__之section詳解

前言

第一次接觸 “section” 是在公司的一個STM32的項目代碼中,前工程師將所有的初始化函數都使用的“section”進行設定了屬性。當時知道其目的,但是不知道原因。然後到後來在接觸了Linux的驅動程序的時候,發現linux的驅動註冊的宏定義層層解析以後,也是使用的“section”進行修飾,但是當時看教程以爲必須限定到內存的特定位置中,以及經驗不足,所以沒有深究。然現在在寫Linux應用程序的的時候,發現在SDK中也有使用“section”進行一類程序的修飾,然後我就專門花了幾個小時時間去查閱各種論壇,進行了一次算還算比較深入的學習吧。

使用section可以使我們如在初始化函數時,不用在主函數中去添加一個新的初始化程序,只需要在自己的函數模塊內註冊就好了。或者在實現某些命令時,添加或刪除該命令的支持,會方便很多。

使用方法

"section"關鍵字會將被修飾的變量或函數編譯到特定的一塊位置,不是物理存儲器上的特定位置,而是在可執行文件的特定段內。在編譯好的程序中我們可以使用命令:

seven@root:~/section/$ readelf -S a.out
There are 37 section headers, starting at offset 0x201c:

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        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 00002c 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481d8 0001d8 000050 10   A  6   1  4
  [ 6] .dynstr           STRTAB          08048228 000228 00004c 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048274 000274 00000a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         08048280 000280 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482a0 0002a0 000018 08   A  5   0  4
  [10] .init             PROGBITS        080482b8 0002b8 000023 00  AX  0   0  4
  [11] .plt              PROGBITS        080482e0 0002e0 000010 04  AX  0   0 16
  [12] .plt.got          PROGBITS        080482f0 0002f0 000018 00  AX  0   0  8
  [13] .text             PROGBITS        08048310 000310 000212 00  AX  0   0 16
  [14] .fini             PROGBITS        08048524 000524 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048538 000538 00007f 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        080485b8 0005b8 000044 00   A  0   0  4
  [17] .eh_frame         PROGBITS        080485fc 0005fc 00012c 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e0 08  WA  6   0  4
  [22] .got              PROGBITS        08049ff4 000ff4 00000c 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 00000c 04  WA  0   0  4
  [24] .data             PROGBITS        0804a00c 00100c 000008 00  WA  0   0  4
  [25] .application_init PROGBITS        0804a014 001014 00000c 00  WA  0   0  4
  [26] .bss              NOBITS          0804a020 001020 000004 00  WA  0   0  1
  [27] .comment          PROGBITS        00000000 001020 000035 01  MS  0   0  1
  [28] .debug_aranges    PROGBITS        00000000 001055 000020 00      0   0  1
  [29] .debug_info       PROGBITS        00000000 001075 000200 00      0   0  1
  [30] .debug_abbrev     PROGBITS        00000000 001275 0000e3 00      0   0  1
  [31] .debug_line       PROGBITS        00000000 001358 000079 00      0   0  1
  [32] .debug_str        PROGBITS        00000000 0013d1 0001c9 01  MS  0   0  1
  [33] .debug_loc        PROGBITS        00000000 00159a 0000f1 00      0   0  1
  [34] .shstrtab         STRTAB          00000000 001ebd 00015e 00      0   0  1
  [35] .symtab           SYMTAB          00000000 00168c 000560 10     36  59  4
  [36] .strtab           STRTAB          00000000 001bec 0002d1 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

如上,可以看到程序被分成了很多的段,其中“.application_init”爲稍後步驟中自定義的一個段。

測試源代碼如下:

#include <stdio.h>
#include <string.h>

struct _s_application_init {
    int(*function)(void);
};

struct _s_application_init _init_start;//段".application_init"的起始地址,在*.lds文件中定義
struct _s_application_init _init_end;//段".application_init"的末尾地址,在*.lds文件中定義
#define __app_init_section __attribute__((section(".application_init")))
#define __application_init(function) \
    struct _s_application_init _s_a_init_##function  __app_init_section = {function}


static int application_init_a(void)
{
    printf("execute funtion : %s\n", __FUNCTION__);
    return 0;
}
__application_init(application_init_a);

static int application_init_b(void)
{
    printf("execute funtion : %s\n", __FUNCTION__);
    return 0;
}
__application_init(application_init_b);

static int application_init_c(void)
{
    printf("execute funtion : %s\n", __FUNCTION__);
    return 0;
}
__application_init(application_init_c);

int main(int argc, char **argv)
{
    /*
     * 從段的起始地址開始獲取數據,直到末尾地址
     */
    struct _s_application_init *pf_init = &_init_start;
    do {
        printf("Load init function from address %p\n", pf_init);
        pf_init->function();
        ++pf_init;
    } while (pf_init < &_init_end);
    return 0;
}

然後我們還需要編寫lds文件,首先使用命令生成默認的文件:

seven@root:~/section/$ ld --verbose > main.lds
seven@root:~/section/$ cat main.lds
GNU ld (GNU Binutils for Ubuntu) 2.26.1
  Supported emulations:
   elf_i386
   i386linux
   elf_iamcu
   elf32_x86_64
   elf_x86_64
   elf_l1om
   elf_k1om
   i386pep
   i386pe
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf32-i386", "elf32-i386",
	      "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib32"); SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/i686-linux-gnu/lib32"); SEARCH_DIR("=/usr/i686-linux-gnu/lib");
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  .hash           : { *(.hash) }
  .gnu.hash       : { *(.gnu.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
  .rel.dyn        :
    {
      *(.rel.init)
      *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
      *(.rel.fini)
      *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
      *(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*)
      *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
      *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
      *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
      *(.rel.ctors)
      *(.rel.dtors)
      *(.rel.got)
      *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
      *(.rel.ifunc)
    }
  .rel.plt        :
    {
      *(.rel.plt)
      PROVIDE_HIDDEN (__rel_iplt_start = .);
      *(.rel.iplt)
      PROVIDE_HIDDEN (__rel_iplt_end = .);
    }
  .init           :
  {
    KEEP (*(SORT_NONE(.init)))
  }
  .plt            : { *(.plt) *(.iplt) }
.plt.got        : { *(.plt.got) }
  .text           :
  {
    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  }
  .fini           :
  {
    KEEP (*(SORT_NONE(.fini)))
  }
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : { *(.rodata1) }
  .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gcc_except_table   : ONLY_IF_RO { *(.gcc_except_table
  .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO { *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO { *(.exception_ranges
  .exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gnu_extab      : ONLY_IF_RW { *(.gnu_extab) }
  .gcc_except_table   : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata	  : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
  .tbss		  : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array     :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array     :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : { KEEP (*(.jcr)) }
  .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : { *(.dynamic) }
  .got            : { *(.got) *(.igot) }
  . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 12 ? 12 : 0, .);
  .got.plt        : { *(.got.plt)  *(.igot.plt) }
  .data           :
  {
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : { *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  .bss            :
  {
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we don't
      pad the .data section.  */
   . = ALIGN(. != 0 ? 32 / 8 : 1);
  }
  . = ALIGN(32 / 8);
  . = SEGMENT_START("ldata-segment", .);
  . = ALIGN(32 / 8);
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : { *(.stab) }
  .stabstr       0 : { *(.stabstr) }
  .stab.excl     0 : { *(.stab.excl) }
  .stab.exclstr  0 : { *(.stab.exclstr) }
  .stab.index    0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment       0 : { *(.comment) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end ) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { *(.debug_macro) }
  .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}


==================================================

然後我們修改這個文件:

首先我們需要將默認文件的首尾“==================================================”包含這一行要刪除,不然會報格式錯誤

/usr/bin/ld:test.lds:1: syntax error
collect2: error: ld returned 1 exit status

然後選擇在“__bss_start”前添加我們自己的段

  ...

  . = .;

  _init_start = .;/* 獲取當前的地址賦值給__init_start,在源碼中有使用到,指向“.application_init”段的起始地址 */
  .application_init  : { *(.application_init) }/* 將“.application_init”的所有內容放在這一段 */
  _init_end = .;/* 獲取當前的地址賦值給__init_end,表示“.application_init”段的結束地址 */

  __bss_start = .;

  ...

 然後我們在鏈接的時候使用一下命令:

seven@root:~/section/$ gcc main.c -Tmain.lds

如無意外的情況下,即可編譯出最原始的“a.out”,如果出現錯誤,請燒香拜佛。

seven@root:~/section$ ./a.out 
Load init function from address 0x804a014
execute funtion : application_init_a
Load init function from address 0x804a018
execute funtion : application_init_b
Load init function from address 0x804a01c
execute funtion : application_init_c

可以看到依次的執行了三個初始化函數。

注:

  1. 初始化的宏放置的位置,直接影響初始化函數執行的順序,同一文件中行號越小越優先。(是註冊的順序,不是函數的位置)
  2. 多文件中的註冊,應該也會存在一定規律,如果初始化順序有特定的先後順序的,如需要先初始化GPIO,再初始化LED燈等,則可以採用其他方法進行順序限定(如優先級值和鏈表)。
  3. 在參考的文檔中,段的地址貌似可以指定,但是我沒有嘗試。

參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章