- 前言
- 给MenuOS增加命令
- 使用gdb跟踪调用内核函数sys_getpid
- gdb调试
- 查找sys_getpid和system_call代码
- 画出流程图
- 总结
之前我们使用了两种方法进行了系统调用,一种是使用库函数(调用API),第二种是C语言嵌入内嵌汇编。详见博客
给MenuOS增加命令首先可以删除实验楼自带的系统,然后再git clone一个新的,这样减少了编译过程,一个make搞定
我们进入menu中的test.c,新增我上一次博客中的两个功能的代码(一个API,一个内嵌汇编)-注意要加头文件**#include
之前我们编译这个内核需要的命令是
gcc -pthread -o init linktable.c menu.c test.c -m32 -static mv init ../rootfs cd ../rootfs find . | cpio -o -Hnewc | gzip -9 > ../rootfs.img cd .. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
这四行命令意思是将三个C语言一起编译成可执行文件Init,将Init移动到rootfs(没有就自己建)文件夹下,进入rootfs文件夹,第四行是将这个文件夹下的init打包成rootfs.img镜像。最后一行使用qemu虚拟机模拟rootfs.img镜像
但是现在只需要在menu文件夹下执行如下命令即可
make rootfs
可以使用函数(pid为1-对应的是init)
首先返回父目录,再启动镜像,但一定要把镜像冻结起来,命令如下
cd .. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
这里-s是在1234端口创建一个gdb-server,-S是将其冻结起来,方便gdb调试
再打开一个窗口,打开gdb,然后输入
file linux-3.18.6/vmlinux target remote:1234
将内核加载进来
然后可以设置两个断点
b start_kernel b sys_getpid查找sys_getpid和system_call代码
可以看到这个sys_getpid函数在kernel/sys.c里,查找代码如下
SYSCALL_DEFINE0(getpid) { return task_tgid_vnr(current); } SYSCALL_DEFINE0(gettid) { return task_pid_vnr(current); } SYSCALL_DEFINE0(getppid) { int pid; rcu_read_lock(); pid = task_tgid_vnr(rcu_dereference(current->real_parent)); rcu_read_unlock(); return pid; } SYSCALL_DEFINE0(getuid) { return from_kuid_munged(current_user_ns(), current_uid()); } SYSCALL_DEFINE0(geteuid) { return from_kuid_munged(current_user_ns(), current_euid()); } SYSCALL_DEFINE0(getgid) { return from_kgid_munged(current_user_ns(), current_gid()); } SYSCALL_DEFINE0(getegid) { return from_kgid_munged(current_user_ns(), current_egid()); }
实际的system_call在linux-3.18.6/arch/x86/kernel/entry_32.S里
ENTRY(system_call) RING0_INT_frame # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # 保存系统调用号 SAVE_ALL #保存现场,将用到的所有CPU寄存器保存到栈中 GET_THREAD_INFO(%ebp) #ebp用于存放当前进程thread_info结构的地址 # system call tracing in operation / emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax #检查系统调用号(系统调用号应小于nr_syscalls) jae syscall_badsys #不合法 跳入异常处理 syscall_call: call *sys_call_table(,%eax,4) #通过系统调用号在系统调用表中找到相应的系统调用内核处理函数 -这一行很重要 syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value 保存返回值到栈中 syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt 检查是否有任务需要处理 # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work #需要,进入syscall_exit_work,这是常见进程调度时机 restore_all: TRACE_IRQS_IRET #恢复现场 restore_all_notrace: #ifdef CONFIG_X86_ESPFIX32 movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS # Warning: PT_OLDSS(%esp) contains the wrong/random values if we # are returning to the kernel. # See comments in process.c:copy_thread() for details. movb PT_OLDSS(%esp), %ah movb PT_CS(%esp), %al andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax CFI_REMEMBER_STATE je ldt_ss # returning to user-space with LDT SS #endif restore_nocheck: RESTORE_REGS 4 # skip orig_eax/error_code irq_return: INTERRUPT_RETURN #结束的iret画出流程图
至于流程图中的syscall_exit_work,working_pending与work_resched在linux-3.18.6/arch/x86/kernel/entry_32.S里的593到665行。
上次实验是通过API和内嵌汇编来系统调用,这次是具体分析了系统调用的内核处理过程,比较重要的是call *sys_call_table(,%eax,4),这一段汇编是通过系统调用号,比如我这次实验中的0x14(变为十进制就是20),来查找与之匹配的系统调用函数,20对应的系统调用函数是getpid,其他系统调用原理与之相似,详细对应关系见对应关系,在流程图中SAVE_ALL将所有CPU寄存器保存到栈中,找到syscall_call,sys_call_table与call *sys_call_table(,%eax,4)调用系统调用函数。
以上有很多是我个人见解,如果有错误,欢迎各位大佬指正。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)