LinuxZone

LinuxZone.net
Linux系统网——精选每一篇高品质的技术干货
  1. 首页
  2. 开源快讯
  3. 正文

Linux内核提供管理内核符号可见性的方法,解决办法

2023年5月12日 603点热度

linux 调用内核函数_linux内核 sleep函数_linux 调用内核函数

Linux内核为了减轻命名空间的污染,并做到正确的信息隐藏,内核提供了管理内核符号可见性的方式,没有被EXPORT_SYMBOL相关的宏导入的变量或函数是不能直接使用的,为了说明并解决这个问题,我们不妨先看如下一段内核模块,功能为复印超级块super_block结构中一些域的值。

我们晓得vfs(虚拟文件系统)是用super_block(超级块)来描述整个文件系统的信息,内核在对一个文件系统进行初始化和注册时,就为其分配了一个super_block,该文件系统卸载时,其对应的super_block也会被手动删掉。super_block结构中有一个list_head类型的数组s_list拿来把系统中的super_block组成一个单向循环数组,并使用一个称作super_blocks的全局变量来指向该单向循环数组中的第一个元素。super_block中还有一个称作s_inodes的数组,指向链接该超级块中所有的inode的数组i_sb_list。我们也使用了载流子锁spin_lock对数组的相关操作进行了加锁,保护共享变量,如今看内核模块:

#include 
#include 
#include 
#include 
#include 
#include 
#include  //方法二
 
/*
*方法二,使用kallsyms_lookup_name()查找函数或变量的虚拟地址
*使用时,需要注释其它方法的代码,取消此处及下面方法二的注释
spinlock_t * sb_lock_address;
struct list_head * super_blocks_address;
*/
 
/*
*方法三,内核模块中直接使用内核函数的虚拟地址
*使用时,需要注释其他方法的代码,取消此处及下面方法三的注释
#define SUPER_BLOCKS_ADDRESS 0xffffffff91d2efe0
#define SB_LOCK_ADDRESS 0xffffffff922f35d4
*/
 
static int __init my_init(void)
{
struct super_block *sb;
struct list_head *pos;
struct list_head *linode;
struct inode *pinode;
unsigned long long count = 0;
 
printk("nPrint some fields of super_blocks:n");
 
/*
*方法二
sb_lock_address = (spinlock_t *)kallsyms_lookup_name("sb_lock");
super_blocks_address = (struct list_head *)kallsyms_lookup_name("super_blocks");
spin_lock(sb_lock_address);
list_for_each(pos, super_blocks_address) {
*/
 
/*
*方法三
spin_lock((spinlock_t *)SB_LOCK_ADDRESS);
list_for_each(pos, (struct list_head *)SUPER_BLOCKS_ADDRESS) {
*/
 
/*此处使用了未导出变量,若使用方法二或方法三时需要注释以下两行*/
spin_lock(&sb_lock); //加锁,此处使用了未导出的变量
list_for_each(pos, &super_blocks) {
 
sb = list_entry(pos, struct super_block, s_list);
printk("dev_t:%d:%d", MAJOR(sb->s_dev),MINOR(sb->s_dev));
//打印文件系统所在设备的主设备号和次设备号
printk("file_type name:%sn", sb->s_type->name);
//打印文件系统名
 
list_for_each(linode, &sb->s_inodes) {
pinode=list_entry(linode, struct inode, i_sb_list);
count++;
printk("%lut", pinode->i_ino); //打印索引节点号
}
}
//spin_unlock(sb_lock_address); //方法二
//spin_unlock(SB_LOCK_ADDRESS); //方法三
spin_unlock(&sb_lock); //解锁,此处使用了未导出的变量
printk("The number of inodes:%llun", sizeof(struct inode)*count);
return 0;
}
 
