实验内容:
寻找系统呼叫,系统呼叫号码为学号的最后2位相同的系统呼叫【即97号系统呼叫】
汇编指令触发此系统调用
用gdb跟踪这个系统调用的内核处理过程
重点阅读和分析系统调用入口的保存现场、恢复现场和系统调用的返回,重点关注系统调用过程中内核堆栈状态的变化
实验环境:
VMWare虚拟机下的Ubuntu18.04.4,用于实验的内核版本为linux-5.4.34。
1环境准备
1.1内核编译
回滚实验1的修补程序操作:
cd linux-5.4.34
patch-r-P1 ./my kernel-2.0 _ for _ Linux-5.4.34.patch
生成定义
更改内核编译配置并重新编译:
打开调试相关选项
Kernel hacking ---
compile-timechecksandcompileroptions----
[ * ] compilethekernelwithdebuginfo
[ * ] providegdbscriptsforkerneldebugging
[ * ]密钥调试
关闭KASLR。 否则断点将失败
Processor type and features ---
[ ] randomizetheaddressofthekernelimage (ka SLR )。
生成菜单
make -j$(nproc )
启动内核。 内核无法正常工作。 指示Kernel panic报告错误。
QEMU-system-x86 _ 64-kernel arch/x86/boot/bzimage
错误消息表明内核无法装载,因为缺少所需的根文件系统。
1.2创建根文件系统
要打开并启动计算机,引导加载器首先加载内核。 必须立即挂载包含所需设备驱动程序和工具的内存根文件系统。
为了简化实验环境,只用BusyBox制作迷你内存根文件系统,并给出基本的用户状态可执行程序。
首先从https://www.busybox.net下载并解压缩busybox源代码,解压缩完成后配置并安装编译。
axel-n 20https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
设置为不使用动态链接库编译为静态链接。
cd busybox-1.31.1
生成菜单
编译安装时,缺省情况下安装在源目录的_install目录中。
make -j$(nproc ) make install
镜像内存根文件系统:
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脚本文件(rootfs/init )添加到根文件系统目录中。 init的内容如下。
#! /在意的乌冬面/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo 'Wellcome MengningOS!'
echo-------------------- "
光盘主页
/在意的乌冬面/sh
将可执行权限添加到init脚本:
chmod x init
打包并镜像到内存根文件系统:
find.- print0| cpio-- null-ov-- format=newc|gzip-9 ./rootfs.cpio.gz
装载根文件系统,并检查是否在内核启动完成后运行init脚本。
qmu-system-x86 _ 64-kernel Linux-5.4.34/arch/x86/boot/bzimage-initrd rootfs.cpio.gz
当bootloader成功将根文件系统加载到内存中时,内核将挂载在根目录下。
接下来,运行根文件系统
统中 init 脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。2 系统调用
2.1 查找系统调用
在 linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl 文件中找到相应的系统调用:
2.2 触发系统调用
getrlimit用于获得每个进程能够创建的各种系统资源的限制使用量。
在rootfs/home/目录下新建getrlimit_test.c进行测试:
#include
#include
int main()
{
struct rlimit limit;
int ret = getrlimit(RLIMIT_NOFILE, &limit);
printf("ret = %d,tcur = %ld,tmax = %ldn",
ret, limit.rlim_cur, limit.rlim_max);
return 0;
}
函数执行成功返回0,失败返回1。
其中,RLIMIT_NOFILE表示每个进程能打开的最多文件数。
limit.rlim_cur为当前软件限制,limit.rlim_max为最大硬件限制。
采用静态编译:
gcc -o getrlimit_test getrlimit_test.c -static
代码测试结果如下:
getrlimit测试成功后,通过编写汇编代码来触发系统调用:
#include
#include
int main()
{
struct rlimit limit;
int ret = -1;
asm volatile(
"movq %2, %%rsint"
"movl %1, %%edint"
"movl $0x61, %%eaxnt"
"syscallnt"
"movq %%rax,%0nt"
:"=m"(ret)
:"a"(RLIMIT_NOFILE), "b"(&limit)
);
printf("ret = %d,tcur = %ld,tmax = %ldn",
ret, limit.rlim_cur, limit.rlim_max);
return 0;
}
2.3 跟踪系统调用内核处理过程
重新制作根文件系统:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
纯命令行启动qemu:
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
开启新的terminal进行gdb调试:
cd linux-5.4.34
gdb vmlinux
target remote:1234
c
添加断点测试:
b __x64_sys_getrlimit
发现断点处无法停止,需要分析getrlimit反汇编的代码:
此处实际调用的是0x12e也就是302号系统调用,所以之前的断点才会没有反应。
重新设置断点:
b __x64_sys_prlimit64
成功进入中断:
观察函数调用栈,可以找到系统调用入口 entry_SYSCALL_64:
ENTRY(entry_SYSCALL_64)
UNWIND_HINT_EMPTY
/*
* Interrupts are off on entry.
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
* it is too small to ever cause noticeable irq latency.
*/
swapgs
/* tss.sp2 is scratch space. */
movq%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movqPER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* Construct struct pt_regs on stack */
pushq$__USER_DS/* pt_regs->ss */
pushqPER_CPU_VAR(cpu_tss_rw + TSS_sp2)/* pt_regs->sp */
pushq%r11/* pt_regs->flags */
pushq$__USER_CS/* pt_regs->cs */
pushq%rcx/* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
pushq%rax/* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
TRACE_IRQS_OFF
之后调用 do_syscall_64:
#ifdef CONFIG_X86_64
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
struct thread_info *ti;
enter_from_user_mode();
local_irq_enable();
ti = current_thread_info();
if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY)
nr = syscall_trace_enter(regs);
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
#ifdef CONFIG_X86_X32_ABI
} else if (likely((nr & __X32_SYSCALL_BIT) &&
(nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) {
nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
X32_NR_syscalls);
regs->ax = x32_sys_call_table[nr](regs);
#endif
}
syscall_return_slowpath(regs);
}
#endif
SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource,
const struct rlimit64 __user *, new_rlim,
struct rlimit64 __user *, old_rlim)
{
struct rlimit64 old64, new64;
struct rlimit old, new;
struct task_struct *tsk;
unsigned int checkflags = 0;
int ret;
if (old_rlim)
checkflags |= LSM_PRLIMIT_READ;
if (new_rlim) {
if (copy_from_user(&new64, new_rlim, sizeof(new64)))
return -EFAULT;
rlim64_to_rlim(&new64, &new);
checkflags |= LSM_PRLIMIT_WRITE;
}
rcu_read_lock();
tsk = pid ? find_task_by_vpid(pid) : current;
if (!tsk) {
rcu_read_unlock();
return -ESRCH;
}
ret = check_prlimit_permission(tsk, checkflags);
if (ret) {
rcu_read_unlock();
return ret;
}
get_task_struct(tsk);
rcu_read_unlock();
ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL,
old_rlim ? &old : NULL);
if (!ret && old_rlim) {
rlim_to_rlim64(&old, &old64);
if (copy_to_user(old_rlim, &old64, sizeof(old64)))
ret = -EFAULT;
}
put_task_struct(tsk);
return ret;
}
运行结束后,通过syscall_return_slowpath返回,系统调用完毕。