/dev/kmem /proc/kallsyms

2023/11/30 8:48:38

文章目录

    • 前言
    • 概述
    • 使用 /dev/kmem
    • 使用 /proc/kallsyms
    • 验证
    • 进阶

前言

上篇文章我们介绍了 /dev/mem,今天再来介绍下它的好兄弟 /dev/kmem

crw-r----- 1 root kmem 1, 1 May 26 06:10 /dev/mem
crw-r----- 1 root kmem 1, 2 May 26 06:10 /dev/kmem

对比一下:

  • /dev/mem:映射系统所有的物理内存
  • /dev/kmem:映射系统所有的内核态虚拟内存

概述

简单来说,通过 /dev/mem 我们可以查询一个物理地址上的数据是多少,
通过 /dev/kmem 我们可以查询一个内核虚拟地址上的数据是多少。
要知道,CPU 或着说内核看到的地址都是虚拟地址,我们在内核中使用 printk 打印出一个全局变量的地址,后面想要继续追踪该地址上值的变化,这时候再用 /dev/mem 就不行了,因为打印出的地址是一个虚拟地址,而通过 /dev/mem 访问的是物理地址。这种情况下应该使用 /dev/kmem,通过它可以访问内核虚拟内存

使用 /dev/kmem

由于/dev/kmem 暴露的权限过大,存在安全隐患,所以内核一般默认禁用该设备,仅仅保留 /dev/mem。我们想要使用 /dev/kmem,可以打开 CONFIG_DEVKMEM=y 这个编译选项。

使用 /proc/kallsyms

有了 /dev/kmem 设备,我们就可以访问内核虚拟内存了,但是我们访问哪个地址呢?如何知道一个全局变量的虚拟内存地址呢?答案是可以使用 /proc/kallsyms 查看内核符号信息。

# cat /proc/kallsyms 
c0008000 T stext
c0008000 T _text
c000808c t __create_page_tables
c0008138 t __turn_mmu_on_loc
c0008144 t __fixup_smp
c00081ac t __fixup_smp_on_up
c00081d0 t __fixup_pv_table
c0008224 t __vet_atags
c0008280 T _stext
c0008280 T __turn_mmu_on
c0008280 T __idmap_text_start
c00082a0 T cpu_resume_mmu
c00082a0 t __turn_mmu_on_end
c00082c4 T cpu_ca15_reset
c00082c4 T cpu_ca8_reset
c00082c4 T cpu_ca9mp_reset
c00082c4 T cpu_v7_bpiall_reset
c00082c4 T cpu_v7_reset
c00082e0 T __idmap_text_end
c0009000 T asm_do_IRQ
c0009000 T __exception_text_start
c0009000 T __hyp_idmap_text_end
c0009000 T __hyp_idmap_text_start
c0009014 T do_undefinstr
c0009268 T handle_fiq_as_nmi
c00092ec T do_IPI
c00092f0 T do_DataAbort
c00093a4 T do_PrefetchAbort
c000943c T gic_handle_irq
c0009508 T __do_softirq
。。。

第一列为符号地址,第二列为类型,第三列为符号名

第二列的类型:
大写字母表示该符号没有被 static 修饰,可以被整个内核代码使用;
小写字母表示该符号被 static 修饰了,只能在当前文件使用。

b 符号在未初始化数据区(BSS)
c 普通符号,是未初始化区域
d 符号在初始化数据区
g 符号针对小object,在初始化数据区
i 非直接引用其他符号的符号
n 调试符号
r 符号在只读数据区
s 符号针对小object,在未初始化数据区
t 符号在代码段
u 符号未定义

我们找个全局变量练练手

# cat /proc/kallsyms | grep " D "
#

却发现没有找到任何全局变量,这是怎么回事?
原来,内核只开启了 CONFIG_KALLSYMS=y

CONFIG_KALLSYMS=y # 符号表中包含所有的函数

我们还需要开启下面编译选项

CONFIG_KALLSYMS_ALL=y # 符号表中包括所有的变量(包括没有用 EXPORT_SYMBOL 导出的变量)

重新编译后,便可列出全局变量符号了