static void __exit my_exit(void)
{
printk("unloading…n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

里面的内核模块中,我们使用list_for_each函数来遍历系统中的链接所有super_block的单向循环数组s_list,此宏有两个参数,第一个参数是pos,是一个输出型参数,用于保存每次遍历得到的list_head类型的结点的地址,第二个参数是head,是一个输入型参数,用于向要遍历的数组传递头结点,list_for_each函数在4.19内核中定义如下:

#define list_for_each(pos, head) 
for (pos = (head)->next; pos != (head); pos = pos->next)

list_for_each函数只能遍历超级块中的单向循环数组s_list,不能得到正在被遍历的超级块的地址,此时难以访问超级块的其它数组,我们再使用内核中的list_entry函数,通过当前超级块中的成员s_list的地址,获得当前超级块的地址。该函数有三个参数,第一个参数是指向结构体成员的表针,第二个参数是结构体的类型,第三个参数是结构体成员的名称s_list,该函数最后返回结构体的首地址。list_for_each在4.19内核中定义如下:

#define hlist_entry(ptr, type, member) container_of(ptr,type,member)

可以看见,它是container_of宏的一个封装,我们再看内核中的container_of宏,定义如下:

#define container_of(ptr, type, member) ({ 
void *__mptr = (void *)(ptr); 
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && 
!__same_type(*(ptr), void), 
"pointer type mismatch in container_of()"); 
((type *)(__mptr - offsetof(type, member))); })

本内核模块对应的Makefile文件如下:

obj-m:=print_sb.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

在使用内核中未被导入的变量时,执行make命令进行编译,发生如下错误:

linux 调用内核函数_linux内核 sleep函数_linux 调用内核函数

编译报错,‘sb_lock’undeclared(firstuseinthisfunction);,‘super_blocks’undeclared(firstuseinthisfunction);,即sb_lock和super_blocks变量没有定义,实际上在内核中早已定义了这两个变量,包含在头文件fs.h和spinlock.h中,在内核源码中如下:

在4.19版内核fssuper.c中,定义了super_blocks变量来指向super_block结构中的s_list双数组的数组头linux 调用内核函数,其中s_list是拿来链接系统中已安装文件系统超级块的单向循环数组,也定义了sb_lock锁变量对超级块的相关操作进行加锁,如右图:

linux内核 sleep函数_linux 调用内核函数_linux 调用内核函数

编译报错的诱因就是在内核中并没有导入sb_lock和super_blocks变量,这么问题来了:

1、我们为何不导入更多的变量或则函数来供我们使用呢?

2、我们可以使用内核中没有导入的函数或变量吗?假如可以使用,怎么使用?

下边给出一些技巧来使用内核中未被导入的变量或函数linux 调用内核函数,并进行验证。

方式一:使用EXPORT_SYMBOL宏导入函数或变量

Linux内核提供了一个便捷的方式拿来管理符号的对模块外部的可见性,即内核符号表。在4.19版内核includelinuxexport.h中,定义了EXPORT_SYMBOL宏,如右图:

linux 调用内核函数_linux 调用内核函数_linux内核 sleep函数

假如我们要使用内核中的变量或函数,可以使用上图中的宏,在函数或变量定义后使用如下宏,之后编译内核:

EXPORT_SYMBOL(sb_lock);

或则

EXPORT_SYMBOL_GPL(sb_lock);

此时EXPORT_SYMBOL定义的函数或则变量对全部内核代码公开,不用更改内核代码就可以在内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数或变量以符号的形式导入给其他模块使用。EXPORT_SYMBOL导入的符号,是把这种符号和对应的地址保存上去,在内核运行的过程中,可以找到这种符号对应的地址。

而模块在加载过程中,其本质就是能动态联接到内核,假如在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这种符号,这样就能找到对应的地址联接。

里面的两个宏均可把给定的符号导入到模块外,EXPORT_SYMBOL_GPL宏只能使符号对GPL许可的模块可用。符号必须在模块文件的全局部份导入,不能在函数中导入,这是由于上述这两个宏将被扩充成一个特殊用途的申明,而该变量必须是全局的。这个变量储存于模块的一个特殊的可执行部份(一个"ELF段"),在装载时,内核通过这个段来找寻模块导入的变量。

