目錄

RPI4 MAC 模組無法載入問題

問題描述

我在 RPI4 使用 FreeBSD 15.0 的時候,編譯 MAC 模組的時候,開機的時候並不會自動載入。

如下我在自己的 mac module 目錄使用 Makefile 編譯:

kola@generic:~/proj/mac_casper $ ls
Makefile  checker.h  mac_casper.c  mac_casper.ko.full test
README.md  checker.o  mac_casper.h  mac_casper.o  test_program
a.txt   document  mac_casper.kld  machine   vnode_if.h
casper_src  export_syms  mac_casper.ko  opt_global.h  vnode_if_newproto.h
checker.c  label.h   mac_casper.ko.debug script   vnode_if_typedef.h
kola@generic:~/proj/mac_casper $ cat Makefile 
.include <bsd.own.mk>

KMOD=   mac_casper
SRCS=   mac_casper.c
SRCS+= mac_casper.h
SRCS+= checker.c
SRCS+= checker.h
SRCS+=  label.h
SRCS+= vnode_if.h

.include <bsd.kmod.mk>

如上,Makefile 基本上就是使用 sys/conf/kmod.mk 的 Makefile 編譯。

然後我們看一下 sudo make all install 編譯的流程。

kola@generic:~/proj/mac_casper $ sudo make all install
machine -> /usr/src/sys/arm64/include
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -p
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -q
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -h
touch opt_global.h
Warning: Object directory not changed from original /home/kola/proj/mac_casper
cc  -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -include /home/kola/proj/mac_casper/opt_global.h -I. -I/usr/src/sys -I/usr/src/sys/contrib/ck/include -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fPIC -ffile-prefix-map=/usr/src/sys=/usr/src/sys -ffile-prefix-map=/home/kola/proj/mac_casper=/usr/obj/usr/src/arm64.aarch64/sys/modules/mac_casper -fdebug-prefix-map=./machine=/usr/src/sys/arm64/include    -MD  -MF.depend.mac_casper.o -MTmac_casper.o -mgeneral-regs-only -ffixed-x18 -mbranch-protection=standard -mno-outline-atomics -ffreestanding -fwrapv -fstack-protector  -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wswitch -Wno-error=tautological-compare -Wno-error=empty-body -Wno-error=parentheses-equality -Wno-error=unused-function -Wno-error=pointer-sign -Wno-error=shift-negative-value -Wno-address-of-packed-member -Wno-format-zero-length     -std=gnu17 -c mac_casper.c -o mac_casper.o
cc  -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -include /home/kola/proj/mac_casper/opt_global.h -I. -I/usr/src/sys -I/usr/src/sys/contrib/ck/include -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fPIC -ffile-prefix-map=/usr/src/sys=/usr/src/sys -ffile-prefix-map=/home/kola/proj/mac_casper=/usr/obj/usr/src/arm64.aarch64/sys/modules/mac_casper -fdebug-prefix-map=./machine=/usr/src/sys/arm64/include    -MD  -MF.depend.checker.o -MTchecker.o -mgeneral-regs-only -ffixed-x18 -mbranch-protection=standard -mno-outline-atomics -ffreestanding -fwrapv -fstack-protector  -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wswitch -Wno-error=tautological-compare -Wno-error=empty-body -Wno-error=parentheses-equality -Wno-error=unused-function -Wno-error=pointer-sign -Wno-error=shift-negative-value -Wno-address-of-packed-member -Wno-format-zero-length     -std=gnu17 -c checker.c -o checker.o
ld -m aarch64elf -d -warn-common --build-id=sha1 --no-relax  -r  -o mac_casper.kld mac_casper.o checker.o 
:> export_syms
awk -f /usr/src/sys/conf/kmod_syms.awk mac_casper.kld  export_syms | xargs -J % objcopy % mac_casper.kld
ld -m aarch64elf -Bshareable -znotext -znorelro -d -warn-common --build-id=sha1 --no-relax  -o mac_casper.ko mac_casper.kld
ld: warning: -z norelro ignored
objcopy --strip-debug mac_casper.ko
install -T release -o root -g wheel -m 444   mac_casper.ko /boot/modules/
kldxref /boot/modules

kola@generic:~/proj/mac_casper $ ls /boot/modules/
mac_casper.ko

