开启内核地址随机化KASLR后, qemu 调试 kernel 不能设置断点

Posted CHENG Jian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开启内核地址随机化KASLR后, qemu 调试 kernel 不能设置断点相关的知识,希望对你有一定的参考价值。

#1 问题: gdb 断点异常

这几天更新了 qemu, 然后在进行 gdb 调试的时候, 发现断点断不住了.

之前都是正常的, 从来没有出现过这种情况啊. 继续分析下看看是哪里出现的异常.

#2 原因分析

难道是 gdb 或者 QEMU 出现 BUG 了, 我们先看下断点的位置是否正确.

  • vmlinux 中符号的地址(gdb插入断点的位置)

gdb 是直接读取 vmlinux 中的符号的加载地址去添加断点的, 那么 0xffffffff81aa1800 应该就是 vmlinux 中 schedule 的函数地址

#objdump -d ./vmlinux| grep "<schedule>:"
ffffffff81aa1800 <schedule>:

可以看到没有问题, 但是为什么没有断到呢, 难道当前内核镜像中的地址不是这个么?

  • 当前内核镜像的符号地址
# cat /proc/kallsyms | grep -E " schedule$"
ffffffffb0ca1800 T schedule

  • 原因分析

schedule 在 vmlinux 镜像中的符号地址(ffffffff81aa1800)与 qemu 启动的内核中虚拟地址(ffffffffb0ca1800)不一样, 貌似发现问题所在了. 所以 gdb 根据 vmlinux 中的地址插入断点, 其实插入的位置并不是我们想要的, 这也就解释了为什么断点断不到.

可以看到两个地址刚好差了一个偏移 0x2f200000L.

这让我们想到了什么? 地址随机化?

内核启用 kaslr 这项特性之后, 内核启动时会随机化内核的各个 section 的虚拟地址(VA), 导致 gdb 断点设置在错误的虚拟地址上, 内核执行时就不会触发这些断点。

新版本的 qemu 竟然已经支持了地址随机化, 好事情.

#3 问题解决

##3.1 方法1 disable KASLR

最直接了当的方法(也是官方提供的方法) 是关闭地址随机化,

  • 重新编译内核关闭地址随机化
  • 或者在cmdline 里面直接添加 nokaslr

现在可以看到我们断点成功了, 内核的符号也没有偏移.

##3.2 方法2 重新加载内核镜像

###3.2.1 重新加载内核 text 段

我们找到内核加载的起始地址, 这个一般是 _text 符号的地址.

# echo 0x$(cat /proc/kallsyms | egrep -e "T _text$" | awk 'print $1')
# cat /proc/kallsyms | grep -E " _text$"
ffffffff9ee00000 T _text

# cat /proc/kallsyms | grep -E " schedule$"
ffffffff9f8a1800 T schedule

此时 KASLR 的偏移量为 0xffffffff9ee00000 - 0xffffffff81000000 = 0x1de00000L

然后通过 add-symbol-file 将 vmlinux 的起始地址指定到 0xffffffff9ee00000 位置处.

add-symbol-file ./vmlinux 0xffffffff9ee00000

可以看到断点断到了实际位置.

但是由于开启 KASLR 之后内核各个段的地址都是分别映射的, 这段我们只重新指定了内核代码段的位置.

###3.2.2 进阶用法

objdump -h ./vmlinux | grep -E " .text| .data "
  0 .text 00e010f1 ffffffff81000000 0000000001000000 00200000 2**12
 11 .data 0014c540 ffffffff82400000 0000000002400000 01600000 2**13

之前算出来 KASLR 的偏移是 0x1de00000L, .data 在 vmlinux 中的地址为 0xffffffff82400000, 加上偏移后实际的加载地址就是 0xffffffffa0200000. 这个地址就是 init_stack 的地址

使用 add-symbol-file 指定其他段(比如 data) 的地址.

# add-symbol-file ./vmlinux 0xffffffff9ee00000 -s .data 0xffffffffa0200000

add symbol table from file "./vmlinux" at
 .text_addr = 0xffffffff9ee00000
 .data_addr = 0xffffffffa0200000
(y or n) y
Reading symbols from ./vmlinux...done.

但是如此读取后, 再去读取数据段的值, 依旧失败, gdb 还是寻找错误的地址.
我们在 StackOverFlow 中找到了类似的例子, GDB can’t load kernel symbol correctly with KASLR enabled

这个感觉应该是 gdb 的问题, 记录在此, 等待问题解决后, 更新

#4 参考资料

gdb-qemu-cant-put-break-point-on-kernel-function-kernel

gdb-kernel-debugging

Using kgdb, kdb and the kernel debugger internals

Commands to specify files

以上是关于开启内核地址随机化KASLR后, qemu 调试 kernel 不能设置断点的主要内容,如果未能解决你的问题,请参考以下文章

linux内存布局和地址空间布局随机化(ASLR)下的可分配地址空间

基于mykernel 2.0编写一个操作系统内核

基于mykernel 2.0编写一个操作系统内核

gdb调试开启PIE且去掉符号表的程序

使用QEMU调试Linux内核代码

Android:有没有办法在运行时验证 KASLR