编译并vscode可视化调试Linux 4.18内核

1. 安装必要软件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 安装编译依赖组件。
sudo apt install -y libncurses5-dev libssl-dev bison flex libelf-dev gcc make openssl libc6-dev
sudo apt install -y flex bison make gcc libssl-dev bc libelf-dev
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v4.x/linux-4.18.tar.gz
tar -zxf linux-4.18.tar.gz
cd linux-4.18.tar.gz

# 设置调试的编译菜单
export ARCH=x86
make x86_64_defconfig
make menuconfig

# 下面选项如果没有选上的,选上(点击空格键),然后 save 保存设置,退出 exit。
##################################################################
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support

Device Drivers --->
[*] Block devices --->
<*> RAM block device support
(65536) Default RAM disk size (kbytes)

Processor type and features --->
[*] Randomize the address of the kernel image (KASLR)

Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging

Device Drivers -->
Network device support -->
<*> Universal TUN/TAP device driver support

[*] Networking support -->
Networking options -->
<*> 802.1d Ethernet Bridging

# 编译内核。
make -j8

2. 编译内核

编译内核会遇见很多问题

2.1 网络库的问题

1
2
3
4
5
6
7
8
9
In file included from scripts/selinux/genheaders/genheaders.c:19:
# scripts/selinux/mdp/mdp.c 也会出现这种现象
./security/selinux/include/classmap.h:249:2: error: #error New address family defined, please update secclass_map.
249 | #error New address family defined, please update secclass_map.
| ^~~~~
make[3]: *** [scripts/Makefile.host:90: scripts/selinux/genheaders/genheaders] Error 1
make[2]: *** [scripts/Makefile.build:558: scripts/selinux/genheaders] Error 2
make[1]: *** [scripts/Makefile.build:558: scripts/selinux] Error 2
make: *** [Makefile:1045: scripts] Error 2

解决方法

1.找到错误中提示的.c文件,genheader.c文件和mdp.c文件编辑,去掉两个文件的头部引用中的

1
#include <sys/socket.h>

2.找到错误提示中的classmap.h文件
a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
编辑classmap.h,在头文件中添加

1
#include <linux/socket.h>

重新编译即可

2.2 连接的问题