# cat /proc/kallsyms | grep " D "
c0884000 D __per_cpu_load
c0884000 D __per_cpu_start
c0884048 D cpu_data
c0884208 D harden_branch_predictor_fn
c0884210 D process_counts
c0884260 D ksoftirqd
c0884280 D kernel_cpustat
c08842d0 D kstat
c08842fc D select_idle_mask
c0884300 D load_balance_mask
c088432c D sd_llc
c0884330 D sd_llc_size
c0884334 D sd_llc_id
c0884338 D sd_llc_shared
c088433c D sd_numa
c0884340 D sd_asym
c0888480 D srcu_online
c08895c0 D hrtimer_bases
c0889818 D tick_cpu_device
c0889944 D pcpu_drain
c0889978 D dirty_throttle_leaks
c0889b2c D __kmap_atomic_idx
c088a008 D cpuidle_devices
c088a010 D cpuidle_dev
c088a4e8 D flush_works

比方说,我们想查看变量 flush_works 的值,其虚拟地址为 0xc088a4e8 = 3230180584,那么,使用下面命令就可以从 /dev/kmem 中读取该变量的值了

# dd if=/dev/kmem bs=1 count=4 skip=3230180584 | hexdump
4+0 records in
4+0 records out
0000000 60af 7ce4                              
0000004

验证

为了验证读取到值的正确性,我们在内核(usb.c)中添加一个全局变量进行测试,添加代码如下

int lyj_ccc = 0x1234;

/*
 * Init
 */
