1. 简介
这篇文章主要介绍系统调用在arm64下的实现及使用原理,考虑到目前bpf在系统调试和调优工作中被大量使用,在文章的最后也简单介绍一下系统调用相关的bpf工具。
系统调用在每个平台的实现方式各不相同,x86通过int 0x80实现;arm通过指令SVC实现(SVC属于arm64下的同步异常)。
在arm64架构下,异常分为同步异常和异步异常。
同步异常包括如下:
- SVC,HVC,SMC
- MMU中止(比如权限错误,对齐错误等)
- 栈指针或指令地址没有对齐
- 未定义指令
异步异常包括如下:
- IRQ
- FIQ
- SError
2. 系统调用的定义
以openat为例讲一下系统调用的定义。
openat系统调用主要通过如下几个地方定义,
include/linux/syscalls.h
fs/open.c
编译器会将SYSCALL_DEFINE4宏展开,具体细节就不详述了,最终通过do_sys_open函数实现openat系统调用功能。
有了系统调用处理函数的定义后,还需要在系统调用表中维护一套系统调用号和处理函数的映射关系,arm64的系统调用表sys_call_table定义如下:
arch/arm64/kernel/sys.c
具体的映射定义在:
include/uapi/asm-generic/unistd.h
openat系统调用号为56。
3. 系统调用的使用
当应用程序调用libc库的open函数,libc库调用SVC指令进入异常模式,在arm64架构下,通过异常向量表找到相应的入口函数,然后通过系统调用号找到处理函数并执行它,最后返回。
举个例子:
open_test.c
//静态编译
gcc open_test.c -o open_test --static
//反汇编
objdump -D open_test > open_arm_asm
open_arm_asm
可以看到将寄存器x8设置为系统调用号0x38 (openat),
然后调用了svc进入异常处理。
进入异常模式后,内核根据异常类型(同步异常)及当前所处的ELx, 调用相应的异常处理函数,这里是el0_sync。
arch/arm64/kernel/entry.S
667,668行:通过读取esr_el1得到产生异常的原因,保存到寄存器x24
669行:判断x24中保存的异常原因是否为svc
670行:上一行如果判断相等,调用el0_svc
914行:读入syscall table的指针
915行:将系统调用号(w8)保存到wscno中,上面的讲解中libc库将系统调用号写入了x8寄存器
928行:以系统调用号为索引,得到syscall table表中相应的函数地址,这里就是sys_openat
929行:调用sys_openat
到这里,系统调用实现原理大体就讲完了,接下来我们介绍一下系统调用相关的bpf工具。
4. 系统调用相关bpf工具
可以使用BPF对Linux内核进行跟踪,收集我们想要的内核数据,从而对Linux中的程序进行分析和调试。
BPF的主要优点是几乎可以访问Linux内核和应用程序的任何信息,因为BPF可以在内核直接处理数据,和传统工具相比,不需要将数据从内核态搬运到用户态,所以BPF执行效率非常高,对系统性能影响很小。
下面这张图来自Brendan Gregg的新书BPF Performance Tools,其中黑体字标明的是BCC/bpftrace先前存在的工具,红色是为『BPF Performance Tools』这本新书所创建的新工具,我用红框所框起来的几个工具是本文所讲的系统调用相关工具。
下面简要列一下这几个工具用到的tracepoint:
execsnoop:
-> tracepoint:syscalls:sys_enter_execve
syscount:
-> tracepoint:raw_syscalls:sys_enter
killsnoop:
-> tracepoint:syscalls:sys_enter_kill
-> tracepoint:syscalls:sys_exit_kill
opensnoop:
-> tracepoint:syscalls:sys_enter_openat
-> tracepoint:syscalls:sys_exit_openat
statsnoop:
-> tracepoint:syscalls:sys_enter_statfs
-> tracepoint:syscalls:sys_enter_statx
-> tracepoint:syscalls:sys_exit_statfs
-> tracepoint:syscalls:sys_exit_statx
syncsnoop:
-> tracepoint:syscalls:sys_enter_sync
-> tracepoint:syscalls:sys_enter_syncfs
-> tracepoint:syscalls:sys_enter_fsync
-> tracepoint:syscalls:sys_enter_fdatasync
-> tracepoint:syscalls:sys_enter_sync_file_range
-> tracepoint:syscalls:sys_enter_msync
简要介绍一下opensnoop工具。
该工具跟踪文件打开事件,对发现系统中使用的数据文件、日志文件以及配置文件来说十分有用。该工具还可以检测由于快速打开大量文件导致的性能问题,也可以帮助调试找不到文件导致的问题。
opensnoop.bt
#!/usr/bin/bpftrace
/*
* opensnoop Trace open() syscalls.
* For Linux, uses bpftrace and eBPF.
*
* Also a basic example of bpftrace.
*
* USAGE: opensnoop.bt
*
* This is a bpftrace version of the bcc tool of the same name.
*
* Copyright 2018 Netflix, Inc.
* Licensed under the Apache License, Version 2.0 (the "License")
*
* 08-Sep-2018 Brendan Gregg Created this.
*/
BEGIN
{
printf("Tracing open syscalls... Hit Ctrl-C to end.\n");
printf("%-6s %-16s %4s %3s %s\n", "PID", "COMM", "FD", "ERR", "PATH");
}
tracepoint:syscalls:sys_enter_openat
{
@filename[tid] = args->filename;
}
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
$ret = args->ret;
$fd = $ret > 0 ? $ret : -1;
$errno = $ret > 0 ? 0 : - $ret;
printf("%-6d %-16s %4d %3d %s\n", pid, comm, $fd, $errno,
str(@filename[tid]));
delete(@filename[tid]);
}
END
{
clear(@filename);
}
这个程序跟踪了openat系统调用,同时从返回值中获取了文件描述符信息或者错误代码。