GCC Arm 12.2編譯提示 LOAD segment with RWX permissions 警告

2022-10-06 06:01:04

使用GCC Arm工具鏈開發的專案, 在升級到 arm-gnu-toolchain-12.2 之後, 編譯出現警告

arm-gnu-toolchain-12.2.mpacbti-bet1-x86_64-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/12.2.0/../../../../arm-none-eabi/bin/ld: warning: Build/app.elf has a LOAD segment with RWX permissions

關於 LOAD segment with RWX permissions 警告

這是 Binutils 2.39 引入的一個新的安全型別的警告, GCC在升級版本時會帶著新版本的 Binutils 一起釋出. 如果要消除這個警告, 要麼修改ld檔案, 要麼遮蔽掉它.

說明

這篇文章裡有比較詳細的說明
https://www.redhat.com/en/blog/linkers-warnings-about-executable-stacks-and-segments

The executable segment warnings

當程式載入記憶體時會分段載入, 一些屬於可執行的程式碼,一些屬於資料, 可讀或者可讀可寫, 可能還有一些用於其它特殊用途. 每一段記憶體都會區分可讀、可寫和可執行這三個屬性, 如果一個記憶體段同時具有這三種屬性, 則存在受到攻擊的可能性, 因此在這種情況下連結器將產生以下警告

warning: <file> has a LOAD segment with RWX permissions

這個警告表示elf檔案中存在一個或多個存在安全問題的段, 可以通過執行readelf程式進行檢視

readelf -lW <file>

注意: 在readelf的輸出中, 段的可執行標誌被標記為E而不是X, 三個屬性的標識為RWE而不是RWX. 警告出現的常見原因是使用自定義連線指令碼進行連結, 該指令碼未將程式碼和資料分成不同的段, 所以最好的解決辦法是更新連線指令碼. readelf命令將顯示每個段包含哪些部分, 可以通過這些資訊計算出聯結器對映需要如何更新, 才能將程式碼部分和可寫的資料部分分開.

消除 LOAD segment with RWX permissions 警告

選項一: 使用 --no-warn-rwx-segments 遮蔽

  • 如果連線使用的是ld, 可以用--no-warn-rwx-segments選項
  • 如果連線使用的是gcc, 直接用會提示無法識別的選項, 需要用-Wl,--no-warn-rwx-segments這樣的方式

選項二: 修改連線描述

對於存在問題的elf, 可以通過這個命令檢視檔案結構, 注意後面的Flg部分, RWE分別表示Read,Write,Execute.

$ readelf -lW app.elf 

Elf file type is EXEC (Executable file)
Entry point 0x15e1
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010000 0x00000000 0x00000000 0x026f4 0x026f4 RWE 0x10000
  LOAD           0x020000 0x20000000 0x000026f4 0x00088 0x00334 RW  0x10000
  LOAD           0x000334 0x20000334 0x0000277c 0x00000 0x00004 RW  0x10000

 Section to Segment mapping:
  Segment Sections...
   00     .isr_vector .text .rodata .init_array .fini_array 
   01     .data .bss 
   02     ._user_heap_stack 

其中LOAD 0x010000 0x08000000 0x08000000 0x03ffc 0x03ffc RWE 0x10000就是存在問題的segment, 如果要消除這個警告, 可以將ld檔案中的 .init_array 和 .fini_array 這部分註釋掉, 程式碼如下. 這部分是 startup 檔案中 __libc_init_array使用的, 如果不需要可以直接刪除, 對應的編譯引數也可以加上-nostartfiles.

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH

這樣編譯完之後的結果如下, 第一個segment中, Flg變成了R E就沒問題了.

$ readelf -lW app.elf 

Elf file type is EXEC (Executable file)
Entry point 0x1549
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x010000 0x00000000 0x00000000 0x02654 0x02654 R E 0x10000
  LOAD           0x020000 0x20000000 0x00002654 0x00088 0x00318 RW  0x10000
  LOAD           0x000318 0x20000318 0x000026dc 0x00000 0x00300 RW  0x10000

 Section to Segment mapping:
  Segment Sections...
   00     .isr_vector .text .rodata 
   01     .data .bss 
   02     ._user_heap_stack 

上面這種修改並不是通用的, 對於需要使用libc的應用而言並不可行.

實際上, 對於Cortex M系列的MCU而言, elf中第一個segment對應的實際上是燒錄到flash中的部分(可執行), 第二個segment對應的才是執行時可讀寫的記憶體部分(資料), 第一個segment在通過flash啟動正常執行時並不存在修改的可能性.

因此結論是可以通過選項一, 簡單地將警告遮蔽掉

參考