泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓Linux实验盘,即刻上手内核与嵌入式开发
请稍侯

探索从 vmlinux 中抓取 Linux 内核 config 文件

Wu Zhangjin 创作于 2019/07/22

By Falcon of TinyLab.org Jul 18, 2019

1 背景简介

最近两周在忙活 Linux Lab V0.2 RC1,其中一个很重要的目标是添加国产龙芯处理器支持。

在添加龙芯 ls2k 平台的过程中,来自龙芯的张老师已经准备了 vmlinux 和 dtb,还需要添加配置文件和源代码,但源码中默认的配置编译完无法启动,所以需要找一个可复用的内核配置文件。

在张老师准备的内核 vmlinux 中,确实有一个 /proc/config.gz,说明内核配置文件已经编译到内核了,但是由于内核没有配置 nfs,尝试了几次没 dump 出来。

当然,其实也可以用 zcat /proc/config.gz 打印到控制台,然后再复制出来,这个时候要把控制台的 scrollback lines 设置大一些,但是没那么方便。

2 极速体验

这里讨论另外一个方法,这是张老师分享的一个小技巧,那就是直接用 Linux 内核源码下的小工具:script/extract-ikconfig

  1. $ cd /path/to/linux-kernel
  2. $ scripts/extract-ikconfig /path/to/vmlinux

执行完的结果跟 zcat 一致,需要保存到文件,可以这样:

  1. $ scripts/extract-ikconfig /path/to/vmlinux > kconfig

需要注意的是,这个前提是配置内核时要开启 CONFIG_IKCONFIG 选项。而如果要拿到 /proc/config.gz,还得打开 CONFIG_IKCONFIG_PROC

3 原理分析

大概的原理我们来剖析一下。

Makefile

初始化 KCONFIG_CONFIG

  1. KCONFIG_CONFIG ?= .config

kernel/Makefile

.config 用 gzip 压缩了一份,放到了 kernel/config_data.gz

  1. $(obj)/configs.o: $(obj)/config_data.gz
  2. targets += config_data.gz
  3. $(obj)/config_data.gz: $(KCONFIG_CONFIG) FORCE
  4. $(call if_changed,gzip)

kernel/configs.c

kernel/config_data.gz 放到 .rodata section,并在前后加了字符串标记:IKCFG_STIKCFG_ED

  1. /*
  2. * "IKCFG_ST" and "IKCFG_ED" are used to extract the config data from
  3. * a binary kernel image or a module. See scripts/extract-ikconfig.
  4. */
  5. asm (
  6. " .pushsection .rodata, \"a\" \n"
  7. " .ascii \"IKCFG_ST\" \n"
  8. " .global kernel_config_data \n"
  9. "kernel_config_data: \n"
  10. " .incbin \"kernel/config_data.gz\" \n"
  11. " .global kernel_config_data_end \n"
  12. "kernel_config_data_end: \n"
  13. " .ascii \"IKCFG_ED\" \n"
  14. " .popsection \n"
  15. );

scripts/extract-ikconfig

通过 grep -abo 去找到 kconfig data 的位置。-abo 的意思是:-a 把二进制文件当 text 处理,-b 打印字节偏移,-o 只打印要匹配的字符串:

  1. dump_config()
  2. {
  3. if pos=`tr "$cf1\n$cf2" "\n$cf2=" < "$1" | grep -abo "^$cf2"`
  4. then
  5. pos=${pos%%:*}
  6. tail -c+$(($pos+8)) "$1" | zcat > $tmp1 2> /dev/null
  7. if [ $? != 1 ]
  8. then # exit status must be 0 or 2 (trailing garbage warning)
  9. cat $tmp1
  10. exit 0
  11. fi
  12. fi
  13. }

这个脚本写得有点晦涩,大体意思是先找到 “IKCFG_ST”,算出 kconfig data 位置,再用 tail 取出来。

4 换个思路

我们自己换个更清晰的思路。

先看看 vmlinux 和 kernel/config_data.gz 的布局:

  1. "IKCFG_ST ..... IKCFG_ED" --> vmlinux
  2. ^ kernel/config_data.gz ^ --> kernel/config_data.gz

首先,找出 IKCFG_STIKCFG_ED 的位置。然后换算出 kernel/config_data.gz 的前后位置:

  1. $ egrep -abo "IKCFG_ST|IKCFG_ED" boards/loongson/ls2k/bsp/kernel/v3.10/vmlinux
  2. 14508864:IKCFG_ST
  3. 14529536:IKCFG_ED

kernel/config_data.gz 的起始地址需要加上 “IKCFG_ST” 的长度,即 +8$((14508864+8)),而结束地址刚好是 “IKCFG_ED” 的地址 -1$((14529536-1)),总的 size 是:

  1. $ echo $(((14529536-1) - (14508864+8) + 1))
  2. 20664

这样,我们就可以用 dd 命令截取出来:

  1. $ dd if=boards/loongson/ls2k/bsp/kernel/v3.10/vmlinux bs=1 skip=$((14508864+8)) count=20664 of=kconfig.gz
  2. $ file kconfig.gz
  3. kconfig.gz: gzip compressed data, max compression, from Unix
  4. $ zcat kconfig.gz

完美!逻辑上更清晰,基于这个逻辑改写了一个自己的 extract-ikconfig,见 Linux Lab 下的 tools/kernel/extract-ikconfig

5 小结

小技巧,大道理!

上述探索涉及到内联汇编,涉及到如何把一个文件嵌入到内核执行文件中,涉及到如何解析二进制文件并截取想要的内容。

回到这个技巧本身,也不失为 Debugging 的一个必备技能,内核配置文件对于复现问题,找到问题现场必不可少!

再来总结一下:

  • 编译内核时,打开 CONFIG_IKCONFIGCONFIG_IKCONFIG_PROC
  • 从 Runtime 内核中抓取:zcat /proc/config.gz,如果找不到了 vmlinux,这个不失为一个好方法。
  • 从静态内核 vmlinux 中抓取:scripts/extract-ikconfig /path/to/vmlinux


Read Album:

Read Related:

Read Latest: