Document

プログラミングの基礎 第2回
systemcall
workshop 資料作成委員会
本ワークショップの目標
 システムコールの仕組みを理解する
 カーネルモードとユーザモード
 ソフトウェア割り込み
 演習
 実際にシステムコールを直接呼んでみる
 システムコールを使ったプログラミング
table of contents
1.
2.
3.
4.
5.
システムコール概要
カーネルモードユーザーモード
ソフトウェア割り込み
システムコール実例
演習:
プログラミングとは?
 コンピュータのデバイスを操作する命令手順
を書くこと
Hello!
メモリ
プログラミング
ディスプレイに
出力する命令手順
ディスプレイ
デバイス
OSはすべてのデバイスを管理する
 OS上でプログラミングするには?
 OSに対してデバイスを操作する命令を発行
OS
画面に文字を
出力する
プログラム
プログラム例
 画面に”Hello!”を出力するprintf
プログラミング
出力命令を発行
Hello!
write()
実際に制御
出力関数(OSの機能)
ディスプレイ
デバイス
書いたプログラムを動かす!
ソースコード
hello.c
実行プログラム
オブジェクトコード
コンパイル
hello.o
リンク
a.out
実行
Hello!
a.outの構成
 元のhello.oよりもサイズが大きくなる
a.out
Cランタイムオブジェクト
libCへの参照
hello.o
自分で書いた部分
実習:
a.outからCランタイムとlibcを削除する
前準備
 Hello, world! を作ってね!
 FreeBSD か Linux ホストでお願いします.
#include <stdio.h>
#define MESSAGE “Hello, world!¥n”
int main() {
printf(MESSAGE);
return(123);
}
Example program
ちなみに,この時点でコードサイズは…
11368 バイト!
これから…
 邪魔なリンクファイル (crt* Cランタイムファ
イル) を消そう
 そのかわり、Cランタイムがやってくれていたことは
全部自分でやらなければいけません。
 これにより、プログラムがどのように動く(動かされて
いる)のか知ることができます。
 いらないセクションを消そう
邪魔なリンクファイルを消そう!
 先ほどの説明で,crt* ファイルがコンパイル
時にリンクされていることがわかりました.
 リンクしないように,-nostartfiles をつけてみ
ましょう.
21:55 [0] skk@aries% gcc -v hello.c
Using built-in specs.
Configured with: FreeBSD/i386 system compiler
Thread model: posix
gcc version 3.4.4 [FreeBSD] 20050518
・・・
/usr/bin/ld -V -dynamic-linker /libexec/ld-elf.so.1
/usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o L/usr/lib /var/tmp//ccdlOU3m.o -lgcc -lc -lgcc
/usr/lib/crtend.o /usr/lib/crtn.o
GNU ld version 2.15 [FreeBSD] 2004-05-23
Supported emulations:
elf_i386_fbsd
21:55 [0] skk@aries% gcc -v -nostartfiles hello.c
Using built-in specs.
Configured with: FreeBSD/i386 system compiler
Thread model: posix
gcc version 3.4.4 [FreeBSD] 20050518
/usr/libexec/cc1 -quiet -v -D_LONGLONG hello.c -quiet dumpbase hello.c -auxbase hello –
・・・
elf_i386_fbsd
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting
to 0000000008048200
/usr/lib/libc.so: undefined reference to `environ'
/usr/lib/libc.so: undefined reference to `__progname'
warning: cannot find entry symbol _start; defaulting to
0000000008048200
 「_start というシンボルが見つからないー.仕方ない
から,.text セクションの先頭アドレスを開始アドレス
にしちゃうもんね.」という意味。
 _startはcrt1.o(Cランタイム)の中にある関数で、プログラ
ム実行時に最初に呼び出される。今回はcrt1.oを丸ごと消
したいので使えない。
 プログラムの開始アドレスを _start じゃなく,main
にしたい!
開始アドレスの変更
 ld コマンドの -e オプションで変更できます!
 -e の後ろには,シンボル名を指定します.
% gcc -c hello.c
% ld -e main -o a.out hello.o
hello.o(.text+0x25): In function `main':
: undefined reference to `printf'
printf() がない??
•
-lgcc -lc を削除したことに注目しましょう。



前者はgcc固有ライブラリ、後者はlibcライブラリをリンクするためのものです。
ですが今回はとっちゃいます。なぜでしょう?
ライブラリとは

ライブラリは、便利な関数を集めていつでも使えるようにしておいたファイル
です。

libcにはさまざまなC言語の標準関数が含まれています。



printf,もその一つなのです。
逆に言うと、Hello World!を表示するだけのプログラムでは、printfの機能
だけ使えればいいので、ライブラリ全てをリンクするのは無駄です。
そこで