1
2
3
4
arch/x86/entry/entry_64.S: Assembler messages:
arch/x86/entry/entry_64.S:1667: Warning: no instruction mnemonic suffix given and no register operands; using default for `sysret'
AS arch/x86/entry/thunk_64.o
arch/x86/entry/thunk_64.o: warning: objtool: missing symbol table

使用这个patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Reported-by: Nathan Chancellor <natechancellor@xxxxxxxxx>
Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
tools/objtool/elf.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index be89c741ba9a..2b0f4f52f7b5 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -380,8 +380,11 @@ static int read_symbols(struct elf *elf)

symtab = find_section_by_name(elf, ".symtab");
if (!symtab) {
- WARN("missing symbol table");
- return -1;
+ /*
+ * A missing symbol table is actually possible if it's an empty
+ * .o file. This can happen for thunk_64.o.
+ */
+ return 0;
}

symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
--
2.29.2

3. 安装修改版的gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 删除 gdb
gdb -v | grep gdb
apt remove gdb -y

# 安装其它组件。
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt install software-properties-common
sudo apt-get update

# 安装高版本 gcc。
gcc --version
sudo apt install gcc-9 g++-9 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9
gcc --version

# 下载解压 gdb
cd /root
wget http://ftp.gnu.org/gnu/gdb/gdb-8.3.tar.gz
tar zxf gdb-8.3.tar.gz

# 修改 gdb/remote.c 代码。
cd gdb-8.3
vim gdb/remote.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#在gdb/remote.c文件下作如下修改
/* Further sanity checks, with knowledge of the architecture. */
// if (buf_len > 2 * rsa->sizeof_g_packet)
// error (_("Remote 'g' packet reply is too long (expected %ld bytes, got %d "
// "bytes): %s"),
// rsa->sizeof_g_packet, buf_len / 2,
// rs->buf.data ());
if (buf_len > 2 * rsa->sizeof_g_packet) {
rsa->sizeof_g_packet = buf_len;
for (i = 0; i < gdbarch_num_regs (gdbarch); i++){
if (rsa->regs[i].pnum == -1)
continue;
if (rsa->regs[i].offset >= rsa->sizeof_g_packet)
rsa->regs[i].in_g_packet = 0;
else
rsa->regs[i].in_g_packet = 1;
}
}

#编译GDB
./configure
./make -j8
# 有可能会有这个报错
# error: "Please include config.h first."
# vim ./gdb/build-gnulib/import/unistd.h
# 加入 #include "../config.h"
# 总之缺啥就在哪儿加上 #include "../config.h"
# 打开GDB,看是否为新版本

sudo apt install texinfo
sudo ./make install
# makeinfo: command not found

gdb -v

4. 安装qemu并配置qemu

1
sudo apt install -y qemu qemu-system-x86

制作helloworld的rootfs用于测试

1
touch main.c

键入以下代码

1
2
3
4
5
6
7
8
9
10
11
#include <stdio>
int main()
{
printf("hello world!");
printf("hello world!");
printf("hello world!");
printf("hello world!");
fflush(stdout);
while(1);
return 0;
}

编译

1
2
gcc --static -o helloworld main.c
echo helloworld | cpio -o --format=newc > rootfs

Qemu直接运行测试(非必须)

1
2
3
4
5
qemu-system-x86_64 \
-kernel ./arch/x86/boot/bzImage \
-initrd ./rootfs \
-append "root=/dev/ram rdinit=/helloworld console=ttyS0" \
-nographic -serial mon:stdio

但是实践证明还是有问题,最好是来一个图形化界面

Qemu 开启GDB调试

1
2
3
4
5
6
7
qemu-system-x86_64  \
-kernel ./arch/x86/boot/bzImage \
-initrd ./rootfs \
-append "root=/dev/ram rdinit=/helloworld" \
-smp 2 \
-s -S \
-append nokaslr

-s= -gdb tcp:1234, 这个可以改

-S 表示进入调试模式,在这个模式下已开始CPU是冻结的,在gdb中按下c来继续

这里的root=/dev/ram 因此需要在内核里面重新编译,开启对ext2的支持

进行以上会打开Qemu并进入等待调试状态,此时可以直接gdb调试,如下(非必须)

1
2
3
4
5
gdb ./vmLinux
#以下进行调试
target remote:1234
b start_kernel
c

可以发现内核被断点在start_kernel函数上

5. 使用vscode远程阅读linux内核源码

首先直接打开的话会发现有很多地方没法跳转,需要安装C/C++ GNU Global

在VScode远程窗口中安装C/C++ GNU Global 这个扩展

在远程机器中

1
sudo apt install global

在vscode设置中,写入

1
2
3
4
5
{
"gnuGlobal.globalExecutable": "/usr/bin/global",
"gnuGlobal.gtagsExecutable": "/usr/bin/gtags",
"gnuGlobal.objDirPrefix": "/home/damaoooo/.global"
}

shift + command + P,然后 rebuild Gtags Data

之后就可以进行Linux源代码阅读了

如果需要debug,需要安装 GDB debug 插件,随后配置launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "kernel-debug",
"type": "cppdbg",
"request": "launch",
"miDebuggerServerAddress": "192.168.1.114:1234",
"program": "${workspaceFolder}/vmlinux",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"logging": {
"engineLogging": false
},
"MIMode": "gdb",
}
]
}

启动qemu,就可以对内核进行调试了

但是有问题,如图所示,很多的变量会被optimized out,是因为默认编译的时候开了O2,而开O0选项,直接编译不过,需要做一些修改,我懒得改了直接单步调试看内存了。

image-20220703171844616