上述方式须要更改内核代码,编译内核。

方式二:使用kallsyms_lookup_name()查找函数或变量的虚拟地址

kallsyms抽取了内核用到的所有函数地址(全局的、静态的)和非栈数据变量地址,生成了一个数据块,作为只读数据链接进kernelimage,使用root权限可以在/proc/kallsyms中查看,没错,root权限下是可以直接看见内核函数的虚拟地址,如右图所示:

使用kallsyms_lookup_name()函数可以找到对应符号在内核中的虚拟地址,包含在头文件linux/kallsyms.h中,它接受一个字符串格式内核函数,返回那种内核函数的地址,倘若没找到指定的内核函数,它会返回0,要使用它必须启用CONFIG_KALLSYMS配置编译内核。

linux 调用内核函数_linux内核 sleep函数_linux 调用内核函数

在4.19版内核kernelkallsyms.c中kallsyms_lookup_name()函数定义如下:

/* Lookup the address for this symbol. Returns 0 if not found. */
unsigned long kallsyms_lookup_name(const char *name)
{
char namebuf[KSYM_NAME_LEN];
unsigned long i;
unsigned int off;
 
for (i = 0, off = 0; i < kallsyms_num_syms; i++) {
off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf));
 
if (strcmp(namebuf, name) == 0)
return kallsyms_sym_address(i);
}
return module_kallsyms_lookup_name(name);
}
EXPORT_SYMBOL_GPL(kallsyms_lookup_name);

可以看见该函数早已使用EXPORT_SYMBOL_GPL,可以直接在内核模块中使用。假如要使用内核中未被导入的函数,我们可以定义钩子函数,返回值和参数都要与我们要导入的函数原型一致。在本文复印超级块super_block结构中一些域值的内核模块代码中,我们把使用了内核未导入的变量和技巧单相关代码进行注释,取消方式二相关代码的注释,再执行make命令进行编译,可以看见,并没有编译报错,我们早已成功地使用了内核中未被导入的的变量sb_lock和super_blocks,编译结果如右图所示。

linux 调用内核函数_linux内核 sleep函数_linux 调用内核函数

加载模块后使用dmesg查看结果:

反之,内核中也有通过虚拟地址查找内核中的函数或变量的函数sprint_symbol,在内核中被定义如下:

int sprint_symbol(char *buffer, unsigned long address)
{
return __sprint_symbol(buffer, address, 0, 1);
}
EXPORT_SYMBOL_GPL(sprint_symbol);

可以看见,sprint_symbol函数是__sprint_symbol函数的封装,该函数早已使用EXPORT_SYMBOL_GPL导入,可以直接在内核模块中使用。该函数有两个参数,第一个参数是bufferlinux启动盘制作工具,字符型文本缓冲区,它拿来记录内核符号的信息,是一个输出型参数,第二个参数是address,无符号长整型的内核符号中的某一地址,是一个输入型参数。该函数中调用了__sprint_symbol内核函数,定义如下:

/* Look up a kernel symbol and return it in a text buffer. */
static int __sprint_symbol(char *buffer, unsigned long address,
int symbol_offset, int add_offset)
{
char *modname;
const char *name;
unsigned long offset, size;
int len;
 
address += symbol_offset;
name = kallsyms_lookup(address, &size, &offset, &modname, buffer);
if (!name)
return sprintf(buffer, "0x%lx", address - symbol_offset);
 
if (name != buffer)
strcpy(buffer, name);
len = strlen(buffer);
offset -= symbol_offset;
 
if (add_offset)
len += sprintf(buffer + len, "+%#lx/%#lx", offset, size);
 
if (modname)
len += sprintf(buffer + len, " [%s]", modname);
 
return len;
}

