深入理解系统调用
Posted yongjason
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解系统调用相关的知识,希望对你有一定的参考价值。
一、实验要求
找一个系统调用,系统调用号为学号最后2位相同的系统调用
通过汇编指令触发该系统调用
通过gdb跟踪该系统调用的内核处理过程
重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、实验环境搭建
2.1安装开发工具
1 sudo apt install build-essential
2 sudo apt install qemu # install QEMU
3 sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
2.2下载内核源码
1 sudo apt install axel
2 axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
3 xz -d linux-5.4.34.tar.xz
4 tar -xvf linux-5.4.34.tar
5 cd linux-5.4.34
2.3配置内核
1 make defcon?g # Default con?guration is based on ‘x86_64_defcon?g‘ 2 make menucon?g 3 Kernel hacking ---> 4 Compile-time checks and compiler options ---> 5 [*] Compile the kernel with debug info 6 [*] Provide GDB scripts for kernel debugging 7 [*] Kernel debugging 8 Processor type and features ----> 9 [] Randomize the address of the kernel image (KASLR)
2.4编译运行内核
1 make -j$(nproc) # nproc gives the number of CPU cores/threads available 2 kernel panic 3 qemu-system-x86_64 -kernel arch/x86/boot/bzImage
2.5制作根文件系统
1 首先从https://www.busybox.net下载 busybox源代码解压,解压完成后,跟内核一样先配置编译,并安装。 2 axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2 3 tar -jxvf busybox-1.31.1.tar.bz2 4 cd busybox-1.31.1
1 make menucon?g #若出现打不开menuconfig,重启虚拟机即可 2 #记得要编译成静态链接,不?动态链接库。 3 Settings ---> 4 [*] Build static binary (no shared libs) 5 #然后编译安装,默认会安装到源码?录下的 _install ?录中。 6 make -j$(nproc) && make install
制作根文件系统镜像
1 mkdir rootfs 2 cd rootfs 3 cp ../busybox-1.31.1/_install/* ./ -rf 4 mkdir dev proc sys home 5 sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
准备init脚本文件放在根文件系统跟目录下(rootfs/init),添加如下内容到init文件。
1 #!/bin/sh 2 mount -t proc none /proc 3 mount -t sysfs none /sys 4 echo "Wellcome MengningOS!" 5 echo "--------------------" 6 cd home 7 /bin/sh
给init脚本添加可执?权限
1 chmod +x init
1 #打包成内存根?件系统镜像 2 ?nd . -print0 | cpio --null -ov --format=newc | gzip -9 > ../ rootfs.cpio.gz 3 #测试挂载根?件系统,看内核启动完成后是否执?init脚本 4 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
三、系统调用
所有的操作系统在其内核里都有一些内建的函数,这些函数可以用来完成一些系统级别的功能。Linux系统使用的这样的函数叫做“系统调用”,英文是systemcall。这些函数代表了从用户空间到内核空间的一种转换,例如在用户空间调用open函数,则会在内核空间调用sys_open。一个已经安装的系统的支持的所有的系统调用可以在/usr/include/bits/syscall.h文件里面看到。
系统调用是内核向用户进程提供服务的唯一方法,应用程序调用操作系统提供的功能模块(函数)。用户程序通过系统调用从用户态(user mode)切换到核心态(kernel mode ),从而可以访问相应的资源。这样做的好处是:为用户空间提供了一种硬件的抽象接口,使编程更加容易。有利于系统安全。有利于每个进程度运行在虚拟系统中,接口统一有利于移植。
由于学号后两位是52,系统调用号为52的是getpeername
#define DEBUG 0
socklen_t *_RESTRICT address_len, nwio_tcpconf_t *tcpconfp);
int getpeername(int socket, struct sockaddr *_RESTRICT address,
socklen_t *_RESTRICT address_len)
{
int r;
nwio_tcpconf_t tcpconf;
r= ioctl(socket, NWIOGTCPCONF, &tcpconf);
if (r != -1 |e68a84e799bee5baa631333363393630| errno != ENOTTY)
{
if (r == -1)
{
/* Bad file descriptor */
return -1;
}
return _tcp_getpeername(socket, address, address_len,
&tcpconf);
}
#if DEBUG
fprintf(stderr, "getpeername: not implemented for fd %d ", socket);
#endif
errno= ENOSYS;
return -1;
}
static int _tcp_getpeername(int socket, struct sockaddr *_RESTRICT address,
socklen_t *_RESTRICT address_len, nwio_tcpconf_t *tcpconfp)
{
socklen_t len;
struct sockaddr_in sin;
if (tcpconfp->nwtc_remaddr == 0 ||
tcpconfp->nwtc_remport == 0)
{
errno= ENOTCONN;
return -1;
}
memset(&sin, ‘ ‘, sizeof(sin));
sin.sin_family= AF_INET;
sin.sin_addr.s_addr= tcpconfp->nwtc_remaddr;
sin.sin_port= tcpconfp->nwtc_remport;
len= *address_len;
if (len > sizeof(sin))
len= sizeof(sin);
memcpy(address, &sin, len);
*address_len= len;
return 0;
}
int FAR* namelen);
name:接收端地址的名字结构。
namelen:一个指向名字结构的指针。
getpeername()函数用于从端口s中获取与它捆绑的端口名,并把它存放在sockaddr类型的name结构中。它适用于数据报或流类套接口。
若无错误发生,getpeername()返回0。否则的话,返回SOCKET_ERROR,应用程序可通过WSAGetLastError()来获取相应的错误代码。
WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
WSAEFAULT:namelen参数不够大。
WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
WSAENOTCONN 套接口未连接。
WSAENOTSOCK:描述字不是一个套接口。
int main() { asm volatile( "movl $0x34,%eax " //使?EAX传递系统调?号52 "syscall " //触发系统调? ); return 0; }
四、通过gdb跟踪该系统调用的内核处理过程
通过如下命令重新启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S –s
另开一个终端,设置gdb连接qemu的端口
cd linux-5.4.34/ gdb vmlinux (gdb) target remote:1234 (gdb) b __x64_sys_getpeername
得到对方的地址
struct sockaddr_in sa; int len = sizeof(sa); if(!getpeername(sockfd, (struct sockaddr *)&sa, &len)) { printf( "对方IP:%s ", inet_ntoa(sa.sin_addr)); printf( "对方PORT:%d ", ntohs(sa.sin_port)); }
五、系统调用总结
程序执行系统调用大致可归结为以下几个步骤:
1、程序调用libc 库的封装函数。
2、调用软中断int 0x80 进入内核。
3、在内核中首先执行system_call 函数(首先将系统调用号(eax)和可以用到的所有CPU寄存器保存到相应的堆栈中(由SAVE_ALL完成),接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程。
4、执行该服务例程。
5、执行完毕后,转入ret_from_sys_call 例程,从系统调用返回
系统调用由内核分配的一个编号唯一标识(系统调用号)。
所有的系统调用都由一处中枢代码处理,根据调用编号和一个静态表,将调用分派到具体的函数。传递的参数也是由中枢代码处理,这样参数的传递独立于实际的系统调用。从用户态到内核态,以及调用分派和参数传递,都是由汇编语言代码实现的。
为容许用户态和内核态之间的切换,用户进程必须通过一条专用的机器指令,引起处理器/内核对该进程的关注,这需要 C 标准库的协助。内核也必须提供一个例程,来满足切换请求并执行相关操作。该例程不能在用户空间中实现,因为其中需要执行普通应用程序不允许执行的命令。
以上是关于深入理解系统调用的主要内容,如果未能解决你的问题,请参考以下文章