深入理解系统调用
Posted yhhh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解系统调用相关的知识,希望对你有一定的参考价值。
一、实验要求
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用。本人学号最后2位为30,采用30号调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验环境
VMware Workstation Pro 15
ubuntu18.04.4
三、实验准备
配置内核
make defcon?g # Default con?guration is based on ‘x86_64_defcon?g‘ make menucon?g # 打开debug相关选项 Kernel hacking ---> Compile-time checks and compiler options ---> [*] Compile the kernel with debug info [*] Provide GDB scripts for kernel debugging [*] Kernel debugging # 关闭KASLR,否则会导致打断点失败 Processor type and features ----> [] Randomize the address of the kernel image (KASLR)
编译内核
make -j$(nproc) # nproc gives the number of CPU cores/threads available qemu-system-x86_64 -kernel arch/x86/boot/bzImage # 测试?下内核能不能正常加载运?,因为没有?件系统最终会kernel panic
制作根文件系统
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 menucon?g #记得要编译成静态链接,不?动态链接库。 Settings ---> [*] Build static binary (no shared libs) #然后编译安装,默认会安装到源码?录下的 _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?件
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Welcome My OS!" echo "-------------------" cd home /bin/sh
给init脚本增加可执行权限
chmod +x init
输入下面代码查看qemu运行情况
#打包成内存根?件系统镜像 ?nd . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz #测试挂载根?件系统,看内核启动完成后是否执?init脚本 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
启动内核:
四、实验内容
1.查询30号系统调用是shmat函数
shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
为了观察该系统调用,在rootfs/home目录下写一个程序来触发系统调用
#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> int main(){ int shmid; asm volatile( "movl %1, %%ebx " //将第一个参数放入edi "movl %2, %%ecx " //将第二个参数放入rsi "movl %3, %%edx " //将第三个参数放入esi "movl $0x1E, %%eax " //中断号30放入eax "syscall " //触发系统调? "movl %%eax, %0 " :"=m"(shmid) //将结果放入shmid中 ); if(shmid<0){ printf("error"); return -1; } printf("%d ",shmid); return 0; }
对该程序进行静态编译
gcc testSys.c -o testSys -static
重新打包内存跟文件系统镜像
?nd . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
启动虚拟机
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
2.在linux-5.4.34目录下启动gdb调试
gdb vmlinux target remote:1234 b __64x_sys_shmat c
GDB调试
寄存器eax保存系统调用号30、ebx,ecx和edx分别保存三个栈顶参数。
调用中断指令int 0x80进入内核态。找到函数中断入口,执行 entry_SYSCALL_64() ,然后开始通过swapgs 和压栈动作保存现场。
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 /*swapgs指令切换gs寄存器从用户态到内核态*/ /* tss.sp2 is scratch space. */ movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /*保存中断上下文中的rsp寄存器的值*/ SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp /*切换到内核堆栈空间*/ movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs->ss */ pushq PER_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 /* IRQs are off. */ movq %rax, %rdi movq %rsp, %rsi call do_syscall_64 /* returns with IRQs disabled */
之后执行do_syscall_64(),此时根据eax查询系统调用表,获得系统调用号,执行系统调用的内容。
#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); }
服务结束后, 调用entry_SYSCALL_64( )最后完成现场恢复,回到用户态,返回结果。
以上是关于深入理解系统调用的主要内容,如果未能解决你的问题,请参考以下文章