深入理解系统调用
1. 实验要求
本人学号后两位为53,则寻找系统调用号为53的系统调用
通过汇编指令触发该系统调用
通过gdb跟踪该系统调用的内核处理过程
重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
2. 环境搭建
2.1 安装环境
在实验一中已经安装过了环境,如果需要可以按照以下命令:
sudo apt install build-essential
sudo apt install qemu
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
sudo apt install axel
axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
# 建议不要使用上面命令,下载慢,可以提前下载好linux-5.4.34.tar.xz
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar cd linux-5.4.34
2.2 配置内核编译选项
运行以下命令:
make defconfig
make menuconfig
进入 Kernel hacking的Compile-time checks and compiler options:


进入Processor type and features:

2.3 编译内核
运行以下命令:
make -j$(nproc)
qemu-system-x86_64 -kernel arch/x86/boot/bzImage

由于没有文件系统,最终会 kernel panic
2.4 利用busybox制作根文件系统
下载并制作根文件系统:
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
make menuconfig
make -j$(nproc) && make install
在配置选项时进入Settings,选择Build static binary (no shared libs)

制作内存根文件系统镜像:
mkdir rootfs
cd rootfs
cp ../busybox-1.31.1/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
编写init脚本,并修改权限:
vi init
#!/bin/sh
mount -t proc none /proc mount -t sysfs none /sys
echo "Wellcome WangbaOS!"
cd home
/bin/sh
chmod +x init

打包成内存根文件系统镜像,并测试挂载根文件系统:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
cd ..
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
运行结果如下:

3. 汇编指令触发该系统调用
3.1 查询系统调用号为53为的系统调用
使用以下命令查询:
cd linux-5.4.34/arch/x86/entry/syscalls # 进入syscalls文件夹
cat syscall_64.tbl | grep 53 # 查询和53相关的系统调用

可以看到是 socketpair:
使用手册看该函数:
man socketpair

从上面的描述中可以看出:
socketpair 函数用于创建一对无名的、相互连接的套接字;如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。
3.2 编写普通汇编调用代码
在 rootfs/home 编写socketpair.c,如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdlib.h>
const char* str = "SOCKET PAIR TEST.";
int main(int argc, char* argv[]){
char buf[128] = {0};
int socket_pair[2];
pid_t pid;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1 ) {
// 如果返回-1,打印errno
printf("Error, socketpair create failed, errno(%d): %s\n", errno, strerror(errno));
return EXIT_FAILURE;
}
int size = write(socket_pair[0], str, strlen(str)); // 将str从socket_pair[0]写入
read(socket_pair[1], buf, size);
printf("Read result: %s\n",buf); // 将str从socket_pair[1]读出,并打印结果
return EXIT_SUCCESS;
}
gcc 静态编译,并运行,可以看到 str 的值:

4. 利用gdb跟踪系统调用的内核处理过程
4.1 重新制作根文件系统,并启动qemu
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
cd ..
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
重新打开一个终端,并运行以下命令:
# 打开gdb
gdb vmlinux
target remote:1234
# 并在gdb中对__x64_sys_socketpair进行断点
b __x64_sys_socketpair

输入c(continue)继续运行。
4.2 在qemu窗口运行程序并在gdb窗口进行单步调试
qemu窗口:

gdb窗口:

可以看到调用了 net/socket.c下的1619行:

又调用了do_syscall_64:

继续调用了entry_SYSCALL_64 ()


整个流程大概如下:
用户态:
socketpair.c ----> __x64_sys_socketpair ----> _sys_socketpair
内核态:
entry_SYSCALL_64 () ----> do_syscall_64 ----> _sys_socketpair ----> do_syscall_64 ----> syscall_return_slowpath() ----> entry_SYSCALL_64 ()

浙公网安备 33010602011771号