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文件格式
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文件加载
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.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块”类型的符号,一般来说,未初始化的全局符号定义就是这种类型。
3. Shell命令行执行模块内函数实现
3.1 构建模块内部函数映射表
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命令可退出当前模块的会话。