#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/kdev_t.h>
#include <linux/kallsyms.h> //方法二
/*
*方法二,使用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:%s\n", sb->s_type->name);
//打印文件系统名
list_for_each(linode, &sb->s_inodes) {
pinode=list_entry(linode, struct inode, i_sb_list);
count++;
printk("%lu\t", pinode->i_ino); //打印索引节点号
}
}
//spin_unlock(sb_lock_address); //方法二
//spin_unlock(SB_LOCK_ADDRESS); //方法三
spin_unlock(&sb_lock); //解锁,此处使用了未导出的变量
printk("The number of inodes:%llu\n", 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");
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#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))); })
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
编译报错,‘sb_lock’ undeclared (first use in this function);
,‘super_blocks’ undeclared (first use in this function);
,即sb_lock和super_blocks变量没有定义,实际上在内核中已经定义了这两个变量,包含在头文件fs.h和spinlock.h中,在内核源码中如下:
fs\super.c
中,定义了 super_blocks 变量来指向 super_block 结构中的 s_list 双链表的链表头,其中 s_list 是用来链接系统中已安装文件系统超级块的双向循环链表,也定义了 sb_lock 锁变量对超级块的相关操作进行加锁,如下图:编译报错的原因就是在内核中并没有导出sb_lock和super_blocks变量,那么问题来了:
1、我们为什么不导出更多的变量或者函数来供我们使用呢?
2、我们可以使用内核中没有导出的函数或变量吗?如果可以使用,如何使用?
include\linux\export.h
中,定义了EXPORT_SYMBOL宏,如下图:如果我们要使用内核中的变量或函数,可以使用上图中的宏,在函数或变量定义后使用如下宏,然后编译内核:
EXPORT_SYMBOL(sb_lock);
或者
EXPORT_SYMBOL_GPL(sb_lock);
使用kallsyms_lookup_name()函数可以找到对应符号在内核中的虚拟地址,包含在头文件linux/kallsyms.h中,它接受一个字符串格式内核函数,返回那个内核函数的地址,如果没找到指定的内核函数,它会返回0,要使用它必须启用CONFIG_KALLSYMS配置编译内核。
在4.19版内核kernel\kallsyms.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);
加载模块后使用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导出,可以直接在内核模块中使用。该函数有两个参数,第一个参数是buffer
,字符型文本缓冲区, 它用来记录内核符号的信息, 是一个输出型参数,第二个参数是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;
}
cat /proc/kallsyms | grep sb_lock
cat /proc/kallsyms | grep super_blocks
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
ffffffff922f35d4
,super_blocks 变量的虚拟地址为ffffffff91d2efe0
。现在修改内核模块代码,我们把使用了内核未导出的变量和方法二相关的代码进行注释,取消方法三相关代码的注释,再执行make命令进行编译。