GCC Arm 12.2編譯提示 LOAD segment with RWX permissions 警告
- 2022 年 10 月 5 日
- 筆記
- C/CPP/ASM, Embed/Mobile, STM32
使用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文件, 要麼屏蔽掉它.
說明
這篇文章里有比較詳細的說明
//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啟動正常運行時並不存在修改的可能性.
因此結論是可以通過選項一, 簡單地將警告屏蔽掉
參考
- //github.com/raspberrypi/pico-sdk/issues/1029
- //stackoverflow.com/questions/73429929/gnu-linker-elf-has-a-load-segment-with-rwx-permissions-embedded-arm-project
- //github.com/OP-TEE/optee_os/issues/5471