可以認真看一下上面的輸出:

  1. vnode_if.src 是一個長得像清單的原始定義檔,裡面記錄了各種檔案操作(如 open, read, write)
    1. awk 讀取 vnode_if.src 的邏輯,並按照模板 自動生出 vnode_if.h 這個 C 語言標頭檔
  2. “opt_global.h” 是核心的編譯選項
  3. .c 編譯成 .o
    1. -fPIC:aarch64 被強制使用的位置無關代碼(Position Independent Code)參數
    2. -D_KERNEL / -DKLD_MODULE:定義這是要跑在核心空間的載入模組
    3. -mbranch-protection=standard:aarch64 特有的分支保護機制(防範 BTI/PAC 攻擊)
  4. ld -r -o mac_casper.kld 將多個 .o 檔案變成 .kld 檔案
  5. :> export_syms: 用來建立一個空的 export_syms 檔案,這個檔案是用來存放你想要「公開」給其他核心模組使用的函數清單
  6. awk -f ...kmod_syms.awk mac_casper.kld export_syms | xargs ... objcopy: 掃描你的 mac_casper.kld 檔案,並對照 export_syms,objcopy 接手 awk 傳過來的清單,把這些內部符號通通變成「局部符號」(Local Symbols),讓外面的人看不到
  7. ld -m aarch64elf -Bshareable -znotext ...: 這是產生最終檔案 .ko 的步驟
    1. -Bshareable: 將模組封裝成「共享物件」(Shared Object)格式
    2. -znotext: 允許在唯讀段(text)中存在重定位資訊,這能縮小檔案體積
    3. --no-relax: ,用來關閉連結器的指令優化(Relaxation),避免引發 VNET 等核心功能的 Bug
  8. ld: warning: -z norelro ignored: 代表連結器不理會「關閉唯讀重定位(norelro)」的要求。這通常是因為 aarch64 的連結器有自己的安全邏輯,這不影響功能,但顯示了系統正在「強迫」執行某些安全規範
  9. objcopy --strip-debug mac_casper.ko: 把檔案中巨大的「偵錯資訊(Debug Symbols)」通通切掉
  10. install ... 安裝到 /boot/modules/ 底下
  11. kldxref /boot/modules: 系統會掃描該目錄下所有的 .ko 檔案,提取它們的身分資訊,並將其彙整成一個名為 linker.hints 的二進位索引檔
    1. 當 loader (也就是你看到的 Revision 3.0 Loader) 在開機階段要載入模組時,它不必逐一打開目錄下的幾百個檔案去檢查誰是誰。它只要讀取 linker.hints 這張「地圖」,就能立刻知道 mac_casper 躲在 mac_casper.ko 檔案裡。

所以問題就是,我在開機的時候沒辦法載入此模組,可以看到 /boot/modules 底下沒有 .hints 檔案,所以代表此 .ko 檔案怪怪的

-Bshareable

在 FreeBSD,一般的 Kernel Module 在 AMD64 是用 Relocatable Object (ET_REL),但在其他架構下,就是一般的 Shared Object (ET_DYN),像一個完整的 .so 程式庫。

可以參考 [原创]把 .so 变成 .ko:一次 ELF 格式的奇妙之旅

解法

先跑 make all 在跑 sudo make install

