1 实验要求
2 实验准备
sudo apt install build-essential
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison ?ex 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
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar cd linux-5.4.34
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
# 测试?下内核能不能正常加载运?,因为没有?件系统终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage # 此时应该不能正常运行
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/
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Welcome My OS!"
echo "-------------------"
cd home
/bin/sh
chmod +x init
#打包成内存根?件系统镜像
?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
启动内核:
3 实验内容
int shmget(key_t key, size_t size, int shmflg);
。其中
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SIZE 1024
int main(){
int shmid;
asm volatile(
"movl %1, %%edi\n\t" //将第一个参数放入edi
"movl %2, %%rsi\n\t" //将第二个参数放入rsi
"movl %3, %%esi\n\t" //将第三个参数放入esi
"movl $0x1D, %%eax\n\t" //中断号29放入eax
"syscall\n\t" //产生中断信号进行系统调用
"movl %%eax, %0\n\t"
:"=m"(shmid) //将结果放入shmid中
:"b"(&IPC_PRIVATE),"c"(&SIZE),"d"(&SIZE,IPC_CREAT|0600)
);
if(shmid<0){
printf("error");
return -1;
}
printf("%d\n",shmid);
return 0;
}
将形成的可执行文件放到rootfs/home/目录下,然后重新打包rootfs文件夹
`$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz`
通过gdb跟踪该系统调用的内核处理过程
执行下列命令,重新打包根文件系统,开启虚拟机gdb调试。
$ qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
开启新的terminal
$ cd linux-5.4.34
$ gdb vmlinux
$ target remote:1234
$ c
运行结果如下:
使用bt命令查看系统调用栈:
根据上图可以看出,系统调用的进入点为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 /*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 /*保存系统调用号到rdi*/
movq %rsp, %rsi /*保存内核堆栈地址在rsi*/
call do_syscall_64 /* returns with IRQs disabled */
swapgs指令主要的作用是将保存现场和恢复现场时的寄存器保存起来,然后将pt_regs中的相关字段保存到内核栈中。
代码最后调用了do_syscall_64。该函数包含两个参数,其中rdi传递的是系统调用号,rsi传递的是内核堆栈地址。函数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);
}
首先通过传入的系统调用号nr找到相应的系统调用,并将返回值保存在regs的ax中。
调用结束后,执行syscall_return_slowpath,进行返回。然后在gdb单步调试中,我们可以看到从syscall_return_slowpath返回后,开始恢复现场。
主要是将之前保存在栈中的寄存器的值,重新恢复到原来的寄存器中。继续单步执行,直到恢复现场完成:
4 实验总结
通过实验可知,系统调用的大致过程为:
首先,通过汇编指令中的系统调用号找到系统调用入口ENTRY(entry_SYSCALL_64),然后在ENTRY(entry_SYSCALL_64)中,执行swapgs保存现场。随后执行do_syscall_64方法,该方法中根据传参nr保存的的系统调用号跳转至具体的系统调用函数,系统调用函数执行完毕后,执行syscall_return_slowpath,进行返回,最后进行现场恢复。
原文:https://www.cnblogs.com/L-xuan/p/12964746.html