1. 適用範圍
本文檔介紹了在Shell命令行執行內核模塊內函數實現原理。
在VxWorks中,系統自帶有在Shell命令行直接執行驅動、應用內函數的功能,此功能完善了驅動開發工程師、應用開發工程師的開發、調試的手段。爲了讓這類工程師能快速適應SylixOS,據此開發了類似的功能模塊,目前第一版支持在Shell命令行執行內核模塊內的函數。
2. SylixOS內核模塊動態加載原理
2.1 SylixOS中的ELF文件
SylixOS中的ELF文件主要有三種:
- obj文件: 一個源文件編譯完成後,編譯器將源文件內所有函數的指令塊拼接形成TEXT節,將數據塊拼接行成DATA節,同樣還會根據需要生成其它節(如符號表、重定位表)。這些節拼接在一起形成obj文件。
- 內核模塊文件: 內核模塊文件是多個obj文件組合形成的一個大文件,它將多個obj文件生成的TEXT節、DATA節、符號表、重定位表各自拼接爲更大的TEXT節、DATA節、符號表和重定位表。
- 位置無關ELF文件: SylixOS的應用程序和動態庫都使用位置無關ELF文件格式,它支持代碼段共享和寫時拷貝。
2.2 內核模塊的ELF文件格式
內核模塊包括ELF頭、程序頭表(也可以沒有)和段節頭表。
2.2.1 ELF頭
ELF頭描述了整個文件的基本屬性,比如ELF文件版本,目標機器型號,程序入口地址等,如下所示。
typedef struct elf32_hdr { unsigned char e_ident[EI_NIDENT]; /*標示該文件爲可執行的object文件 */ Elf32_Half e_type; /*標示object文件的類型 */ Elf32_Half e_machine; /*指出該object需要的體系結構 */ Elf32_Word e_version; /*確定object的文件版本 */ Elf32_Addr e_entry; /*是系統第一個傳輸控制的虛擬地址 */ Elf32_Off e_phoff; /*保持了程序頭表在文件中的偏移量 */ Elf32_Off e_shoff; /*保持着段節頭表在文件中的偏移量 */ Elf32_Word e_flags; /*保存着相關文件的處理器標誌 */ Elf32_Half e_ehsize; /*保存着ELF頭大小 */ Elf32_Half e_phentsize; /*一個程序頭的大小 */ Elf32_Half e_phnum; /*程序頭表的個數 */ Elf32_Half e_shentsize; /*一個段節頭的大小 */ Elf32_Half e_shnum; /*段節頭表中的段節頭數目 */ Elf32_Half e_shstrndx; /*段節名字符表相關入口的段節頭表索引 */ } Elf32_Ehdr;
2.2.2 ELF文件頭校驗
SylixOS中使用insmod命令或modulereg命令加載內核模塊時,首先會讀取ELF文件的文件頭,並校驗ELF文件頭的有效性。
在SylixOS中,ELF文件頭的e_ident數值應固定爲0x7f、‘E’、‘L’、‘F’四個字節;e_machine會根據arm、PowerPC、x86等不同架構爲不同數值,以此來區分該ELF文件是否是架構適配的文件。
2.2.3 獲取內核模塊版本
SylixOS的ELF文件中都包含有“__sylixos_version”符號,該符號數值爲一個字符串,字符串的內容爲ELF內核模塊版本。SylixOS將該版本與內核版本進行比較,確定ELF文件與SylixOS版本是否兼容。
2.3 ELF文件加載
SylixOS會根據ELF文件頭中的e_type數值對ELF文件進行不同種類的加載操作。ET_REL爲可重定位文件,內核模塊屬於可重定位文件;ET_EXEC和ET_DYN爲可執行文件,應用程序、動態庫屬於可執行文件。
根據e_shentsize和s_shnum可以計算出整個ELF段節頭表的大小,根據e_shoff可以獲取ELF段節頭表的偏移地址,由此將整個段節頭表讀入內存。
根據每個段節頭表中的每個段節頭的信息可以獲取每個段的類型、大小、存儲地址,由此將每個段的內容讀入內存。
2.3.1 段節頭
段節頭表中的每一個段節頭都有如下所示的結構。
typedef struct { Elf32_Word sh_name; /* 段名稱,值爲段頭字符表的索引 */ Elf32_Word sh_type; /* 段類型 */ Elf32_Word sh_flags; /* 段屬性 */ Elf32_Addr sh_addr; /* 段在內存中的位置 */ Elf32_Off sh_offset; /* 段字節偏移量(從文件開始計數) */ Elf32_Word sh_size; /* 段的字節大小 */ Elf32_Word sh_link; /* 段報頭表的索引連接 */ Elf32_Word sh_info; /* 保存額外信息 */ Elf32_Word sh_addralign; /* 段地址對齊的約束 */ Elf32_Word sh_entsize; /* 段中每個入口的字節大小 */ } Elf32_Shdr;
2.3.2 加載各個段
首先爲符號表、重定位段、字符串表申請內存,然後將這些段讀入內存。
符號表的sh_type爲SHT_SYMTAB,重定位節的sh_type爲SHT_RELA(帶加數的重定位項)和SHT_REL(不帶加數的重定位項),字符串表的sh_type爲SHT_STRTAB。
其次爲數據段、代碼段申請內存,並將數據段和代碼段讀入內存。
2.3.3 重定位操作
在處理目標文件時,鏈接器會遍歷所有的重定位條目,碰到外部引用時,鏈接器會找到外部引用函數的確切地址,並且把它寫回到指令操作數所佔用的地址單元。像這樣的操作,稱之爲重定位操作。對於每個需要重定位的代碼段或數據段,都會有一個相應的重定位表。
SylixOS加載內核模塊時,會對重定位節進行重定位處理,本文對重定位暫不做具體分析。
2.4 導出符號表
2.4.1 符號表
typedef struct elf32_sym { Elf32_Word st_name; /* 符號名 */ Elf32_Addr st_value; /* 符號相應的值 */ Elf32_Word st_size; /* 符號佔用的字節大小 */ unsigned char st_info; /* 符號類型和綁定信息 */ unsigned char st_other; /* 目前爲0 */ Elf32_Half st_shndx; /* 符號所在的段 */ } Elf32_Sym;
符號類型
STT_NOTYPE表示是未知類型符號;
STT_OBJECT表示該符號是數據對象,比如變量、數組等;
STT_FUNC表示該符號是個函數或其他可執行代碼;
STT_SECTION表示該符號表示一個段,這種符號必須是STB_LOCAL的;
STT_FILE表示該符號表示文件名,一般都是該目標文件所對應的源文件名。
符號作用域
STB_LOCAL表示是局部符號,對於目標文件的外部不可見;
STB_GLOBAL表示是全局符號,外部可見;
STB_WEAK表示是弱引用。
符號所在段
SHN_UNDEF表示該符號未定義,該符號在本目標文件被引用到,但是定義在其他目標文件中;
SHN_COMMON表示該符號是一個“COMMON塊”類型的符號,一般來說,未初始化的全局符號定義就是這種類型。
2.4.2 目前的導出符號表操作
SylixOS中insmod命令或modulereg命令進行內核模塊加載時,只會將全局符號進行導出。在模塊控制塊LW_LD_EXEC_MODULE中定義了符號緩衝,用於存儲模塊內的符號節點。當有符號需要導出時,從模塊的符號緩衝內開闢內存節點,存儲符號的字符串,符號的入口地址等信息。
並且將該符號佔用的所有內存節點按照哈希表排序,以方便快速查找與定位。
3. Shell命令行執行模塊內函數實現
3.1 構建模塊內部函數映射表
由於目前系統只會對全局符號進行導出,導致當需執行模塊內部非全局符號時就無法獲取其入口地址,所以需要對模塊內部函數構建函數映射表。
構建模塊內部函數映射表的流程與insmod的流程基本類似,需要重新對ELF文件進行解析,但是有兩點區別:
第一點,無需對函數進行重定位。因爲內核模塊此時已經加載,在獲取函數的相對偏移地址後,只需獲取內核模塊加載在內存後的首地址,即可計算出內核模塊內函數在內存中的實際地址。
第二點,在導出符號時,應該修改過濾條件,使內部函數也可以加入到內核的符號緩衝中。
3.2 功能模塊的使用
3.2.1 解析模塊ELF文件
相關功能被製作成SymbolShell.ko,使用功能前首先加載SymbolShell.ko文件,同時加載測試用的test_module.ko,如下圖所示。
SymbolShell.ko會註冊call命令,此命令提供瞭如下圖所示的幾個功能。
3.2.2 打印模塊內函數及其相對偏移
該模塊以會話的概念管理對某個模塊內函數的調用,想調用某模塊內的函數時,首先需要進入該模塊的會話。
以test_module.ko爲例,如下圖所示使用call –m test_module.ko命令進入test_module.ko的會話。然後使用call –l命令即可查看test_module.ko內的符號。
3.2.3 執行模塊內函數
使用call test_func即可調用test_module.ko內的test_func函數,如下圖所示,最終使用call –e命令可退出當前模塊的會話。