从猫蛇之战三看内核戏CPU

商务办公
小时候曾经目睹过猫与蛇战斗,面对昂首发威的毒蛇,小猫不慌不忙,挥舞前爪,沉着冷静,看准时机进攻,胆大心细。

小时候曾经目睹过猫与蛇战斗,面对昂首发威的毒蛇,小猫不慌不忙,挥舞前爪,沉着冷静,看准时机进攻,胆大心细。

在网上搜一下,可以看到很多猫蛇战斗的照片,看来猫蛇之战是很多人都喜欢看的“精彩节目”。

[[260574]]

(照片来自搜索引擎)

再来一张更清晰一些的。

 

[[260575]]

(照片来自搜索引擎)

之所以想到猫蛇之战,是因为今天在“格友会讲”群里一位同行问了一个很有深度的问题。

(前方内容只适合技术控,其他读者止步)

简单说问题是,调试器是如何访问不能访问的内存的。

看了这个问题,我立刻觉得这位同行是有功力的。因为普通的程序员是问不出这样的问题的。

要理解这个问题,必须有些底层的基础。

***个基础是要有保护模式的概念。很多同行都知道,今天的CPU是运行在所谓的保护模式中,软件访问的内存空间都是虚拟空间。而且这个虚拟空间中的内容是分三六九等的,是分平民区和富人区的,是分道路和深坑的。因为此,访问内存时是要小心的,有些地方可以访问,有些地方一访问就可能出大问题的,爆炸崩溃甚至“死亡”的。

大多数的应用程序崩溃和系统蓝屏都是因为访问了不该访问的地方。

第二个基础是对调试器有比较深的认识,知道在调试器里可以放心大胆地想访问哪里就访问哪里,不用那么小心。

举例来说,在普通程序里,如果访问空地址,那么不死也伤半条命(处理不好,就被系统杀了)。但是在调试器里,dd 0没有问题,调试器会给出一串串可爱的问号,代表不可访问,子虚乌有。

  1. 6: kd> dd 0 
  2. 00000000`00000000  ???????? ???????? ???????? ???????? 
  3. 00000000`00000010  ???????? ???????? ???????? ???????? 
  4. 00000000`00000020  ???????? ???????? ???????? ???????? 
  5. 00000000`00000030  ???????? ???????? ???????? ???????? 
  6. 00000000`00000040  ???????? ???????? ???????? ???????? 
  7. 00000000`00000050  ???????? ???????? ???????? ???????? 
  8. 00000000`00000060  ???????? ???????? ???????? ???????? 
  9. 00000000`00000070  ???????? ???????? ???????? ???????? 

那么问题来了,为啥普通程序一碰就爆炸,而调试器访问却安然无恙呢?

坦率说,***次在脑海中出现这个问题时,也令我困惑了一阵。直到后来发现了内核中的一个神秘机制。这个机制是跨操作系统的,Windows中有,Linux也有,而且都是相同的名字,叫Probe。

有点令人诧异的是,连函数名很类似,比如Windows(NT内核)中的两个函数为:

  1. 6: kd> x nt!probe* 
  2. fffff800`06581d70 nt!ProbeForWrite (void) 
  3. fffff800`06518ad0 nt!ProbeForRead (<no parameter info>) 

而Linux内核中的两个函数为:

  1. root@gedu-VirtualBox:/home/gedu/labs/linux-source-4.8.0# sudo cat /proc/kallsyms | grep "\bprobe_ke" 
  2. ffffffff811a5f00 W probe_kernel_read 
  3. ffffffff811a5fc0 W probe_kernel_write 

搜一下KDB/KGDB的源代码,可以看到很多地方调用了上面两个函数:

 

简单来说,内核里封装了两个特殊的函数,提供给包括调试器在内的一些特殊客户使用。

接下来的问题是,probe函数内部是如何做的呢?有关的源代码如下。

 

(更完整的请见https://elixir.bootlin.com/linux/v4.8/source/mm/maccess.c#L23 )

其中的关键是在__copy动作前后分别有:

  1. pagefault_disable(); 
  2. pagefault_enable(); 

也就是先禁止了pagefault,访问好之后再启用。这有点像是在耍蛇之前,先把它的毒牙包上。

继续深挖,在目前的Linux内核实现中,是维护一个计数器:pagefault_disabled。

 

(https://elixir.bootlin.com/linux/v5.0-rc8/source/include/linux/uaccess.h)

在处理页错误的do_page_fault函数中,会判断这个标志,如果发现禁止条件,则忽略这次访问错误。

讲到这里,问题说清了一半,要继续深追的话,还有一些细节,今天有点晚了,改日再叙。

责任编辑:武晓燕 来源: 格友
相关推荐

2021-06-26 07:04:24

Epoll服务器机制

2013-08-01 15:56:37

2021-07-07 23:38:05

内核IOLinux

2021-06-18 06:02:24

内核文件传递

2017-01-15 23:46:37

2022-03-03 08:01:41

阻塞与非阻塞同步与异步Netty

2021-04-08 09:32:17

鸿蒙HarmonyOS应用

2016-09-20 15:21:35

LinuxInnoDBMysql

2013-02-22 09:32:13

2020-06-08 09:11:47

Linux 内核Linux内核

2017-06-30 09:00:40

共享单车物联网

2011-09-11 18:21:11

笔记本常见问题

2019-09-27 10:25:39

5G浪潮云计算

2020-08-04 16:07:16

华为苹果三星

2019-10-31 16:31:53

CPUTDP电脑

2009-04-30 10:31:07

2012-04-23 17:36:40

ES8000三星智能电视

2012-12-26 09:14:11

SDN信息数据

2022-02-17 08:16:23

MMU内存管理
点赞
收藏

51CTO技术栈公众号