<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    elf各個段介紹(堆棧基礎知識)

    段基礎

    段表(Section Header Table)是一個以 Elf32_Shdr 結構體為元素的數組,每個結構體對應一個段,它描述了各個段的信息。ELF 文件頭的 e_shoff 成員給出了段表在 ELF 中的偏移,e_shnum 成員給出了段描述符的數量,e_shentsize 給出了每個段描述符的大小。

    typedef struct
    {
      Elf32_Word    sh_name;        /* Section name (string tbl index) */
      Elf32_Word    sh_type;        /* Section type */
      Elf32_Word    sh_flags;       /* Section flags */
      Elf32_Addr    sh_addr;        /* Section virtual addr at execution */
      Elf32_Off sh_offset;      /* Section file offset */
      Elf32_Word    sh_size;        /* Section size in bytes */
      Elf32_Word    sh_link;        /* Link to another section */
      Elf32_Word    sh_info;        /* Additional section information */
      Elf32_Word    sh_addralign;       /* Section alignment */
      Elf32_Word    sh_entsize;     /* Entry size if section holds table */
    } Elf32_Shdr;
    
    typedef struct
    {
      Elf64_Word    sh_name;        /* Section name (string tbl index) */
      Elf64_Word    sh_type;        /* Section type */
      Elf64_Xword   sh_flags;       /* Section flags */
      Elf64_Addr    sh_addr;        /* Section virtual addr at execution */
      Elf64_Off sh_offset;      /* Section file offset */
      Elf64_Xword   sh_size;        /* Section size in bytes */
      Elf64_Word    sh_link;        /* Link to another section */
      Elf64_Word    sh_info;        /* Additional section information */
      Elf64_Xword   sh_addralign;       /* Section alignment */
      Elf64_Xword   sh_entsize;     /* Entry size if section holds table */
    } Elf64_Shdr;

    使用 readelf 命令查看目標文件中完整的段:

    $ readelf -S elfDemo.o
    There are 15 section headers, starting at offset 0x41c:
    
    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .group            GROUP           00000000 000034 000008 04     12  16  4
      [ 2] .text             PROGBITS        00000000 00003c 000078 00  AX  0   0  1
      [ 3] .rel.text         REL             00000000 000338 000048 08   I 12   2  4
      [ 4] .data             PROGBITS        00000000 0000b4 000008 00  WA  0   0  4
      [ 5] .bss              NOBITS          00000000 0000bc 000004 00  WA  0   0  4
      [ 6] .rodata           PROGBITS        00000000 0000bc 000004 00   A  0   0  1
      [ 7] .text.__x86.get_p PROGBITS        00000000 0000c0 000004 00 AXG  0   0  1
      [ 8] .comment          PROGBITS        00000000 0000c4 000012 01  MS  0   0  1
      [ 9] .note.GNU-stack   PROGBITS        00000000 0000d6 000000 00      0   0  1
      [10] .eh_frame         PROGBITS        00000000 0000d8 00007c 00   A  0   0  4
      [11] .rel.eh_frame     REL             00000000 000380 000018 08   I 12  10  4
      [12] .symtab           SYMTAB          00000000 000154 000140 10     13  13  4
      [13] .strtab           STRTAB          00000000 000294 0000a2 00      0   0  1
      [14] .shstrtab         STRTAB          00000000 000398 000082 00      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),
      p (processor specific)
    

    注意,ELF 段表的第一個元素是被保留的,類型為 NULL。

    下面將介紹三個非常重要的段

    kkFhYGtngn.png!large
    各個段在內存中的具體位置,圖中的地址從上到下為從低地址到高地址

    一般情況,一個程序本質上都是由 bss段、data段、text段三個段組成——這是計算機程序設計中重要的基本概念。
    text段:就是放程序代碼的,編譯時確定,只讀;

    data段:存放在編譯階段(而非運行時)就能確定的數據,可讀可寫。也就是通常所說的靜態存儲區,賦了初值的全局變量賦初值的靜態變量存放在這個區域,常量也存放在這個區域;

    bss段:定義而沒有賦初值的全局變量和靜態變量,放在這個區域

    .bss段

    在采用段式內存管理的架構中(比如intel的80x86系統),bss段(Block Started by Symbol segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域,一般在初始化時bss 段部分將會清零(bss段屬于靜態內存分配,即程序一開始就將其清零了)。比如,在C語言程序編譯完成之后,已初始化的全局變量保存在.data 段中,未初始化的全局變量保存在.bss 段中。

    .text段

    用于存放程序代碼的區域, 編譯時確定, 只讀。更進一步講是存放處理器的機器指令,當各個源文件單獨編譯之后生成目標文件,經連接器鏈接各個目標文件并解決各個源文件之間函數的引用,與此同時,還得將所有目標文件中的.text段合在一起,但不是簡單的將它們“堆”在一起就完事,還需要處理各個段之間的函數引用問題。

    .data段

    用于存放在編譯階段(而非運行時)就能確定的數據,可讀可寫。也是通常所說的靜態存儲區,賦了初值的全局變量、常量和靜態變量都存放在這個域。

    關于.bss段和.data段更詳細的區別我們用下面兩個程序來介紹

    程序一
    int array[65537];
    void main()
    {
        ......
    } 
    程序二
    int array[65537] = {1, 2, 3, 4, 5, 6 };
    void main()
    {
        ......
    } 

    發現程序2編譯之后所得的可執行文件比程序1大得多。
    區別很明顯,程序1位于bss段,程序2位于data段,兩者的區別在于:

    • 全局的未初始化變量存在于bss段中,具體體現為一個占位符,全局的已初始化變量存于data段中,而函數內的自動變量都在棧上分配空間。
    • bss不占用可執行文件空間,其內容由操作系統初始化(清零),裸機程序需要自行手動清零。
    • 而data段則需要占用可執行文件空間,其內容由程序初始化,因此造成了上述情況。

    堆棧基礎知識

    申請方式

    • stack: 由系統自動分配。 int a; 系統自動在棧中為a開辟空間
    • heap: 需要程序員自己申請,并指明大小(例如malloc)。

    申請后系統的響應

    • 棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
    • 堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。

    申請大小的限制

    • 棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
    • 堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

    申請效率的比較:

    • 棧:由系統自動分配,速度較快。但程序員是無法控制的。
    • 堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
      另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活

    堆和棧中的存儲內容

    • 棧:在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的。
      當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
    • 堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

    參考

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类