/dev/kmem /proc/kallsyms

news2024/11/27 21:07:22

文章目录

    • 前言
    • 概述
    • 使用 /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.coloradmin.cn/o/577169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第十四届全国大学生数学竞赛决赛(非数类)游记+答案解析

2023/5/27 20:08:今天早上9:00~12:00考了数学竞赛国赛。广州是真的热啊!西安才17度,还下着小雨,到广州之后那个艳阳直接给我人干废了,去酒店的路上步行了20分钟真的要死了已经。 拿到卷子的我是崩溃的,用正…

计算机视觉:填充(padding)技术

本文重点 在前面的课程中,我们学习了使用3*3的过滤器去卷积一个5*5的图像,那么最终会得到一个3*3的输出。那是因为 33 过滤器在 55 矩阵中,只可能有 33 种可能的位置。 这背后的数学解释是,如果我们有一个nn的图像,用ff的过滤器做卷积,那么输出的维度就是(n−f+1)(n−f…

码出高效_第一章 | 有意思的二进制表示及运算

目录 0与1的世界1.如何理解32位机器能够同时处理处理32位电路信号?2.如何理解负数的加减法运算3.溢出在运算中如何理解4.计算机种常用的存储单位及转换5.位移运算规则6.有趣的 && 和 & 浮点数1.定点小数(为什么会出现浮点数表示?…

Linux 系统烧写

目录 MfgTool 工具简介MfgTool 工作原理简介烧写方式系统烧写原理 烧写NXP 官方系统烧写自制的系统系统烧写网络开机自启动设置 改造我们自己的烧写工具改造MfgTool烧写测试解决Linux 内核启动失败 前面我们已经移植好了uboot 和linux kernle,制作好了根文件系统。但…

Spring 事件相关知识ApplicationEvent

Spring 事件相关知识ApplicationEvent 事件工作流程相关类ApplicationListenerApplicationEvent 我们可以发布自己的事件ApplicationEventPublisher Spring框架中提供了多种事件类型,常用的几个事件类型如下: Spring 事件驱动模型是 Spring 框架中的一个…

uCOSii中的事件标志组

事件标志管理 (EVENT FLAGS MANAGEMENT) OSFlagAccept() 无等待查询”事件标志组的事件标志位”是否建立 OSFlagPend() 需要等待”事件标志组的事件标志位”建立 OSFlagCreate() 建立一个事件标志组 OSFlagDel() 删除一个事件标志组 OSFlagPost() 置位或清0事件标志组中的…

SpringBoot整合百度云人脸识别功能

SpringBoot整合百度云人脸识别功能 1.在百度官网创建应用 首先需要在百度智能云官网中创建应用,获取AppID,API Key,Secret Key 官网地址:https://console.bce.baidu.com/ 2.添加百度依赖 添加以下依赖即可。其中版本号可在mav…

【问卷分析】调节效应检验的操作①

文章目录 1.首先要明白自己的调节和自变量是什么类别的2.实操演练2.1 当调节变量是连续变量时2.1.1 将ml中心化2.1.2 使用分层回归探讨自变量和ml的交互对adh的影响2.1.3 结果解读 1.首先要明白自己的调节和自变量是什么类别的 2.实操演练 在本次演练中,我们以自变…

马斯克要用人工智能对抗人工智能

导读:马斯克对人工智能可能变得失控并“摧毁人类”的担忧促使他采取行动,发起了一个名为“TruthGPT”的项目。 本文字数:1400,阅读时长大约:9分钟 亿万富翁埃隆马斯克在谈到人工智能(AI)的危险时…

瑞合信LED字幕WiFi卡使用教程(8.0版)

请按照提示下载和安装软件,同时请允许所有权限,如下图; 也可以在各大主流应用商店(华为、小米、OPPO、vivo、App Store等)搜索“瑞合信”直接安装。 首次使用APP时会提示注册登录软件,你可以选择“使用微信…

深度学习进阶篇-预训练模型[4]:RoBERTa、SpanBERT、KBERT、ALBERT、ELECTRA算法原理模型结构应用场景区别等详解

【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍:【深度学习入门到进阶】必看系列,含激活函数、优化策略、损失函数、模型调优、归一化…

Full-Scanner是一个多功能扫描工具,支持被动/主动信息收集,漏洞扫描工具联动,可导入POC和EXP

github项目地址:https://github.com/Zhao-sai-sai/Full-Scanner gitee项目地址:https://gitee.com/wZass/Full-Scanner 工具简介 做挖漏洞渗透测试有的时候要去用这个工具那个工具去找感觉麻烦我自己就写了一个简单的整合工具,有互联网大佬不…

Presto之BroadCast Join的实现

一. 前言 在Presto中,Join的类型主要分成Partitioned Join和Broadcast Join,在Presto 之Hash Join的Partition_王飞活的博客-CSDN博客 中已经介绍了Presto的Partitioned Join的实现过程,本文主要介绍Broadcast Join的实现。 二. Presto中Broa…

ChatGPT免费使用的方法有哪些?

目录 一、ChatGpt是什么? 二、ChatGPT国内免费使用的方法: 第一点:电脑端 第二点:手机端 三、结语: 一、ChatGpt是什么? ChatGPt是美国OpenAI [1] 研发的聊天机器人程序 。更是人工智能技术驱动的自然语…

1. 从JDK源码级别彻底刨析JVM类加载机制

JVM性能调优 1. 类加载的运行全过程1.1 加载1.2 验证1.3 准备1.4 解析 本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。 课程内容: 1、从java.exe开始讲透Java类加载运行全过程 2、从JDK源码级别剖析JVM核…

【地铁上的面试题】--基础部分--数据结构与算法--数组和链表

零、章节简介 《数据结构与算法》是《地铁上的面试题》专栏的第一章,重点介绍了技术面试中不可或缺的数据结构和算法知识。数据结构是组织和存储数据的方式,而算法是解决问题的步骤和规则。 这一章的内容涵盖了常见的数据结构和算法,包括数组…

DevOps工作级开发者认证——软件发展

. 本文先从软件产业的现状、发展趋势及挑战,说明敏捷和DevOps必将成为业界主流的研发模式。接着介绍和阐述了敏捷和DevOps的关系,相关理念、价值和主要工程方法。最后重点介绍了某著名公司端到端DevOps的实施框架及其主要内容。 本文的目的&#…

javascript基础八:JavaScript原型,原型链 ? 有什么特点?

一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个…

15_Linux工程目录与顶层Makefile

目录 Linux 工程目录分析 顶层Makefile详解 make xxx_defconfig过程 Makefile.build脚本分析 make过程 built-in.o文件编译生成过程 make zImage过程 Linux 工程目录分析 将正点原子提供的Linux源码进行解压,解压完成以后的目录如图所示: 图就是正点原子提供的未编译的…

【数据分类】GRNN数据分类 广义回归神经网络数据分类【Matlab代码#30】

文章目录 【可更换其他算法,获取资源请见文章第6节:资源获取】1. 数据分类问题2. 广义回归神经网络(GRNN)3. 基于GRNN的数据分类步骤4. 部分代码展示5. 仿真结果展示6. 资源获取说明 【可更换其他算法,获取资源请见文章…