用户态内嵌汇编触发系统调用
配置ARM64环境:
sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev build-essential git bison flex libssl-dev
sudo apt install gdb-multiarch
# 为arm64配置内核编译选项:
make defconfig ARCH=arm64
make menuconfig ARCH=arm64
触发系统调用的方法
以 time
系统调用为例,分别使用C库函数和 int $0x80/syscall
和 svc
指令汇编代码触发系统调用。
C库函数 time_t time(time_t *seconds)
返回自 1970-01-01 00:00:00
(UTC国际时区)起经过的时间,以秒为单位。如果 seconds
不为空,则返回值也存储在变量 seconds
中。
C库函数 struct tm *localtime(const time_t *timer)
使用 timer
的值来填充 stuct tm
结构体。timer
的值被分解到 stuct tm
结构体中,并用本地时区表示。
使用C库函数触发time系统调用
#include <stdio.h>
#include <time.h>
int main()
{
time_t tt;
struct tm *t;
tt = time (NULL);
t = localtime(&tt);
printf("time: %d/%d/%d %d:%d:%d\n",
t一>tm_year + 1900,
t一>tm_mon,
t一>tm_mday,t->tm_hour,t一>tm_min,t一>tm_sec);
return 0;
}
gcc -o time time.c -static
objdump -S time > time64.S
64位机器上编出32位代码需加-m32
sudo apt-get install gcc-multilib
gcc -o time time.c -static -m32
objdump -S time > time32.S
系统调用的参数传递和系统调用号
系统调用表
Linux
源代码中的 arch/x86/entry/syscalls/syscall_32.tbl
和arch/x86/entry/syscalls/syscall_64.tbl
分别定义了32位 x86
和 x86-64
的系统调用内核处理函数,它们最终通过脚本转换按照系统调用号依次存入ia32_sys_call_table
和 sys_call_table
数组中。而系统调用内核处理函数则是由系统调用入口 entry_INT80_32
和 entry_SYSCALL_64
分别调用的do_int80_syscall_32
和 do_syscall_64
来调用执行。do_int80_syscall_32
和 do_syscall_64
的代码如 arch/x86/entry/common.c
。
在 start_kernel
函数开始执行之前是用汇编代码初始化 CPU
,其中非常重要的就是将异常向量表的基地址(vectors
)配置到 VBAR_EL1
寄存器中,从arch\arm64\kernel\head.S
中可以找到代码,这段代码配置了不仅配置了异常向量表,还配置了0号进程的内核堆栈和进程描述符。
异常向量表分为4组,每组有4个向量入口地址,分别处理4种不同类型的异常。每个向量入口空间128字节,也就是说,在这个异常向量空间里可以放入32条指令(每条指令4字节)。举个例子,如果用户态程序执行了 svc
指令,这时CPU自动把 VBAR_EL1
寄存器的值(vectors
),和第3组Synchronous
的偏移量 0x400
相加,即 vectors + 0x400
,得出该异常向量空间的入口地址,然后跳转到那里,执行里面的第一条指令。
ARM64处理保存现场和恢复现场
保存现场
在Linux系统中系统调用发生时,CPU会把当前程序指针寄存器PC放入ELR_EL1
寄存器里,把 PSTATE
放入SPSR_EL1
寄存器里,同时Linux系统从用户态切换到内核态(从EL0切换到EL1),这时SP指的是SP_EL1
寄存器,用户态堆栈的栈顶地址依然保存在 SP_EL0
寄存器中。也就是说异常(这里是指系统调用)发生时CPU的关键状态sp
、pc
和 pstate
分别保存在SP_EL0
、ELR_EL1
和 SPSR_EL1
寄存器中。保存现场的主要工作如上代码所示,是保存 x0-x30
及 sp
、pc
和 pstate
,这和 struct pt_regs
数据结构的起始部分正好一一对应。
恢复现场
kernel_exit 0
负责恢复现场的代码和 kernel_entry 0
负责保存现场的代码相对应
内核堆栈pt_regs等
pt_regs
的数据结构如下: