内存越界问题如何调试

内核里面内存越界问题是一类很常见的错误类型,当然有些也属于异常难调试的问题,这种错误行为会触发内核运行异常,假如越界访问修改了错误的数据区,情况较轻的可能导致应用获取的数据不对;情况严重的,比如修改了一个错误的地址,那么内核访问该地址时就会触发oops和panic。

下面来聊聊内存越界的类型,内存越界发生在哪些情况下呢?

内存问题分类

内存访问异常可以分为如下几种类型:

数组越界

数组的长度是预先定义的,如果在代码中访问数组超过了数组的最大限制,那么就会修改到无法预期的位置。

内核栈溢出

在内核函数创建的临时变量或者数组,那么会从内核栈中分配,当一个进程的内核栈空间超过最大限制时,那么就会出现一些无法预知的问题。

slab/slub内存问题

作为内核开发者,最常用的内存应该就是slab/slub内存了,开发者一般使用kmalloc来申请内存,kmalloc内部的实现就是基于slab/slub的。比如:

$ sudo cat /proc/slabinfo | grep kmalloc
kmalloc-8192        4333   4340   8192    4    8 : tunables    0    0    0 : slabdata   1085   1085      0
kmalloc-4096         418    488   4096    8    8 : tunables    0    0    0 : slabdata     61     61      0
kmalloc-2048        1197   1248   2048   16    8 : tunables    0    0    0 : slabdata     78     78      0
kmalloc-1024        7282   9536   1024   32    8 : tunables    0    0    0 : slabdata    298    298      0
kmalloc-512         5191   5632    512   32    4 : tunables    0    0    0 : slabdata    176    176      0
kmalloc-256        25061  31072    256   32    2 : tunables    0    0    0 : slabdata    971    971      0
kmalloc-192        12019  16632    192   21    1 : tunables    0    0    0 : slabdata    792    792      0
kmalloc-128        18705  19072    128   32    1 : tunables    0    0    0 : slabdata    596    596      0
kmalloc-96          6918  13650     96   42    1 : tunables    0    0    0 : slabdata    325    325      0
kmalloc-64        136718 170496     64   64    1 : tunables    0    0    0 : slabdata   2664   2664      0
kmalloc-32         34639  37888     32  128    1 : tunables    0    0    0 : slabdata    296    296      0
kmalloc-16          9472   9472     16  256    1 : tunables    0    0    0 : slabdata     37     37      0
kmalloc-8           8192   8192      8  512    1 : tunables    0    0    0 : slabdata     16     16      0

该命令可以查看系统中存在多种size类型的slab对象,内核开发者使用kmalloc申请内存后,如果出现使用上的不当就会发生内存oob越界访问或者use-after-free等问题。一般分为几种情况:访问释放后的内存地址、多次释放同一块内存、越界访问kmalloc内存。

如何进行问题定位?

对于数组越界类型是比较难定位的,因为数组越界访问后,会导致其他的数据被异常的修改掉,并且当内核爆出问题时,一般都已经不是第一现场了,那么对于这类问题要怎么定位呢

如何定位内存问题

上面是按照我的个人理解对不同的越界访问错误进行了大致的分类,可能有不全的地方,也欢迎指正,下面我就来聊一下如何debug内存问题。下面这一部分将以实例的方式做一下几种情况的梳理和记录,可能也不会cover所有的场景。

全局变量被异常修改

当我们在驱动或者内核模块中定义了一个全局变量,当我们在使用时发现该变量的值被异常的修改了,那么如何找到是谁修改了我们的变量值呢?一般考虑是由于其他代码路径发生了越界访问,异常修改了我们定义的变量内容。首先第一个考虑的就是数组越界,可以通过查看System.map找到我们定义的全局变量所在的address地址,并且在该address附近查看是否存在数组,如果存在数组,那么该数组可能就是重点怀疑的对象,可以搜索一下内核代码,找到对应的symbol进行查验。

内核访问地址异常触发oops

1.通过查看oops找到是哪个函数访问了异常地址;

2.查看内核代码找到传入的地址指针是否是一个具体的symbol,比如一个特定的数组或者结构体成员;

3.可以在该数组或者结构体成员的前后分别加上magic number,复现故障,使用crash工具查看对应的magic number是否被修改,判断是向前踩内存还是向后踩内存;

4.查看是否可能与memcpy有关,使用kprobe在memcpy前加上我们的hook,如果拷贝的目标地址在symbol所处的区间,那么就打印出来dump_stack;

5.重新复现问题后,如果恰好打印出kprobe中的dump_stack,那么基本可以定位是哪里引起的问题了。

slab/slub内存问题定位

对于slab/slub内存相关的问题,最简单的,我们可以使用内核的SLAB DEBUG功能和KASAN功能。不过这种方式需要重新编译内核,如果应用场景允许使能这些内核特性编译内核,那么这种方式是首选。假如很不幸,我们处在业务机器上,这种影响性能的特性是不开的,只能通过kdump和crash工具分析故障现场来进行debug,这个就属于疑难杂症了,属于比较难以debug的情况了,这里笔者也不能给出具体建议,自求多福吧,未来掌握的更多技巧后再来更新这一节吧。

栈溢出问题

gcc编译器本身带有堆栈溢出保护功能,通过配置如下内核选项:

CONFIG_HAVE_STACKPROTECTOR=y
CONFIG_CC_HAS_STACKPROTECTOR_NONE=y
CONFIG_STACKPROTECTOR=y
CONFIG_STACKPROTECTOR_STRONG=y

可以对内核栈溢出进行检测,除此之外,前面介绍的KASAN功能也具有这个功能,不过需要gcc比较高的版本才能使用该功能。


参考文章:

https://blog.csdn.net/hjkfcz/article/details/84500026
https://blog.csdn.net/cf125313/article/details/95611596
https://blog.csdn.net/chenyu105/article/details/9749549

你可能感兴趣的:(内核调试)