Skip to content

Latest commit

 

History

History
60 lines (41 loc) · 13 KB

linux内核调试技巧.md

File metadata and controls

60 lines (41 loc) · 13 KB

关于调试,除开内存之外,离不开的一点就是各类寄存器。

寄存器

x86_64下的16种通用寄存器

寄存器名寄存器简介主要功能63-031-015-08-0
rax累加器,是算术运算的主要寄存器存储返回值raxeaxaxal
rbx基址寄存器,被调用者保存存放存储区的起始地址rbxebxbxbl
rcx计数寄存器循环操作和字串处理的计数控制;函数调用时的第4个参数rcxecxcxcl
rdx
I/O指针
I/O操作时提供外部设备接口的端口地址;函数调用时的第3个参数rdxedxdxdl
rsi(source index)源变址寄存器,与rds段寄存器联用,可以访问数据段中的任一个存储单元函数调用时的第2个参数rsiesisisil
rdi
(destination index)目的变址寄存器,与res段寄存器联用,可以访问附加段中的任一个存储单元
函数调用时的第1个参数rdiedididil
rbp(base pointer)基址指针寄存器,用于提供堆栈内某个单元的偏移地址,与rss段寄存器联用,可以访问堆栈中的任一个存储单元,被调用者保存
rbpebpbpbpl
rsp(stack pointer)栈顶指针寄存器,提供堆栈栈顶单元的偏移地址,与rss段寄存器联用,以控制数据进栈和出栈
rspespspspl
r8
函数调用时的第5个参数r8r8dr8wr8b
r9
函数调用时的第6个参数r9r9dr9wr9b
r10调用者保存
r10r10dr10wr10b
r11调用者保存
r11r11dr11wr11b
r12被调用者保存
r12r12dr12wr12b
r13被调用者保存
r13r13dr13wr13b
r14被调用者保存
r14r14dr14wr14b
r15被调用者保存
r15r15dr15wr15b

段寄存器:

寄存器功能
CS(code segment)
代码段地址寄存器,存放代码段的起始地址
DS(data segment)数据段地址寄存器,存放数据段的起始地址
SS(stack segment)堆栈段地址寄存器,存放堆栈段的起始地址
ES(extra segment)
附加段地址寄存器,存放附加段的起始地址

控制寄存器

寄存器
功能
Cr0
存放控制处理器操作模式和状态的系统控制标志
Cr1
保留
Cr2
存放导致页错误的线性地址
Cr3
存放页目录表基地址
Cr4
处理器扩展功能标志位
Cr8
当前lrql权限等级

CONFIG_STRICT_DEVMEM

现在内核基本都开了这个编译选项,导致调式的时候根本无法修改内存

wr: cannot write to /proc/kcore

然后通过Systemtap修改是可以:

stap -g -e 'probe kernel.function("devmem_is_allowed").return { $return =1 }'

我在vmware中修改了devmem_is_allowed后再去打开crash就会直接导致vmware卡住,不清楚是什么原因导致的,起初以为是因为新编译的内核缺少一个vmware需要的模块导致的,即报错dracut[38489]: Failed to install module libnvdimmvmxnet3,这其实是两个模块libnvdimmvmxnet3,可以通过修改/etc/dracut.conf.d/nvdimm-security.conf,把add_drivers+="libnvdimm"改成add_drivers+="libnvdimm "即可,后来我发现就算修复了这个问题还是会卡住。

kdump

没办法只能去看一下内核崩溃的原因,就得保存内核崩溃时的状态,这就用到了kdump,这是一个基于kexec的内核崩溃捕获机制,可以将kernel崩溃前的内存镜像保存下来。原理很简单,大概就是在内核崩溃时,kdump启动kexec启动到第二个内核,也称为捕获内核,这个内核会与相应的ramdisk组件一个微型环境搜集生产内核(第一内核)的内存信息并转存。

kdump的实现也是需要条件的,捕获内核的启动绕过了BIOS生产内核在崩溃时候通过kexec启动,这就需要捕获内核一开始就已经加载完成,这就是转存机制实现的关键两点:

  1. kexec_load系统调用会在生产内核启动时候加载捕获内核到指定地址
  2. 用户态下通过kexec-tools捕获内核地址传递给生产内核,得以在崩溃时运行。

排查系统时候发现,kdump并没有被打开,且去尝试systemctl start kdump时候还会直接报错,报错信息简单来说就是内存不足,内核启动参数中有一个传入参数是crashkernel,这个参数用于配置捕获内核的大小和位置,一般会看到内核参数设置都是crashkernel=auto,这会根据内存自动reserve内存出来使用,然而如果内存在8G以下的话,其实并不会保留内存出来,因此对于加内存来说,我手动修改了/boot/grub/grub.cfg的内核参数为crashkernel=256M,成功开启kdump,从而获取到了崩溃的内核镜像。

不过我自己物理机排查都发现保留内存为0,那有点怀疑是可能是哪个版本的内核直接就自动关闭了这部分的保留,除非手动设置。cat /sys/kernel/kexec_crash_size这样查看保留了多少内存。

CONFIG_HARDENED_USERCOPY

调试崩溃内核时发现错误出现在了mm/usercopy.c:72这个位置,当直接翻开源码观察后,发现这儿本来就会去主动发起一个BUG(),然后产生中断,那就说明是一个内核的feature,结果发现在内核4.8以后内核主线中引入了一个特性是CONFIG_HARDENED_USERCOPY,简单来说就是针对copy_form_user()/copy_to_user()这两个函数做安全加固,从而避免内核内存泄露内核内存覆盖的问题出现,至于加固细节大概就是指针检查指向检查拷贝大小不允许越界(slab分配对象)涉及内核栈的拷贝的内容需要在当前进程内核栈中,为了方便就直接修改了.config然后重编译了内核。

/dev/mem

上面说过写入的操作最大的障碍就是devmem_is_allowed,这个函数影响是是crash针对/dev/mem的访问,/dev/mem可以看做是一个内存入口,这个入口提供了一个针对全物理地址访问的能力。简单来说只要能确定地址空间分布,就可以指定任意有效地址映射到用户空间中,因此kernel自然要对其加以限制,而最核心的检查就是devmem_is_allowed,简单来说如果关了这个选项用户可以获得完全读写能力。

参考文档