__sprint_symbol函数的功能是按照一个显存中的地址address查找一个内核符号,并将该符号的基本信息,如符号名name在内核符号表中的偏斜offset和大小size,所属的模块名(假如有的话)等信息联接成字符串形参给文本缓冲区buffer,所查找的内核符号可以是先前就存在于内核中的符号,也可以是坐落动态插入的模块中的符号,其中使用了kallsyms_lookup函数,定义如下:

static inline const char *kallsyms_lookup(unsigned long addr,
unsigned long *symbolsize,
unsigned long *offset,
char **modname, char *namebuf)
{
return NULL;
}

方式三:内核模块中直接使用内核函数的虚拟地址

首先介绍两种获取内核函数或变量虚拟地址的方式:

1、在/proc/kallsyms文件获得内核函数或变量的虚拟地址

此方式同样用到kallsyms,我们可以使用如下命令直接找到内核中sb_lock和super_block变量的虚拟地址,命令如下,图如下:

cat /proc/kallsyms | grep sb_lock 
cat /proc/kallsyms | grep super_blocks

linux内核 sleep函数_linux 调用内核函数_linux 调用内核函数

linux内核 sleep函数_linux 调用内核函数_linux 调用内核函数

2、在System.map文件获得内核函数或变量的虚拟地址

内核镜像的System.map文件储存了内核符号表的信息,可以通过此文件获取到具体内核函数或变量的虚拟地址,命令如下,图如下:

grep sb_lock /boot/System.map-4.18.0-15-generic
grep super_blocks /boot/System.map-4.18.0-15-generic

还可以通过给定一个虚拟地址来查看地址对应那个内核函数,命令如右图如下:

grep ffffffff82af35d4 /boot/System.map-4.18.0-15-generic
grep ffffffff8252efe0 /boot/System.map-4.18.0-15-generic

可以看见,不管用哪种方式,此时内核中sb_lock变量的虚拟地址为ffffffff922922ff353535dd4,super_blocks变量的虚拟地址为ffffffff9191dd22efeefeefe0。现今更改内核模块代码,我们把使用了内核未导入的变量和技巧二相关的代码进行注释,取消方式单相关代码的注释,再执行make命令进行编译。

结果显示,这些方式也可以使用内核中未被导入的变量或函数,并且这仅仅可以临时使用,并非长久之计,每次重启系统,这个变量的虚拟地址会发生变化,若要继续使用,还得再查看地址,再更改宏定义,至于地址发生变化的缘由红旗linux桌面版,这与内核符号表有关。/proc/kallsyms文件是在内核启动后生成的,是动态的符号表,坐落文件系统的/proc目录下,实现代码在kernel/kallsyms.c,使用前提是内核必须打开CONFIG_KALLSYMS编译选项。通常情况下,还是推荐使用第二种方式。

linux 调用内核函数_linux内核 sleep函数_linux 调用内核函数

原文链接:

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: linux系统 linux编译 代码管理 符号函数 虚拟变量
最后更新:2023年5月12日

Linux系统网

每日更新,欢迎收藏♥ 不积跬步无以至千里,加油,共勉。

点赞
< 上一篇
下一篇 >

Linux系统网

每日更新,欢迎收藏♥
不积跬步无以至千里,加油,共勉。

