![[]](../icons/screen-cursor.gif)
2009年12月01日 情報科学類 オペレーティングシステム II 筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/literacy-2009/2009-12-01
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/
青木 峰郎: "ふつうのコンパイラをつくろう", ソフトバンククリエイティブ (2009). ISBN-13: 978-4797337952
% ls -l hello.rb
-rw-r--r-- 1 yas prof 26 Nov 28 17:24 hello.rb
% cat hello.rb
printf("hello, world!\n")
% ruby hello.rb
hello, world!
%
Ruby 言語のインタプリタ自身は、C 言語で記述されている。
図? メタレベルのプログラミング
プログラム「hello.rb」の動きを変更する方法
% ls -l hello.c
-rw-r--r-- 1 yas prof 39 Nov 28 17:35 hello.c
% cat hello.c
main() {
printf("hello, world!\n");
}
% cc hello.c -o hello
% ./hello
hello, world!
%
図? メタレベルのプログラムとしてのOSカーネル
プログラム「hello.c」の動きを変更する方法OSカーネル自身は、普通は、ハードウェアによるインタプリタ(CPU、入出力)で 実行される。インタフェースは、機械語命令や入出力機器への操作。
OSカーネルへのインタフェースは、システム・コール。 == OSカーネルは、システム・コールのインタプリタ。
(OSのメタレベルのプログラムとして「仮想計算機モニタ」を使うこともある。)
図? OSの構造
プログラム・コードの場所システムコールの例:
システムコールとライブラリの見分け方(Unix編)
同じUnix系OSでも、細かい所でシステム・コールとライブラリが入れ替わって いることもある。
システム・コールは、トラップ命令(trap instruction)を含む。 その部分は、アセンブリ言語。
ライブラリ関数は、大部分はC言語で書かれている。 printf() のソース・プログラムがある。
C言語で記述したプログラムは、文法さえあっていれば コンパイルはできる。 ライブラリ関数やシステムコールがないと動作しない。
<省略> 27: int 28: printf (const char *format, ...) 29: { 30: va_list arg; 31: int done; 32: 33: va_start (arg, format); 34: done = vfprintf (stdout, format, arg); 35: va_end (arg); 36: 37: return done; 38: } <省略> 42: strong_alias (printf, _IO_printf);
VFPRINTF(P) VFPRINTF(P) ... int vfprintf(FILE *restrict stream, const char *restrict format, va_list ap); ... DESCRIPTION The vprintf(), vfprintf(), vsnprintf(), and vsprintf() functions shall be equivalent to printf(), fprintf(), snprintf(), and sprintf() respectively, except that instead of being called with a variable num- ber of arguments, they are called with an argument list as defined by <stdarg.h>.
push %ebx # ebxレジスタの内容をスタックへ退避 mov 0x10(%esp),%edx # 第3引数をレジスタ edx へ mov 0xc(%esp),%ecx # 第2引数をレジスタ ecx へ mov 0x8(%esp),%ebx # 第1引数をレジスタ ebx へ mov $0x4,%eax # システム・コールの番号 eax へ int $0x80 # トラップ命令。カーネルへ制御を移す。 pop %ebx # ebxレジスタの内容を回復 cmp $0xfffff001,%eax # エラーが起きたら jae __syscall_error # __syscall_errorへジャンプ (errno等の保存) ret
プログラムは、ハードディスクやCD-ROMに入っていることもある。 1つのプログラムから複数のプロセスが作られることがある。
% ps
PID TTY TIME CMD
20591 pts/1 00:00:00 tcsh
20761 pts/1 00:00:00 nm
20762 pts/1 00:00:00 lv
20793 pts/1 00:00:00 ps
% ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
yas 20591 0.0 0.1 9224 2696 pts/1 Ss 15:30 0:00 -tcsh
yas 20761 0.0 0.0 3724 904 pts/1 T 15:54 0:00 nm /usr/lib/libc.
yas 20762 0.0 0.0 2720 624 pts/1 T 15:54 0:00 lv
yas 20794 0.0 0.0 6280 1376 pts/1 R+ 16:18 0:00 ps u
% ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 1013 20591 20590 15 0 9224 2696 rt_sig Ss pts/1 0:00 -tcsh
0 1013 20761 20591 16 0 3724 904 finish T pts/1 0:00 nm /usr/lib/l
0 1013 20762 20591 16 0 2720 624 finish T pts/1 0:00 lv
0 1013 20795 20591 16 0 4256 628 - R+ pts/1 0:00 ps l
% ps x
PID TTY STAT TIME COMMAND
20590 ? S 0:00 sshd: yas@pts/1
20591 pts/1 Ss 0:00 -tcsh
20761 pts/1 T 0:00 nm /usr/lib/libc.a
20762 pts/1 T 0:00 lv
20796 pts/1 R+ 0:00 ps x
%
1: /* 2: fork-hello.c -- 画面に文字列を表示するプログラム 3: ~yas/syspro/proc/fork-hello.c 4: Start: 2001/05/13 23:19:01 5: */ 6: 7: #include <stdio.h> 8: 9: main() 10: { 11: fork(); 12: fork(); 13: fork(); 14: printf("hello\n"); 15: }
% make fork-hello
cc fork-hello.c -o fork-hello
% ./fork-hello
hello
hello
hello
hello
hello
hello
hello
hello
%
図? fork()システム・コールによるプロセスのコピー
1: /* 2: exec-date.c -- 実行中のプロセスのプログラムをdateプログラムに切替える 3: ~yas/syspro/proc/exec-date.c 4: Start: 2001/05/13 23:25:49 5: */ 6: 7: #include <unistd.h> 8: 9: extern char **environ; 10: 11: main() 12: { 13: char *argv[2], *path ; 14: path = "/bin/date" ; 15: argv[0] = "date" ; 16: argv[1] = 0 ; 17: execve( path,argv,environ ); 18: execve( path,argv,environ ); 19: execve( path,argv,environ ); 20: }
% make exec-date
cc exec-date.c -o exec-date
% ./exec-date
Mon Nov 30 12:57:19 JST 2009
%
図? execve()システム・コールによるプログラムの実行
コンパイルしたオブジェクト・プログラムだけではまだ足りない。 次のものを付け足す(リンクする)して、実行可能(executable) プログラムになる。
実行時ルーチンの役割
用語
図? ソース・プログラムらプロセスまで
% cc -v hello.c -o hello
Reading specs from /usr/lib/gcc/i386-redhat-linux/3.4.6/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man
--infodir=/usr/share/info --enable-shared --enable-threads=posix
--disable-checking --with-system-zlib --enable-__cxa_atexit
--disable-libunwind-exceptions --enable-java-awt=gtk
--host=i386-redhat-linux
Thread model: posix
gcc version 3.4.6 20060404 (Red Hat 3.4.6-11)
/usr/libexec/gcc/i386-redhat-linux/3.4.6/cc1 -quiet -v hello.c -quiet
-dumpbase hello.c -auxbase hello -version -o /tmp/cchy6jGb.s ignoring
nonexistent directory
"/usr/lib/gcc/i386-redhat-linux/3.4.6/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i386-redhat-linux/3.4.6/include
/usr/include
End of search list.
GNU C version 3.4.6 20060404 (Red Hat 3.4.6-11) (i386-redhat-linux)
compiled by GNU C version 3.4.6 20060404 (Red Hat 3.4.6-11).
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
as -V -Qy -o /tmp/ccYqFElf.o /tmp/cchy6jGb.s
GNU assembler version 2.15.92.0.2 (i386-redhat-linux) using BFD version 2.15.92.0.2 20040927
/usr/libexec/gcc/i386-redhat-linux/3.4.6/collect2 --eh-frame-hdr -m
elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o hello
/usr/lib/gcc/i386-redhat-linux/3.4.6/../../../crt1.o
/usr/lib/gcc/i386-redhat-linux/3.4.6/../../../crti.o
/usr/lib/gcc/i386-redhat-linux/3.4.6/crtbegin.o
-L/usr/lib/gcc/i386-redhat-linux/3.4.6
-L/usr/lib/gcc/i386-redhat-linux/3.4.6
-L/usr/lib/gcc/i386-redhat-linux/3.4.6/../../.. /tmp/ccYqFElf.o -lgcc
--as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s
--no-as-needed /usr/lib/gcc/i386-redhat-linux/3.4.6/crtend.o
/usr/lib/gcc/i386-redhat-linux/3.4.6/../../../crtn.o
%
図? ccコマンドの背後で動いているプログラム
% cat -n today.c
1 #define TODAY "Tuesday"
2 main()
3 {
4 printf("Today is %s.\n",TODAY );
5 }
% cc -E today.c > today.i
% cat today.i
# 1 "today.c"
# 1 ""
# 1 ""
# 1 "today.c"
main()
{
printf("Today is %s.\n","Tuesday" );
}
%
C言語のプリプロセッサは、C言語そのものではなく、マクロプロセッサ。
% cpp
#define apple orange
apple computer
^D
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "<stdin>"
orange computer
%
Cプリプロセッサの機能
% cat hello.c
main() {
printf("hello, world!\n");
}
% cc -S hello.c
% cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "hello, world!\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp
subl $12, %esp
pushl $.LC0
call printf
addl $16, %esp
leave
ret
.size main, .-main
.section .note.GNU-stack,"",@progbits
.ident "GCC: (GNU) 3.4.6 20060404 (Red Hat 3.4.6-11)"
%
% cc -c hello.c
% ls hello.?
hello.c hello.o
%
% cc -v hello.o -o hello
Reading specs from /usr/lib/gcc/i386-redhat-linux/3.4.6/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --enable-shared --enable-threads=posix --disab
le-checking --with-system-zlib --enable-__cxa_atexit --disable-libunwi
nd-exceptions --enable-java-awt=gtk --host=i386-redhat-linux
Thread model: posix
gcc version 3.4.6 20060404 (Red Hat 3.4.6-11)
/usr/libexec/gcc/i386-redhat-linux/3.4.6/collect2 --eh-frame-hdr -m e
lf_i386 -dynamic-linker /lib/ld-linux.so.2 -o hello /usr/lib/gcc/i386-
redhat-linux/3.4.6/../../../crt1.o /usr/lib/gcc/i386-redhat-linux/3.4.
6/../../../crti.o /usr/lib/gcc/i386-redhat-linux/3.4.6/crtbegin.o -L/u
sr/lib/gcc/i386-redhat-linux/3.4.6 -L/usr/lib/gcc/i386-redhat-linux/3.
4.6 -L/usr/lib/gcc/i386-redhat-linux/3.4.6/../../.. hello.o -lgcc --as
-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-n
eeded /usr/lib/gcc/i386-redhat-linux/3.4.6/crtend.o /usr/lib/gcc/i386-
redhat-linux/3.4.6/../../../crtn.o
%
様々なライブラリや実行時ルーチンが参照されている。
逆アセンブラは、機械語をアセンブリ言語に戻す。 objdump コマンドに -d オプション(disassemble)を与えると逆アセンブルを行 う。
% cc -c hello.c
% ls hello.o
hello.o
% objdump -d hello.o
hello.o: file format elf32-i386
Disassembly of section .text:
00000000 :
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 83 e4 f0 and $0xfffffff0,%esp
9: b8 00 00 00 00 mov $0x0,%eax
e: 83 c0 0f add $0xf,%eax
11: 83 c0 0f add $0xf,%eax
14: c1 e8 04 shr $0x4,%eax
17: c1 e0 04 shl $0x4,%eax
1a: 29 c4 sub %eax,%esp
1c: 83 ec 0c sub $0xc,%esp
1f: 68 00 00 00 00 push $0x0
24: e8 fc ff ff ff call 25
29: 83 c4 10 add $0x10,%esp
2c: c9 leave
2d: c3 ret
%
0xfffffffc
」になっている。リ
ンクの時に実際の printf の番地に置き換えられる。
% ls -l hello.c
-rw-r--r-- 1 yas prof 39 Nov 28 17:35 hello.c
% cc -c hello.c
% ls -l hello.o
-rw-r--r-- 1 yas prof 884 Nov 28 2009 hello.o
% file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
% cc hello.o -o hello
% ls -l hello
-rwxr-xr-x 1 yas prof 4719 Nov 28 2009 hello
% file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for
GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped
%
file コマンドで調べると ELF 形式のファイルは、ELF と表示される。
図? ELFファイルの構造
.text | 機械語命令 |
.data | 初期化された大域変数やstatic変数 |
.rodata | (.dataに置くべきもののうち)読み読み専用のもの) |
.bss | 初期化されない大域変数やstatic変数(OSが0に初期化) |
.symtab | シンボル・テーブル |
.strtab | 文字列のテーブル。シンボル・テーブルの内容の文字列。 |
% nm -t x hello | egrep 'main|printf'
U __libc_start_main@@GLIBC_2.0
08048368 T main
U printf@@GLIBC_2.0
% nm -t x hello | egrep ' [tT] | [uU] | [dD] '
080494a0 D _DYNAMIC
0804956c D _GLOBAL_OFFSET_TABLE_
08049490 d __CTOR_END__
0804948c d __CTOR_LIST__
08049498 d __DTOR_END__
08049494 d __DTOR_LIST__
0804949c d __JCR_END__
0804949c d __JCR_LIST__
08049580 D __data_start
08048430 t __do_global_ctors_aux
08048308 t __do_global_dtors_aux
08049584 D __dso_handle
080483ec T __libc_csu_fini
08048398 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
08048454 T _fini
08048278 T _init
080482c0 T _start
080482e4 t call_gmon_start
0804833c t frame_dummy
08048368 T main
08049588 d p.0
U printf@@GLIBC_2.0
%
nmの結果は、番地、型、シンボルの3カラム。
型としては、次のものが重要。
これからわかること。
静的リンク用の libc は、ar 形式のアーカイブになっている。 普通、ranlib コマンドにより索引が付けられている。
% (nm -s /usr/lib/libc.a | egrep '^printf in' > /dev/tty )>& /dev/null
printf in printf.o
% ar x /usr/lib/libc.a printf.o
% ls -l printf.o
-rw-r--r-- 1 yas prof 696 Nov 30 13:34 printf.o
% nm printf.o
00000000 T _IO_printf
00000000 T printf
U stdout
U vfprintf
%
ar コマンドに x (extract) という引数を与えると、ファイルを取り出すこと
ができる。nm コマンドでシンボルを調べている。
動的リンク用
% ldd hello
libc.so.6 => /lib/tls/libc.so.6 (0x00aee000)
/lib/ld-linux.so.2 (0x00ad4000)
% nm -tx /lib/tls/libc.so.6 | egrep ' T printf$'
00b30ca0 T printf
%
ldd コマンドを使うと、利用する動的リンク・ライブラリが表示される。
-static
オプションを付ける。
% cc hello.c -o hello
% cc -static hello.c -o hello-static
% ls -l hello hello-static
-rwxr-xr-x 1 yas prof 4719 Nov 30 2009 hello
-rwxr-xr-x 1 yas prof 431829 Nov 30 2009 hello-static
% file hello hello-static
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped
hello-static: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
for GNU/Linux 2.2.5, statically linked, not stripped
% ./hello
hello, world!
% ./hello-static
hello, world!
%
% objdump -d hello-static | lv
...
080481d8 <main>:
80481d8: 55 push %ebp
80481d9: 89 e5 mov %esp,%ebp
80481db: 83 ec 08 sub $0x8,%esp
80481de: 83 e4 f0 and $0xfffffff0,%esp
80481e1: b8 00 00 00 00 mov $0x0,%eax
80481e6: 83 c0 0f add $0xf,%eax
80481e9: 83 c0 0f add $0xf,%eax
80481ec: c1 e8 04 shr $0x4,%eax
80481ef: c1 e0 04 shl $0x4,%eax
80481f2: 29 c4 sub %eax,%esp
80481f4: 83 ec 0c sub $0xc,%esp
80481f7: 68 68 11 09 08 push $0x8091168
80481fc: e8 9b 0a 00 00 call 8048c9c <_IO_printf>
8048201: 83 c4 10 add $0x10,%esp
8048204: c9 leave
8048205: c3 ret
8048206: 90 nop
8048207: 90 nop
...
08048c9c <_IO_printf>:
8048c9c: 55 push %ebp
8048c9d: 89 e5 mov %esp,%ebp
8048c9f: 8d 45 0c lea 0xc(%ebp),%eax
8048ca2: 50 push %eax
8048ca3: ff 75 08 pushl 0x8(%ebp)
8048ca6: ff 35 14 6e 0a 08 pushl 0x80a6e14
8048cac: e8 0b de 00 00 call 8056abc <_IO_vfprintf>
8048cb1: c9 leave
8048cb2: c3 ret
8048cb3: 90 nop
図? リンカの動作
動的リンクのライブラリは、普通、複数のプロセスで共有される。プロセ スごとに利用するライブラリが異なるので、ライブラリがロードされる番地も 異なる。どの番地にロードされてもよいコードが必要になる。そのようなコー ドは、再配置可能コード(relocatable code)やposition independent code (PIC) と呼ばれる。
% ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 1013 22963 22962 16 0 9456 2432 rt_sig Ss pts/2 0:00 -tcsh
0 1013 23098 22963 16 0 12312 5900 finish T pts/2 0:00 ls -l
0 1013 23101 22963 17 0 4096 628 - R+ pts/2 0:00 ps l
%
PID
が 23098
のプロセスが実行される時に、execve()
システムに対してどのような引数が渡されたと考えられるか。ただし、
ls
コマンドのプログラムの実行形式は、/bin/ls
にあるもの
とする。
% cc -c f.c
% nm f.o
00000000 T f
U g
%
この元の C 言語のプログラム f.c の内容としてどのようなものが考えられるか。
3行以内の簡単なプログラムを書きなさい。
foo() { bar(); bar(); } bar() { }これをコンパイルし、静的リンクを行った結果の一部を以下に示す。
00000034 <foo>: 0000034: 55 push %ebp 0000035: 89 e5 mov %esp,%ebp 0000037: 83 ec 08 sub $0x8,%esp 000003a: e8 [ (a) ] call 0000048 <bar> 000003f: e8 [ (b) ] call 0000048 <bar> 0000044: c9 leave 0000045: c3 ret 0000046: 90 nop 0000047: 90 nop 00000048 <bar>: 0000048: 55 push %ebp 0000049: 89 e5 mov %esp,%ebp 000004b: c9 leave 000004c: c3 ret(a) と (b) に入れるべき値(4バイトの整数値)を示しなさい。なお
e8
は、call 命令のオペコードである。また、call 命令のオペランドは、プログ
ラムカウンタ相対で指定するものとする。