383 + 原创作品转载请注明出处 + 中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel。
实验内容:
1、编译linux5.0.2内核
2、制作根文件系统
3、183号系统调用跟踪分析(分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程)(PS:83号有点蛋疼,换了一个)
4、总结(对系统调用工作机制的理解)
一、编译linux5.0.2内核
1、下载linux-5.0.2。
2、解压linux-5.0.2.tar.xz
tar -zvxf linux-5.0.2.tar.xz
3、配置编译选项,编译内核
此处我们直接采用i386缺省编译:make i386_defconfig
也可以自行选择需要编译的内容:make menuconfig(menuconfig是图形化编译选择界面,操作简介明了。注:本人在menuconfig中勾选了kernel hacking -> Compile-time checks and compiler options ->compile the kernel with debug info以及取消了网络相关的内容)
然后开始编译: make 或 make -jN(N为线程数,多线程编译更加快)
(PS:因为赶时间,所以编译中遇到的问题就不多做赘述了,都能从网络上找到解决方案。)
![2c4dcf159bd502886f221385b2edd88f.png](https://i-blog.csdnimg.cn/blog_migrate/b87427d60b9edc2eba18c24fa832e16f.jpeg)
二、制作根文件系统
(PS:在此处我想特别吐槽下,按照ppt去做在较高的版本下肯定失败,之前按照ppt做,弄了好久没弄出来。)
此处我们就直接点,老师为我们准备好了资源就直接拿来用。
1、下载menu文件,并进入menu目录下。
git clone https://github.com/mengning/menu.git
cd menu
(PS:此处有的人的系统是64位的,编译32位需要安装软件包:sudo apt-get install libc6-dev-i386)
2、对menu进行编译,并生成rootfs.img(根文件)
make rootfs
(PS:为什么直接编译就能生成呢?因为在menu/Makefile文件中已经写好了生成的指令,直接编译就能生成)
![2ff782883a76cb481af18c3792813685.png](https://i-blog.csdnimg.cn/blog_migrate/a140dc4bcd19f14ad3fd36e52840dae8.jpeg)
{
补充:自己做rootfs.img,比如拿一个hello.c文件,如下图所示,
![a9ef17926184fbc6bd6da12033b8f041.png](https://i-blog.csdnimg.cn/blog_migrate/e766c17b23934f06d6577317fc02efa2.jpeg)
然后在hello.c所在目录下进行编译:gcc -pthread -o init hello.c -m32 -static
制作img文件:find init | cpio -o -Hnewc |gzip -9 > ../rootfs.img
输出为:xxxx blocks则制作成功了,来运行试试。
qemu -kernel linux-5.0.2/arch/x86/boot/bzImage -initrd rootfs.img
![f6e12dabad19cadbccaa8ebd8757d2e8.png](https://i-blog.csdnimg.cn/blog_migrate/d25ef3ad89c1ec9220e90734e7ad7c75.jpeg)
}
3、qemu启动,测试系统是否可以正常运行
qemu -kernel linux-5.0.2/arch/x86/boot/bzImage -initrd rootfs.img
![7c8124d12ba379767e2e59c2a7248666.png](https://i-blog.csdnimg.cn/blog_migrate/366078d355dea6e6fcd665d8d0a5d383.jpeg)
若出现上图,则说明你的内核已经配置编译完成了。
三、183号系统调用跟踪分析。
1、做好调试跟踪的准备
那么183号系统调用是什么呢?我们可以从linux-5.0.2中找到。
linux-5.0.2/arch/x86/entry/syscalls/syscall_32.tbl(PS:此处说明下,我们的实验都是32位操作的)
![674c843bcb04cf664e87b36f82aaba4b.png](https://i-blog.csdnimg.cn/blog_migrate/a2c8ab8c1453545b42d65dc9e2b35f35.png)
接下来参照menu/test.c文件布置我们自己的系统调用。
![dce22c58525c91471214b05cf1d06725.png](https://i-blog.csdnimg.cn/blog_migrate/1a19883d77e0af805d2cd209dcded17f.jpeg)
![cfb753f10cd02dbf5bea02d6b62c972e.png](https://i-blog.csdnimg.cn/blog_migrate/a8e6dce3c2afc65b233515a890cdceb7.jpeg)
我在此处简单实现了一下:
![20474fd4349940b4f4d2857b367658e6.png](https://i-blog.csdnimg.cn/blog_migrate/3c0e7de28b264b607d1f173ecaa29282.jpeg)
![47322a28f0b9146e2bd85f692b4c49fa.png](https://i-blog.csdnimg.cn/blog_migrate/54c1c309b258f4bf7c9e09184c3a3065.jpeg)
然后对main函数中稍作修改:
![fb3001ce95c41bb5f9b84467354e5f5b.png](https://i-blog.csdnimg.cn/blog_migrate/8194da8b16e6653d24c4ad914b7c423b.jpeg)
最后对menu文件夹重新进行编译:
make rootfs
2、结果展示
![2bad17d3a95539b29cc9bd8ac123c591.png](https://i-blog.csdnimg.cn/blog_migrate/31b7ceee29c1dd35e4339ffda75cb230.jpeg)
getcwd系统调用返回的是当前工作目录路径。
3、进行调试跟踪
qemu -kernel linux-5.0.2/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr(PS:不加nokaslr的话断点可能失效)
另起一个终端,进入linux-5.0.2文件夹下
gdb
![c87c7c279cabd2a708c5e06f144b7cbd.png](https://i-blog.csdnimg.cn/blog_migrate/5892b48b2dc6d00c6526ad17cf6249d0.jpeg)
file vmlinux
target remote:1234
b sys_getcwd
接下来就是直接c。
![45d5bbb28082ab44d2fee531972d2cb1.png](https://i-blog.csdnimg.cn/blog_migrate/fd3bc7c4bbf04e52fe6d59425ba33559.jpeg)
接下来对已运行的qemu中的server进行操作。
getcwd
![3eeafa7951f35b6be25f9736246644b4.png](https://i-blog.csdnimg.cn/blog_migrate/e9338a4c9461dba04e67f4eab9c015ae.png)
然后返回gdb查看,发现断点位置。
![dd5b6069480e1149219809a44e1e3c54.png](https://i-blog.csdnimg.cn/blog_migrate/e7b5dc7db4140a8c374ae0d4128277e2.jpeg)
对此处断点进行汇编语言分析,
disass
![dafed84b7ecdc88e9899d9a8cb092002.png](https://i-blog.csdnimg.cn/blog_migrate/5c44ac163936c74d6cedee4f3ff5120d.jpeg)
![1855d49da48a015e18d3da22a59e0d67.png](https://i-blog.csdnimg.cn/blog_migrate/5b8a02ec63f7f0869243a298ed0857a0.jpeg)
很明显可以看出开头几行是一个压栈操作,之后又申请了一块内存,然后拷贝给用户态,再释放内存,相当于一次现场保护,开始进行getcwd的系统调用。
我们继续,(gdb)n
![b1da25aedf90c6fdc8449f1848f5c6e4.png](https://i-blog.csdnimg.cn/blog_migrate/4d3164b532b7cf5b8dfb7e1a58c9695e.png)
由函数名称可以看出已经开始系统调用,相应终端请求。如果进行disass的话可以发现:
![cc59033f5bae8f878d484ba2b030e0dd.png](https://i-blog.csdnimg.cn/blog_migrate/72a63dac05d1f100e4ad0c7cb26ec54d.jpeg)
其中存在系统调用号(0x7b=183)的传递。
![d8abca5bd8c55f0bd2beecece062838b.png](https://i-blog.csdnimg.cn/blog_migrate/05862ea6af136c0fd2092e88fbbdfc0b.png)
系统调用的实现并返回。
当进入entry_32.S之后,可以看到最初传进来的参数已经保存在寄存器里面了:
![24848df8d71c82f767658c0ec4d221c4.png](https://i-blog.csdnimg.cn/blog_migrate/4439c5f1ea1579dd789e8b8b47a28342.jpeg)
![189403c92f56e7f975cc3ed7fcc76722.png](https://i-blog.csdnimg.cn/blog_migrate/5e3c8516e80e6065ef21a5aa45c5b77a.jpeg)
其实可以直接看common.c的源码进行整个系统调用的分析:
![8271ac36079c2d9faf420ebc6065407a.png](https://i-blog.csdnimg.cn/blog_migrate/37b37551a925fe333aa64502834628bb.jpeg)
开始一次int80中断,进行系统调用,从用户态进入内核态,执行中断请求。
在do_syscall_32_irqs_on函数最后会调用syscall_return_slowpath。
![34a673ceb779c8a28a3032dd8cc533da.png](https://i-blog.csdnimg.cn/blog_migrate/df0dab0dc58ced5d3cd4d30f89f2a4c9.jpeg)
然后若执行结束就返回用户态。
其实用汇编码查看更加简明:
![8a075eb80afe9f6400f500ed911d5f70.png](https://i-blog.csdnimg.cn/blog_migrate/42490cdff3bd778d9569c2066d21edb0.jpeg)
完整的过程可以看entry_32.S文件中的这部分内容:
/*
* 32-bit legacy system call entry.
*
* 32-bit x86 Linux system calls traditionally used the INT $0x80
* instruction. INT $0x//80 lands here.
*
* This entry point can be used by any 32-bit perform system calls.
* Instances of INT $0x80 can be found inline in various programs and
* libraries. It is also used by the vDSO's __kernel_vsyscall
* fallback for hardware that doesn't support a faster entry method.
* Restarted 32-bit system calls also fall back to INT $0x80
* regardless of what instruction was originally used to do the system
* call. (64-bit programs can use INT $0x80 as well, but they can
* only run on 64-bit kernels and therefore land in
* entry_INT80_compat.)
*
* This is considered a slow path. It is not used by most libc
* implementations on modern hardware except during process startup.
*
* Arguments:
* eax system call number
* ebx arg1
* ecx arg2
* edx arg3
* esi arg4
* edi arg5
* ebp arg6
*/
//进入
ENTRY(entry_INT80_32)
ASM_CLAC
pushl %eax /* pt_regs->orig_ax */
SAVE_ALL pt_regs_ax=$-ENOSYS switch_stacks=1 /* save rest */
/*
* User mode is traced as though IRQs are on, and the interrupt gate
* turned them off.
*/
TRACE_IRQS_OFF
movl %esp, %eax
call do_int80_syscall_32
.Lsyscall_32_done:
STACKLEAK_ERASE
restore_all:
TRACE_IRQS_IRET
SWITCH_TO_ENTRY_STACK
.Lrestore_all_notrace:
CHECK_AND_APPLY_ESPFIX
.Lrestore_nocheck:
/* Switch back to user CR3 */
SWITCH_TO_USER_CR3 scratch_reg=%eax
BUG_IF_WRONG_CR3
/* Restore user state */
RESTORE_REGS pop=4 # skip orig_eax/error_code
.Lirq_return:
/*
* ARCH_HAS_MEMBARRIER_SYNC_CORE rely on IRET core serialization
* when returning from IPI handler and when returning from
* scheduler to user-space.
*/
INTERRUPT_RETURN
restore_all_kernel:
TRACE_IRQS_IRET
PARANOID_EXIT_TO_KERNEL_MODE
BUG_IF_WRONG_CR3
RESTORE_REGS 4
jmp .Lirq_return
.section .fixup, "ax"
ENTRY(iret_exc )
pushl $0 # no error code
pushl $do_iret_error
#ifdef CONFIG_DEBUG_ENTRY
/*
* The stack-frame here is the one that iret faulted on, so its a
* return-to-user frame. We are on kernel-cr3 because we come here from
* the fixup code. This confuses the CR3 checker, so switch to user-cr3
* as the checker expects it.
*/
pushl %eax
SWITCH_TO_USER_CR3 scratch_reg=%eax
popl %eax
#endif
jmp common_exception
.previous
_ASM_EXTABLE(.Lirq_return, iret_exc)
ENDPROC(entry_INT80_32)
从上面可以看出系统调用利用INT80中断,进行现场保存,并进入内核态进行系统调用操作,结束以后恢复现场并返回用户态。
四、总结
1)调用是一个用户态->内核态->用户态的过程。当调用一个系统调用时,CPU从用户态切换到内核态并开始执行一个system_call和系统调用内核函数。
2)在Linux中,系统调用采用INT80软中断的方式进行触发,内核为每个系统调用分配一个系统调用号,用户态进程必须明确指明系统调用号,需要使用EAX寄存器来传递。
3)系统调用不同于函数调用,系统调用工作在内核态,需要的参数,不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。