使用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
這是 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命令將顯示每個段包含哪些部分, 可以通過這些資訊計算出聯結器對映需要如何更新, 才能將程式碼部分和可寫的資料部分分開.
--no-warn-rwx-segments
選項-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啟動正常執行時並不存在修改的可能性.
因此結論是可以通過選項一, 簡單地將警告遮蔽掉