最新 热点 随机
最新 热点 随机
Linux系统编程的主要内容-系统调用API函数 如何更改linux最大文件数的限制? Linux内核中的系统调用的标准表示可移植操作系统接口 分区和目录中创建的文件数量有限制 网络攻防干货:显示文件的前10行知识点详解 :Access访问时间函数讲解文件:#include定义函数 嵌入式Linux系统组成部分部分64移植过程 Linux中的文件时间属性可以使用stat命令命令查看 linux 命令 操作系统版本 中标麒麟-内核Linux银河麒麟内核 linux内核移植的主要模块(或组件) Ubuntu下终端默认字体,看起来很舒畅,蛮漂亮的 Linux设备树编译:内核中的dtc工具 服务器配置进行域名解析及域名的解析与解析 安装完linuxredhat5.4,打开浏览器发现显示中文全部成了乱码 linux下mysql英文乱码解决方式(2008-08-22) ubuntu字体安装方法,超实用!!(附详细教程) Linux下USB转串口DB9(RS232)和转换线 Linux上部署Web应用程序时非常常见的做法虚拟主机 (Linux基础知识)Linux用户权限管理 关于Linux发行版本的详细信息和架构信息的说明书
Linux上部署Web应用程序时非常常见的做法虚拟主机Linux下USB转串口DB9(RS232)和转换线ubuntu字体安装方法,超实用!!(附详细教程)linux下mysql英文乱码解决方式(2008-08-22)安装完linuxredhat5.4,打开浏览器发现显示中文全部成了乱码服务器配置进行域名解析及域名的解析与解析Linux设备树编译:内核中的dtc工具Ubuntu下终端默认字体,看起来很舒畅,蛮漂亮的linux内核移植的主要模块(或组件)linux 命令 操作系统版本 中标麒麟-内核Linux银河麒麟内核Linux中的文件时间属性可以使用stat命令命令查看嵌入式Linux系统组成部分部分64移植过程:Access访问时间函数讲解文件:#include定义函数网络攻防干货:显示文件的前10行知识点详解分区和目录中创建的文件数量有限制Linux内核中的系统调用的标准表示可移植操作系统接口如何更改linux最大文件数的限制?Linux系统编程的主要内容-系统调用API函数SHELL编程中使用变量的实验原理和使用技巧和技巧Linux服务器出现CPU负载达到100%居高不下的情况
Linux远程调用下载文件的安装步骤及安装环境 精通LinuxTerminal使用的命令行浏览器下载排行榜分享 Qt是用写脚本将关联的库复制到一起,我尝试了下感觉容错率太低 Linux系统远程连接linux服务器教程教程 基于Xilinx系列FPGA的PCIE方案搭建的HDMI视频采集设计方案 传送门:用Python做一个安全攻防工具 玩蛇网Python之家图文教程和视频教程中的Python扫描器 clangd.nvim+clangd+高亮+语法静态解析+源码体验 linux查看登录用户 Linux命令太多不太容易记,不过熟练了倒还好 教你几种SSD的检测方法可以轻松识别二手SSD Linux下的访问控制列表(ACL)来控制用户权限 如何查看less文件的地址和统计文件? 如何在安卓上安装Linux模拟器? Linux桌面启动器(.Desktop文件)为你的Linux应用创建.desktop文件 重装Linux系统并重新格式化系统盘的解决方案有哪些 如果系统没有自动检测到你的打印机,这篇文章教你 PHP不支持多线程,有时候处理问题不是那么爽,学习啦 linux redhat 安装 轻松安装Linux RedHat,必知步骤和注意事项! 关于Linux发行版本的详细信息和架构信息的说明书 大白菜超级U盘启动盘制作工具!
标签聚合
应用 目录 操作 内核 文件 linux服务器 linux系统 命令 软件 文件目录
书籍
课程
技术群
技术干货大合集↓
  • 2023年12月 / 30篇
  • 2023年11月 / 89篇
  • 2023年10月 / 94篇
  • 2023年9月 / 90篇
  • 2023年8月 / 92篇
  • 2023年7月 / 96篇
  • 2023年6月 / 91篇
  • 2023年5月 / 93篇
  • 2023年4月 / 91篇
  • 2023年3月 / 69篇
友情链接:

Linux书籍 | Linux命令 | Linux系统 | RHCE红帽认证 | Linux软件 | Linux教程 | CentOS系统 | Linux内核 | Linux服务器 | Linux大神 | IT资源 | Linux系统

COPYRIGHT © 2024 LinuxZone.net ALL RIGHTS RESERVED.