プログラミングの基礎 第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
© Copyright 2024 ExpyDoc