kola@generic:~/proj/mac_casper $ make all
machine -> /usr/src/sys/arm64/include
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -p
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -q
awk -f /usr/src/sys/tools/vnode_if.awk /usr/src/sys/kern/vnode_if.src -h
touch opt_global.h
Warning: Object directory not changed from original /home/kola/proj/mac_casper
cc  -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -include /home/kola/proj/mac_casper/opt_global.h -I. -I/usr/src/sys -I/usr/src/sys/contrib/ck/include -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fPIC -ffile-prefix-map=/usr/src/sys=/usr/src/sys -ffile-prefix-map=/home/kola/proj/mac_casper=/usr/obj/usr/src/arm64.aarch64/sys/modules/mac_casper -fdebug-prefix-map=./machine=/usr/src/sys/arm64/include    -MD  -MF.depend.mac_casper.o -MTmac_casper.o -mgeneral-regs-only -ffixed-x18 -mbranch-protection=standard -mno-outline-atomics -ffreestanding -fwrapv -fstack-protector  -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wswitch -Wno-error=tautological-compare -Wno-error=empty-body -Wno-error=parentheses-equality -Wno-error=unused-function -Wno-error=pointer-sign -Wno-error=shift-negative-value -Wno-address-of-packed-member -Wno-format-zero-length     -std=gnu17 -c mac_casper.c -o mac_casper.o
cc  -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -include /home/kola/proj/mac_casper/opt_global.h -I. -I/usr/src/sys -I/usr/src/sys/contrib/ck/include -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fPIC -ffile-prefix-map=/usr/src/sys=/usr/src/sys -ffile-prefix-map=/home/kola/proj/mac_casper=/usr/obj/usr/src/arm64.aarch64/sys/modules/mac_casper -fdebug-prefix-map=./machine=/usr/src/sys/arm64/include    -MD  -MF.depend.checker.o -MTchecker.o -mgeneral-regs-only -ffixed-x18 -mbranch-protection=standard -mno-outline-atomics -ffreestanding -fwrapv -fstack-protector  -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wswitch -Wno-error=tautological-compare -Wno-error=empty-body -Wno-error=parentheses-equality -Wno-error=unused-function -Wno-error=pointer-sign -Wno-error=shift-negative-value -Wno-address-of-packed-member -Wno-format-zero-length     -std=gnu17 -c checker.c -o checker.o
ld -m aarch64elf -warn-common --build-id=sha1 --no-relax -zbti-report=error  -r  -o mac_casper.kld mac_casper.o checker.o 
:> export_syms
awk -f /usr/src/sys/conf/kmod_syms.awk mac_casper.kld  export_syms | xargs -J % objcopy % mac_casper.kld
ld -m aarch64elf -Bshareable -znotext -znorelro -warn-common --build-id=sha1 --no-relax -zbti-report=error  -o mac_casper.ko mac_casper.kld
objcopy --strip-debug mac_casper.ko
kola@generic:~/proj/mac_casper $ sudo make install
install -T release -o root -g wheel -m 444   mac_casper.ko /boot/modules/
kldxref /boot/modules
kola@generic:~/proj/mac_casper $ ls /boot/modules/
linker.hints mac_casper.ko

可以看到他們的差別是

  1. 失敗版,多了 -d,然後沒有 -zbti-report=error
  2. 成功版本,則是相反

所以 -d 是問題,“Force allocation of common symbols” (強制分配公共符號)。簡單來說,這個選項的存在是因為早期連結器在處理「未初始化全域變數」(Common Symbols)時比較懶惰,而 -d 則是強迫它立刻動手處理。

使用 -d 選項:連結器被要求「即便現在只是在做中間產物(如 .kld 檔案),也要立刻幫這些變數分配空間」。使用 -d 選項:連結器被要求「即便現在只是在做中間產物(如 .kld 檔案),也要立刻幫這些變數分配空間」。

那為什麼多了 -d,可以看如下:

/module-can-not-load/image.png

kola@generic:~/proj/mac_casper $ make -V LINKER_TYPE -V LINKER_VERSION
lld
190107
kola@generic:~/proj/mac_casper $ sudo make -V LINKER_TYPE -V LINKER_VERSION
bfd
24400
kola@generic:~/proj/mac_casper $ 

原來是 sudo 底下會抓到錯誤的 ld,不是用 lld,而是 bfd,到至最後的 .ko 的 .text 偏移量沒有對其 4k。

kola@generic:~/proj $ readelf -S no_root.ko | grep .text
  [10] .text             PROGBITS        0000000000011ac8 001ac8 004098 00  AX  0   0  4
kola@generic:~/proj $ readelf -S root.ko | grep .text
  [ 9] .text             PROGBITS        00000000000017f8 0017f8 004098 00  AX  0   0  4

/module-can-not-load/image-1.png

kola@generic:~/proj/mac_casper $ which ld
/usr/bin/ld
kola@generic:~/proj/mac_casper $ sudo which ld
/usr/local/bin/ld
kola@generic:~/proj/mac_casper $ sudo ls -alh /usr/local/bin/ld
-r-xr-xr-x  4 root wheel  2.7M Apr 23 02:35 /usr/local/bin/ld
kola@generic:~/proj/mac_casper $ echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/home/kola/bin
kola@generic:~/proj/mac_casper $ sudo env | grep PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bi

因為 PATH 導致我們優先使用 local 底下的 ld。