printfに相当する機能を自分で作ります。
-> そうだ! write() だ!
CランタイムとLibCの役割
 Cランタイム
 C言語で書かれたプログラムを動作させる環境
USE
Cランタイム
 LibC
 C言語からOSの機能を抽象化して使いやすくする
LibCの役割
 OSの機能を抽象化してくれる!
printfは僕が持っ
てます
LibC
システムコール
OS
OSとのインタフェース
(窓口)です
Cライブラリとシステムコール
 Cライブラリはシステムコールのラッパーを提
供する
 より使いやすく可搬性を高めるため
プロセス
プロセス
プロセス
libc
ユーザモード
システムコール
カーネル
デバイス
普通はCライブラリの
中でシステムコール
を呼んでくれる
システムコール概要
システムコールとは?
 OS(OSのカーネル)の機能を呼び出すために
使用される機構のこと
 システムをカーネルに制御を移すための特別な命
令を実行し、カーネルの機能を呼ぶ
システムコールの実行
プロセス
OS資源の利用
(デバイス、メモリ空間など)
プロセスとかカーネルって?
 プロセス
 アプリケーションの実行単位
 カーネル
 アプリケーションが動作する実行環境を提供
プロセス
プロセス
プロセス
カーネル
デバイス
システムコールがあると何がうれしいの?
 システムのセキュリテが向上する
 カーネルが処理を実行する前に処理要求の正当
性を確認できる
 プログラミングが楽になる
 ハードウェアに関する低水準レイヤについて覚え
なくてよくなる
 プログラムの可搬性が向上
 カーネルが同じインタフェースを提供する限りにお
いて
プログラミングの基礎における意味
 プログラミングする上で、OSの存在を意識で
きるようになる
 このAPIを呼ぶと、カーネルに制御が移りOSの資
源を利用できる
プロセス
カーネル
制御の移り変わり
普通にプログラミングする
場合、システムコールを
直接呼ぶことはしません
システムコールの特徴
 CPUの実行権限を切り替える
 割り込みによる実行
 OSとアプリケーションの中間層
詳細は、この後に解説
カーネルモードとユーザーモード
概要
 カーネルモードとユーザモード
 セキュリティと安全性のため異なる特権状態で命
令を実行する
 異なるCPUのランレベルを利用する
 Linuxでは特権モードと非特権モードの2つを使い
分ける
カーネルモード
 あらゆるハードウェア資源にアクセス可能
 オペレーティングシステムの実行モード
プロセス
プロセス
プロセス
ユーザモード
システムコール
カーネルモード
カーネル
デバイス
ユーザモード
 ハードウェア資源へのアクセスを制限・監視下
でプログラムを実行
 通常のプログラムの実行モード
プロセス
プロセス
プロセス
ユーザモード
システムコール
カーネルモード
カーネル
デバイス
CPUのランレベル
 多くのCPUは2つ以上の実行モードを保有
 実行レベルを使い分けることで安全性、安定性を
向上させる
 Intel 80x86は4つの実行リング(特権の階層)を
持つ
ランレベル0をカーネルモード
それ以上をユーザモードに割り当てる
CPUランレベル移行
 コールゲートによるランレベルの移行
 コールゲートを解した呼び出しだけが許される
 特権レベルの低いコードセグメントから特権レベルの高い
コードセグメントの呼び出し
コールゲート
 OSがランレベルの移行をコントロールできる機構
 ゲートを経由しないと移行できない
 呼び出せる特権レベルのゲートは現動作レベル以下だけ
移行先
特権レベル3の
コールゲート
システムコールの本質
 CPUランレベルを切り替えて、プログラムがデ
バイスを操作できるようにする仕組み
プロセス1
ユーザモード
カーネルモード
デバイスを操作
できる領域
システムコール
システムコール
ハンドラ
ランモードの切り替え方法




ソフトウェア割り込み(システムコールが使う)
タイマ割り込み
デバイス割り込み
例外割り込み
プロセス1
プロセス1
プロセス1
ユーザモード
カーネルモード
システムコール
タイマ割り込み
システムコール
ハンドラ
スケジューラ
デバイス割り込み
割り込みハンドラ
ソフトウェア割り込み
概要
 システムコールはソフトウェア割り込みで行
 割り込みベクターは 0x80
 システムコールの引数は,すべてレジスタで渡さ
れる
 システムコールを実行する手順
1. レジスタに必要な値を設定
2. int 0x80 を実行する
システムコールが利用するレジスタ
システムコールの流れ
 Linuxの場合
