linux内核编译及添加系统调用(hdu)_举例跟踪分析Linux内核5.0系统调用处理过程

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
编译成功,bzImage准备就绪

二、制作根文件系统

(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
Makefile的内容

{

补充:自己做rootfs.img,比如拿一个hello.c文件,如下图所示,

a9ef17926184fbc6bd6da12033b8f041.png

然后在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

}

3、qemu启动,测试系统是否可以正常运行

qemu -kernel linux-5.0.2/arch/x86/boot/bzImage -initrd rootfs.img

7c8124d12ba379767e2e59c2a7248666.png
成功运行的结果

若出现上图,则说明你的内核已经配置编译完成了。

三、183号系统调用跟踪分析。

1、做好调试跟踪的准备

那么183号系统调用是什么呢?我们可以从linux-5.0.2中找到。

linux-5.0.2/arch/x86/entry/syscalls/syscall_32.tbl(PS:此处说明下,我们的实验都是32位操作的)

674c843bcb04cf664e87b36f82aaba4b.png
(ps:其实看到这个我是拒绝的,为什么不是getPid)

接下来参照menu/test.c文件布置我们自己的系统调用。

dce22c58525c91471214b05cf1d06725.png
系统调用time函数

cfb753f10cd02dbf5bea02d6b62c972e.png

我在此处简单实现了一下:

20474fd4349940b4f4d2857b367658e6.png
获取当前系统操作地址

47322a28f0b9146e2bd85f692b4c49fa.png

然后对main函数中稍作修改:

fb3001ce95c41bb5f9b84467354e5f5b.png

最后对menu文件夹重新进行编译:

make rootfs

2、结果展示

2bad17d3a95539b29cc9bd8ac123c591.png

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

file vmlinux

target remote:1234

b sys_getcwd

接下来就是直接c。

45d5bbb28082ab44d2fee531972d2cb1.png

接下来对已运行的qemu中的server进行操作。

getcwd

3eeafa7951f35b6be25f9736246644b4.png

然后返回gdb查看,发现断点位置。

dd5b6069480e1149219809a44e1e3c54.png

对此处断点进行汇编语言分析,

disass

dafed84b7ecdc88e9899d9a8cb092002.png

1855d49da48a015e18d3da22a59e0d67.png

很明显可以看出开头几行是一个压栈操作,之后又申请了一块内存,然后拷贝给用户态,再释放内存,相当于一次现场保护,开始进行getcwd的系统调用。

我们继续,(gdb)n

b1da25aedf90c6fdc8449f1848f5c6e4.png

由函数名称可以看出已经开始系统调用,相应终端请求。如果进行disass的话可以发现:

cc59033f5bae8f878d484ba2b030e0dd.png

其中存在系统调用号(0x7b=183)的传递。

d8abca5bd8c55f0bd2beecece062838b.png

系统调用的实现并返回。

当进入entry_32.S之后,可以看到最初传进来的参数已经保存在寄存器里面了:

24848df8d71c82f767658c0ec4d221c4.png

189403c92f56e7f975cc3ed7fcc76722.png

其实可以直接看common.c的源码进行整个系统调用的分析:

8271ac36079c2d9faf420ebc6065407a.png

开始一次int80中断,进行系统调用,从用户态进入内核态,执行中断请求。

在do_syscall_32_irqs_on函数最后会调用syscall_return_slowpath。

34a673ceb779c8a28a3032dd8cc533da.png

然后若执行结束就返回用户态。

其实用汇编码查看更加简明:

8a075eb80afe9f6400f500ed911d5f70.png

完整的过程可以看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)系统调用不同于函数调用,系统调用工作在内核态,需要的参数,不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值