概览
应用调用系统调用时,执行会经历多个阶段。为了减少样板代码,系统调用专用逻辑是使用古筝工具生成的。在深入了解上述每个阶段的工作原理之前,最好先了解此代码的生成方式。
使用古筝生成代码
Fuchsia 系统调用在 //zircon/vdso 的 FIDL 文件中声明。
zx_channel_create
的声明示例:
library zx;
@transport("Syscall")
protocol channel {
channel_create(struct {
options uint32;
}) -> (resource struct {
status status;
out0 handle;
out1 handle;
});
};
构建内核时,fidlc(FIDL 前端)会获取这些 FIDL 文件并生成 FIDL 中间表示 (IR) JSON 文件。IR 文件在 //out/default/gen/zircon/vdso/zx.fidl.json
中生成。
古筝会读取此 IR 文件,并生成源文件,以便通过每个系统调用的输入和输出来调用 C++ 宏。这些宏是按架构定义的,以便 zither 输出与架构无关的源代码。
古筝输出示例:
KERNEL_SYSCALL(channel_create, zx_status_t, /* no attributes */, 3,
(options, out0, out1), (
uint32_t options,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out0,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out1))
x86 实现示例:
#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) \
m_syscall zx_##name, ZX_SYS_##name, nargs, 1
.macro m_syscall name, num, nargs, public
syscall_entry_begin \name
.cfi_same_value %r12
.cfi_same_value %r13
.if \nargs <= 3
zircon_syscall \num, \name, \name
ret
.endif
.macro zircon_syscall num, name, caller
mov $\num, %eax
syscall
// This symbol at the return address identifies this as an approved call site.
.hidden CODE_SYSRET_\name\()_VIA_\caller
CODE_SYSRET_\name\()_VIA_\caller\():
.endm
在系统调用的各个阶段,都可以找到这种将古筝生成的源代码与 C++ 宏和汇编例程对接的模式。
计划
若要使用系统调用,您应添加来自 Fuchsia SDK 的 <zircon/syscalls.h>
头文件,该头文件由 zither 生成。虽然头文件在编译期间可供程序使用,但实现仅在运行时在 vDSO 内可用。
视频 DSO
虚拟动态共享对象 (vDSO) 是一个 ELF 文件,其中包含每个系统调用的用户空间实现。vDSO 中的汇编例程主要由古筝生成,但都采用以下结构:
- 将用户提供的参数保存到架构特定的寄存器中
- 将系统调用编号存储到特定于架构的寄存器(x86 为
%eax
) - 将上下文切换到内核(对于 x86,为
syscall
)
可通过以下命令查看 vDSO 中的例程:
$ objdump -d `find out/default.zircon -name libzircon.so.debug` | less
zx_channel_create
的 x86 实现:
0000000000007a70 <_zx_channel_create>:
7a70: b8 03 00 00 00 mov $0x3,%eax
7a75: 0f 05 syscall
0000000000007a77 <CODE_SYSRET_zx_channel_create_VIA_zx_channel_create>:
7a77: c3 retq
构建内核时,vDSO 以内核中的字符数组的形式关联,然后加载到 vmo 中。在启动过程中,系统会将一些常量写入 vDSO,以便用户空间程序无需向内核执行跃点即可查询这些常量。
在调用程序的入口点之前,ld.so
会将 vDSO 映射到内存中。为防止 return-to-libc 攻击,系统会将 vDSO 置于进程地址空间中的随机位置,并将基地址提供给特定寄存器中的第一个线程。
vDSO 会在 libc 提供的入口点中动态链接到用户的程序
Syscall 处理程序
为了在特权模式下接收系统调用,内核会在启动时注册系统调用处理程序。调用此例程时,系统调用编号用于编入调度例程映射的索引。
x86 实现://zircon/kernel/arch/x86
write_msr(X86_MSR_IA32_LSTAR, (uint64_t)&x86_syscall);
FUNCTION_LABEL(x86_syscall)
leaq .Lcall_wrapper_table(%rip), %r11
movq (%r11,%rax,8), %r11
jmp *%r11
END_FUNCTION(x86_syscall)
调度例程负责将系统调用参数移到相应的寄存器,以作为 C 函数参数进行选择,以及调用封装容器函数。这些例程是在构建时使用 syscall_dispatch
宏和由古筝生成的源文件生成的。
#define KERNEL_SYSCALL(name, type, attrs, nargs, arglist, prototype) \
syscall_dispatch nargs, name
KERNEL_SYSCALL(channel_create, zx_status_t, /* no attributes */, 3,
(options, out0, out1), (
uint32_t options,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out0,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out1))
.macro syscall_dispatch nargs, syscall
LOCAL_FUNCTION(.Lcall_\syscall\())
// move args around
pre_\nargs\()_args
// calls wrapper
call wrapper_\syscall
// cleans up
post_\nargs\()_args
END_FUNCTION(.Lcall_\syscall\())
.endm
Syscall 封装容器
系统调用封装容器函数由 zither 生成,负责调用系统调用实现,然后将所有句柄复制回客户端。这些封装容器的命名惯例为 wrapper_<syscall>
。
syscall_result wrapper_channel_create(uint32_t options, zx_handle_t* out0, zx_handle_t* out1, uint64_t pc) {
return do_syscall(ZX_SYS_channel_create, pc, &VDso::ValidSyscallPC::channel_create, [&](ProcessDispatcher* current_process) -> uint64_t {
zx_handle_t out_handle_out0;
zx_handle_t out_handle_out1;
auto result = sys_channel_create(options, &out_handle_out0, &out_handle_out1);
if (result != ZX_OK)
return result;
result = make_user_out_ptr(SafeSyscallArgument<zx_handle_t*>::Sanitize(out0))
.copy_to_user(out_handle_out0);
if (result != ZX_OK) {
// We should never fail to copy out a handle to userspace. If we do, a
// handle will be leaked, so throw a SignalPolicyException.
Thread::Current::SignalPolicyException(ZX_EXCP_POLICY_CODE_HANDLE_LEAK, 0u);
}
result = make_user_out_ptr(SafeSyscallArgument<zx_handle_t*>::Sanitize(out1))
.copy_to_user(out_handle_out1);
if (result != ZX_OK) {
// We should never fail to copy out a handle to userspace. If we do, a
// handle will be leaked, so throw a SignalPolicyException.
Thread::Current::SignalPolicyException(ZX_EXCP_POLICY_CODE_HANDLE_LEAK, 0u);
}
return result;
});
}
Syscall 实现
系统调用实现是一个采用命名惯例 sys_<syscall>
的手写函数。这些函数包含系统调用的核心逻辑,并且与架构无关。
//zircon/kernel/lib/syscalls/channel.cc
zx_status_t sys_channel_create(...) {
...
}
附录
用于创建新系统调用的提交示例。