static int __init usb_init(void)
{
	int retval;
	int lyj_ddd = 0x5678;

printk("&lyj_ccc = %p\n", &lyj_ccc);
printk("lyj_ccc = 0x%x\n", lyj_ccc);
printk("&lyj_ddd = %p\n", &lyj_ddd);
printk("lyj_ddd = 0x%x\n", lyj_ddd);

	if (usb_disabled()) {
		pr_info("%s: USB support disabled\n", usbcore_name);
		return 0;
	}

重新编译内核,内核启动阶段打印如下

SCSI subsystem initialized
&lyj_ccc = c08b65a8
lyj_ccc = 0x1234
&lyj_ddd = dd03fef4
lyj_ddd = 0x5678
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb

系统启动完毕后,我们使用 /dev/kmem 进行读取,先使用 /proc/kallsyms 查看符号 lyj_ccc 对应的虚拟地址(其实,在内核启动阶段也打印了该变量的地址,只不过正常情况下我们不会手动添加代码去打印,使用 kallsyms 查看才是更通用的手段

# cat /proc/kallsyms | grep lyj
c08b65a8 D lyj_ccc

看到在符号表中只有 lyj_ccc,而没有 lyj_ddd,因为 lyj_ddd 是局部变量,不是符号(我的理解:符号是全局变量和函数名称)。
拿到变量的虚拟地址 0xc08b65a8 后,我们使用如下命令查看变量的值

# dd if=/dev/mem bs=1 count=4 skip=3230361000 | hexdump
dd: /dev/mem: Bad address

# dd if=/dev/kmem bs=1 count=4 skip=3230361000 | hexdump
4+0 records in
4+0 records out
0000000 1234 0000                              
0000004

可以看到,使用 /dev/mem 是无法查看的,因为它查看的是物理地址。
使用 /dev/kmem 可以读取,且读到的值和代码一致,说明读取成功。🎈🎈🎈

进阶

我在使用另一款设备研究该问题时,发现使用 /dev/mem 竟然也可以查看内核虚拟内存,
啊表情啊

不过最终发现是闹了乌龙,原因是这款设备在内核软件设计时,将内核虚拟地址映射到了地址相同的物理地址上

int lyj_aaa = 0x3344;

/*
 * Init
 */
static int __init usb_init(void)
{
	int retval;
	int lyj_bbb = 0x5566;

	if (usb_disabled()) {
		pr_info("%s: USB support disabled\n", usbcore_name);
		return 0;
	}
	usb_init_pool_max();

printk("&lyj_aaa = %p\n", &lyj_aaa);
printk("lyj_aaa = %d\n", lyj_aaa);
printk("lyj_bbb = %d\n", lyj_bbb);
	retval = usb_debugfs_init();
	if (retval)
		goto out;
&lyj_aaa = 80a67674
lyj_aaa = 13124
lyj_bbb = 21862
root@ATK-IMX6U:~# cat /proc/kallsyms | grep lyj
80a67674 D lyj_aaa
root@ATK-IMX6U:~# dd if=/dev/mem bs=1 count=4 skip=2158392948 | hexdump
4+0 records in
4+0 records out
4 bytes copied, 0.000401333 s, 10.0 kB/s
0000000 3344 0000
0000004
root@ATK-IMX6U:~#
root@ATK-IMX6U:~#
root@ATK-IMX6U:~# dd if=/dev/kmem bs=1 count=4 skip=2158392948 | hexdump
4+0 records in
4+0 records out
4 bytes copied, 0.000410666 s, 9.7 kB/s
0000000 3344 0000
0000004
root@ATK-IMX6U:~# cat /proc/iomem
。。。
  80008000-809d0ca3 : Kernel code
  80a38000-80b02353 : Kernel data

可以看到 0x80a67674 正好落在 80a38000-80b02353 范围内,说明内核数据段的起始地址被映射到了相同的物理内存地址
其实,内核代码段的起始地址也被映射到了相同的物理内存地址,证明如下

root@ATK-IMX6U:~# cat /proc/kallsyms | grep 80008000
80008000 T stext
80008000 T _text
root@ATK-IMX6U:~# cat /proc/kallsyms | grep 80a38000
80a38000 D init_thread_union
80a38000 D _data
80a38000 D _sdata
80a38000 D __data_loc
80a38000 D __init_end

分析过内核启动流程的小伙伴一眼就看出 stext 就是内核的入口函数,可以复习之前的两篇文章《Kernel 启动流程梳理》、《bootz 启动 kernel》。
而 _data 就是代码段的起始地址,这点可以查看内核的链接脚本 arch/arm/kernel/vmlinux.lds

作为对比,之前那款设备,内核的虚拟地址就和物理地址不同了,这才是普遍行为

# cat /proc/iomem 
  40008000-4083efff : Kernel code
  4088c000-40a0a97f : Kernel data
。。。
# cat /proc/kallsyms | grep lyj
c08b65a8 D lyj_ccc

http://www.jnnr.cn/a/499406.html

相关文章

Power BI DAX函数

1、聚合函数——由表达式定义的列或表中所有行的(标量)值,例如计数、求和、平均值、最小值或最大值。 函数说明APPROXIMATEDISTINCTCOUNT在列中返回唯一值的估计计数AVERAGE返回列中所有数字的平均值(算术平均值)AVER…

RK3588平台开发系列讲解(驱动基础篇)设备树常用 of 函数

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、查找节点的 of 函数二、获取属性值的 of 函数三、实验示例3.1、查找的节点代码3.2、获取属性内容代码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 设备树描述了设备的详细信息,这些信息包括数字类型的…

LC-1130. 叶值的最小代价生成树(贪心、区间DP、单调栈)

1130. 叶值的最小代价生成树 难度中等272 给你一个正整数数组 arr,考虑所有满足以下条件的二叉树: 每个节点都有 0 个或是 2 个子节点。数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。每个非叶节点的值等于其左子树和右子树中叶节点的最大…

用Python求最大公约数和最小公倍数(51)

小朋友们好,大朋友们好! 我是猫妹,一名爱上Python编程的小学生。 和猫妹学Python,一起趣味学编程。 今日主题 什么是最大公约数? 如何用Python求最大公约数? 什么是最小公倍数? 如何用Pyt…

Java学习路线(21)——网络通信

一、网络通信三件套 1、IP地址: 设备在网络中的地址,唯一标识 概念: Internet Protocal,简称为IP,全称“互联网协议地址”。 常见分类: IPv4(32位) 和 IPv6(128位&#…

【Apache 网页优化】

文章目录 一、Apahce 网页优化1、网页压缩2、网页缓存 二、Apachen的安全优化1、隐藏版本信息2、Apache 防盗链 一、Apahce 网页优化 1、网页压缩 1.检查是否安装 mod_deflate 模块 apachectl -t -D DUMP_MODULES | grep "deflate"2.如果没有安装mod_deflate 模块…

web自动化测试流程的总结及关注点

目录 一、立项后测试需要拿到的文档 二、需求评审 三、用例编写(同时根据开发计划编写测试计划) 四、用例评审 五、测试执行 六、测试报告及操作手册 项目的测试流程大只包含的几个阶段:立项、需求评审、用例评审、测试执行、测试报告文…

深度:解密数据之力,奏响制造业智能升级的狂想曲!

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在21世纪的今天,我们正在经历着一个伟大的变革,一个由数字技术引领的产业革命——智能制造。在这场变革中,大数据、人工智能、5G专网、工业物联网和智能机器人等尖端技术,正如…

【无标题】win11打开VMware虚拟机蓝屏解决

win11打开VMware虚拟机蓝屏解决 win11打开虚拟机蓝屏!!!解决方案:win11支持16.2以上版本,其他版本不兼容,可用文末的卸载工具卸载之前已安装版本(深度卸载),然后下载16.2…

Docker笔记(二)

一、Docker 复杂安装1.1、mysql 主从复制1.2、Redis1.2.1、Redis存储大量数据解决方案1.2.2、Redis 集群搭建1.2.3、数据读写存储1.2.4、容错切换转移1.2.5、主从扩容案例1.2.6、主从缩容案例 二、Dockerfile2.1、是什么?2.2、保留字指令2.3、案例 三、虚悬镜像3.1、…

Node.js详解(一):基础知识

文章目录 一、Node.js介绍二、Node.js的优势三、Node.js的特点1、V8虚拟机2、事件驱动3、异步、非堵塞I/O 四、NodeJS带来的对系统瓶颈的解决方案1. 并发连接2. I/O阻塞 五、NodeJS的优缺点1、优点:2、缺点: 六、适合NodeJS的场景1、RESTful API2、统一W…

「VS」Visual Studio 字符集

✨博客主页:何曾参静谧的博客 📌文章专栏:「VS」Visual Studio 当我们在使用 Visual Studio 编写程序时,经常会遇到字符集的问题。在 Visual Studio 中,字符集选项有两个选项:Unicode 字符集和多字节字符集…

遗传算法(Genetic Algorithm)

本文为阅读《遗传算法原理及应用》的笔记和心得 ISBN:7-118-02062-1 遗传算法简介 遗传算法是模拟生物在自然环境中的遗传和进化过程中而形成的一种自适应全局优化概率搜索算法 总的来说,求最优解解或近似最优解的方法主要有三种:枚举法、启…

【编译、链接、装载二】/lib/ld64.so.1: bad ELF interpreter: 没有那个文件或目录

【编译和链接二】bash: ./test.out: /lib/ld64.so.1: bad ELF interpreter: 没有那个文件或目录 一、问题起因二、ldd查看三、解决方案一:使用gcc链接四、查找其他解决方案五、解决方案二:软链接 bash: ./test.out: /lib/ld64.so.1: bad ELF interpreter…

(超详细)关于Nacos的共享配置( shared-configs)和拓展配置(extension-config)

前言 用SpringBoot的铁子们,相信大多数人都使用过Nacos作为注册中心和配置文件管理中心,确实很方便。但是很多铁子们依葫芦画瓢,都知道怎么用,但是对于其中的细节可能没有系统地整理过。今天就讲讲关于Nacos的共享配置和扩展配置…

easyX实践上手操作小项目

easyX实践上手操作小项目 效果展示主菜单的装饰玩法介绍界面开始游戏界面制作团队界面排行榜界面注:main()函数拓展数据库小结 这里我们学习过easyX的基础知识后,看看是否能实践操作一下,制作一个属于自己的游戏界面呢? 基础知识…

chatgpt赋能python:Python创建画布的教程

Python 创建画布的教程 Python是一个功能强大的编程语言,其中一个重要的应用是数据可视化。在数据科学、机器学习、图像处理和软件开发中,Python的可视化功能非常实用。本教程将介绍Python创建画布的步骤,以及如何使用Matplotlib创建简单的图…

基于SpringBoot+Vue的小区物业管理系统设计与实现

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

YOLOV5 + PYQT5双目测距(一)

YOLOV5 PYQT5双目测距 1. 测距源码2. 测距原理3. PYQT环境配置4. 实验结果4.1 界面1(简洁版)4.2 界面2(改进版) 1. 测距源码 详见文章 YOLOV5 双目测距(python) 2. 测距原理 如果想了解双目测距原理&a…

手动计算校正年龄、性别后的标准化死亡率 (SMR)

分析队列人群有无死亡人数超额,通常应用标准人群死亡率来校正,即刻观察到中的实际死亡数(D)与定一个标准的死亡人数(E),D与E之比称为死亡比(standarized Mortality ratio&#xff0c…
最新文章