Fuchsia 系統呼叫的生命週期

總覽

應用程式叫用系統呼叫時,執行作業會經過多個階段。為減少樣板程式碼,系統會使用 zither 工具生成系統呼叫專屬邏輯。在深入瞭解上述各階段的運作方式之前,建議先瞭解如何產生這段程式碼。

使用者模式和核心模式區塊的圖表,以及特定程式碼區塊的建立方式。

使用 zither 生成程式碼

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。

Zither 會讀取這個 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

在整個系統呼叫階段,都可以看到這種將 Zither 產生的來源與 C++ 巨集和組語常式介接的模式。

計畫

如要使用系統呼叫,請從 Fuchsia SDK 納入 <zircon/syscalls.h> 標頭,該標頭是由 Zither 產生。雖然標頭可在編譯期間供程式使用,但實作內容僅在 vDSO 內的執行階段提供。

vDSO

虛擬動態共用物件 (vDSO) 是 ELF 檔案,內含每個系統呼叫的使用者空間實作。vDSO 中的組語常式大多是由 Zither 產生,但都具有相同的結構:

  1. 將使用者提供的引數儲存至架構專屬的暫存器
  2. 將系統呼叫編號儲存至架構專用暫存器 (x86 的 %eax)
  3. 將環境切換至核心 (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 提供的進入點中,動態連結至使用者的程式

程序載入和 vDSO 的流程圖

系統呼叫處理常式

為了在特殊權限模式下接收系統呼叫,核心會在啟動時註冊系統呼叫處理常式。呼叫這個常式時,系統會使用系統呼叫編號,為分派常式對應建立索引。

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 巨集和 Zither 產生的來源檔案產生。

#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

系統呼叫包裝函式

系統呼叫包裝函式是由 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;
    });
}

系統呼叫實作

系統呼叫實作項目是手動編寫的函式,命名慣例為 sys_<syscall>。這些函式包含系統呼叫的核心邏輯,且與架構無關。

//zircon/kernel/lib/syscalls/channel.rs

zx_status_t sys_channel_create(...) {
   ...
}

附錄

範例提交,用於建立新的系統呼叫。