システムコール - kentakozuka/yetos GitHub Wiki
低い権限で動くプログラムから、高い権限でしか動作できないプログラムを呼び出す仕組み
実現方法
- syscall命令
- 割り込みを使用した方法
- io_uring
- OSの関数を直接呼び出す
- AMDのx86系プロセッサーでシステムコール用に用意された命令
簡単なsyscall命令の例
EAXレジスタに固定値を設定し、RCXをR10にコピーしたあと、syscall
を実行する。
bits 64
section .text
global SyscallLogString
SyscallLogString:
mov eax, 0x800000000
mov r10, rcx
syscall
ret
- 呼び出し規約(calling conventions)とは、何か処理(手続きや関数)を呼び出すときに、引数と戻り値をどうやってやりとりするかを定めるもの
- 呼ぶ側(caller)と呼ばれる側(callee)それぞれが、CPUのレジスタやスタックをどのように利用するかを規定する
- 基本的にはCPUとOSの組み合わせで定義されるが、言語やコンパイラでも異なることがある
- Windows以外の多くのOS(Linux、BSD系、macOS)の呼び出し規約はこれ
- 最初の6つの整数またはポインタの引数は汎用レジスタ(RDI, RSI, RDX, RCX, R8, R9)で渡される
- 最初の8つの浮動小数点数はベクタレジスタ(XMM0~XMM7)で渡される
- 汎用レジスタとベクタレジスタは独立して使われるので、下記の関数fではaがRDI、bがXMM0で渡される
int f(int a, double b);
レジスタに収まらなかった引数はスタックで渡される。 スタックには引数の後ろの方から積んでいく。 スタックに積むとメモリアドレスは小さくなっていくので、メモリレイアウトとしては、引数の順番に並ぶことになる。 可変長引数の場合は少し特殊で、ベクタレジスタを使用した個数がALレジスタに渡される。 可変長引数のcalleeの例として、va_argを使った関数のアセンブリ出力を見てみたところ、まずALレジスタの個数だけベクタレジスタをスタックの専用領域に保存し、va_argで浮動小数点数を取り出す時にそこから取得する、ということをしていた。
va_argで取り出す型によって取り出す場所が違うということは、入れ替えできるのだろうか? ということでやってみると、
printf("%d %d %.2f %.2f\n", 1, 2, 3.0, 4.0);
printf("%d %d %.2f %.2f\n", 3.0, 4.0, 1, 2);
以下のように同じ結果になった。 ※ ただし後者はコンパイラには順番が違うと怒られる。
1 2 3.00 4.00
1 2 3.00 4.00
なお、プロトタイプ宣言していない関数を呼び出す場合、その関数が可変長引数を取る関数かどうかは不明なので、ALレジスタがセットされる。 すなわち、きちんとプロトタイプ宣言しないと無駄が生じる可能性がある。
mermaid
sequenceDiagram
participant app as App
participant newlib as App(Newlib)
participant cpu as CPU
participant os as OS
os->>cpu: Register entry point
app->>newlib: Call wrapper function
newlib->>app: Call custom function
app->>cpu: Call syscall instruction<br/>- syscall number
cpu->>os: Execute entry point
Note over os: Execute function!
- プロセスを作る (コピーする)
- 現プロセスで指定のプログラムを実行する
- 変種: exec{v,l}p?e? (引数の渡し方, 微妙な意味の 違い)
- 以下, 総称して exec と呼ぶ (実際には exec という名前 の関数はない)
- 現プロセスを終了する
- exit
- 子プロセスの終了待ち + 処理
- 変種: wait, wait3, wait4