1.
2.
3.
4.
5.
6.
7.
システムコール番号をeaxレジスタにセット
必要に応じてほかの引数もレジスタにセット
int 0x80ソフトウェア割り込みを発行
カーネルモードスタック上にレジスタ内容を退避
システムコールサービスルーチンを呼び出す
システムコールの実処理
ハンドラから抜ける
システムコールの流れ図
eax
ebx
ユーザモード
・・・
xyz()
・・・
38 (38: sys_xyzのシステムコール
番号とする)
"param1"
システムコール番号
や引数をセット
xyz(){
・・・
int 0x80
・・・
}
アプリケーション
libc標準ライブラリ
プログラムからの
のラッパールーチン
システムコール発行
カーネルモード
system_call:
・・・
sys_xyz()
・・・
iret
sys_xyz(){
・・・
}
システムコール
ハンドラ
システムコール
サービスルーチン
Cライブラリとシステムコール
 Cライブラリはシステムコールのラッパーを提
供する
 より使いやすく可搬性を高めるため
プロセス
プロセス
プロセス
libc
ユーザモード
システムコール
カーネル
デバイス
普通はCライブラリの
中でシステムコール
を呼んでくれる
システムコール実例
システムコール リスト
 Linux
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
__NR_exit
__NR_fork
__NR_read
__NR_write
__NR_open
__NR_close
__NR_waitpid
__NR_creat
__NR_link
__NR_unlink
__NR_execve
__NR_chdir
__NR_time
__NR_mknod
__NR_chmod
__NR_lchown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
システムコール リスト
 FreeBSD
#define SYS_syscall
#define SYS_exit
#define SYS_fork
#define SYS_read
#define SYS_write
#define SYS_open
#define SYS_close
#define SYS_wait4
/* 8 is old creat */
#define SYS_link
#define SYS_unlink
0
1
2
3
4
5
6
7
9
10
/* 11 is obsolete execv */
#define SYS_chdir
12
#define SYS_fchdir
13
#define SYS_mknod
14
#define SYS_chmod
15
#define SYS_chown
16
#define SYS_break
17
/* 18 is old getfsstat */
/* 19 is old lseek */
#define SYS_getpid
20
#define SYS_mount
21
strateでシステムコールをトレースする
 straceって?
 システムコールのトレースを行ってくれる!
 straceの仕組み
 システムコールのenterとexitをフックして引数と
返り値を出力する
 OSのデバック用インタフェースを用いる
Strace
ユーザモード
カーネルモード
フック
フック
システムコール
strace: emacs on linuxの一部出力
execve("/usr/bin/emacs", ["emacs"], [/* 21 vars */]) = 0
uname({sys="Linux", node="einstein", ...}) = 0
brk(0)
= 0x8424000
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0
x40017000
access("/etc/ld.so.nohwcap", F_OK)
= -1 ENOENT (No
such file or directory)
open("/etc/ld.so.preload", O_RDONLY) = -1 ENOENT
(No such file or directory)
close(3)
=0
read(3,
"\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220
\342"..., 512) = 512
演習:直接システムコールを呼ぼう
システムコールを使ったhelloworld出力
(前回既にできた人は必要なし)
システムコールを呼ぼう
 システムコールには,番号がついています.
 Linux: /usr/include/asm/unistd.h
 FreeBSD: /usr/include/sys/syscall.h
#define
#define
#define
#define
#define
#define
#define
#define
SYS_syscall
SYS_exit
SYS_fork
SYS_read
SYS_write
SYS_open
SYS_close
SYS_wait4
0
1
2
3
4
5
6
7
$FreeBSD: src/sys/sys/syscall.h,v 1.178.2.1 2005/11/21 01:36:27 csjp Exp $
システムコール〜引数の渡し方〜
 Linux
EAX
EBX
ECX
EDX
ESI
EDI
レジスタ
レジスタ
レジスタ
レジスタ
レジスタ
レジスタ
システムコール番号
第一引数
第二引数
第三引数
第四引数
第五引数
mov $4, %eax
mov $1, %ebx
mov buf, %ecx
mov length, %edx
int 0x80
 FreeBSD
引数の順番と逆にスタッ
クに積んでいく
push length
push buf
push $1
push $4
int 0x80
システムコールでhelloworld
 Linux
const char message[] = "hello world¥n";
int writes(const char *buf, int len) {
int ret;
asm(
"int $0x80"
: "=a" (ret)
: "a" (4),
"b" (1),
"c" (buf),
"d" (len)
);
return(ret);
}
int main() {
int ret;
ret = writes(message, sizeof(message));
return(ret);
}
 FreeBSD
const char message[] = "hello world¥n";
int writes(const char *buf, int len) {
int ret;
asm("nop" :: "b"(len));
asm ("pushl %ebx");
asm("nop" :: "c"(buf));
asm("pushl %ecx");
asm("pushl $1");
asm("movl $0x4, %eax");
asm("pushl %eax");
asm("int $0x80");
asm("addl $12, %esp");
return(ret);
}
int main() {
int ret;
ret = writes(message, sizeof(message));
return(ret);
コンパイル&実行!
% gcc -o helloworld helloworld
% ./helloworld
hello world