資料
7shi / ikebin / wiki / pdp11 — Bitbucket
最終目標はOSとコンパイラを作る
40年前のUNIX v6 1975
- 昔のOSであるため簡単であること
- Cのコンパイラがセットになっている
- 「比較的」シンプル
これ以上簡単なものになってしまうと実用性が皆無になる
自分でコンパイラを作ってOSを作ろうとしたけど、 アセンブラって何?ってところから始まってしまった
コンパイラはOSの上で動くので、OSのことが分かってないとコンパイラのことも分からないし、作れない
題材をだしてやっていく。
v6ccで昔のバイナリを呼び出す
UNIX V6 OS(40年前) PDP-11 CPU(40年前)
V6を8086(x86の初期)に移植する作業をやってる
CPUの命令やバイナリの仕組みを覚えておいて損はない (ただしオペランド(引数)の細かいところには大きな違いがある)
逆アセンブル
右側の命令群がアセンブリ言語で、命令群を数字に変換するのがアセンブル。
C→アセンブリ→バイナリ Cからアセンブリに変換するのをコンパイル、 アセンブルからバイナリに変更することをアセンブルという。
逆にバイナリからアセンブリに変換することを逆アセンブルという。
[crt0.o] start: 0000: f009 setd 0002: 1180 mov sp, r0 0004: 1226 mov (r0), -(sp) 0006: 0bd0 tst (r0)+ 0008: 1036 0002 mov r0, 2(sp) 000c: 09f7 0008 jsr pc, 0018 ; _main 0010: 100e mov r0, (sp) 0012: 09df 024e jsr pc, *$024e ; _exit 0016: 8901 sys 1 ; exit
上に書いてあるアセンブリはPDP11のものなので、CPUが違うと命令の書き方も変わる。
レジスタ
CPUのなかには変数があって、その変数をレジスタとよんで差し支えない。
フラグはレジスタとはまた別。
実際に実行しながら表示しているので、メモリに数字が格納されてる。
$ 7run -m a.out r0 r1 r2 r3 r4 r5 sp flags pc start: 0000 0000 0000 0000 0000 0000 fff6 ---- 0000:f009 setd 0000 0000 0000 0000 0000 0000 fff6 ---- 0002:1180 mov sp, r0 fff6 0000 0000 0000 0000 0000 fff6 -N-- 0004:1226 mov (r0), -(sp) ;[fff6]0001 ;[fff4]0000 fff6 0000 0000 0000 0000 0000 fff4 ---- 0006:0bd0 tst (r0)+ ;[fff6]0001 fff8 0000 0000 0000 0000 0000 fff4 ---- 0008:1036 0002 mov r0, 2(sp) ;[fff6]0001 fff8 0000 0000 0000 0000 0000 fff4 -N-- 000c:09f7 0008 jsr pc, 0018 ;[0018]0977 _main: fff8 0000 0000 0000 0000 0000 fff2 -N-- 0018:0977 023c jsr r5, 0258 ;[0258]1140 csv: fff8 0000 0000 0000 0000 001c fff0 -N-- 0258:1140 mov r5, r0 001c 0000 0000 0000 0000 001c fff0 ---- 025a:1185 mov sp, r5 001c 0000 0000 0000 0000 fff0 fff0 -N-- 025c:1126 mov r4, -(sp) ;[ffee]0000 001c 0000 0000 0000 0000 fff0 ffee Z--- 025e:10e6 mov r3, -(sp) ;[ffec]0000 001c 0000 0000 0000 0000 fff0 ffec Z--- 0260:10a6 mov r2, -(sp) ;[ffea]0000
変数の呼び出しがかかるとメモリの間を行ったりきたりしている
逆アセンブルはただ解析しているだけ。
CPUがアップデートすると、言語のコンパイラもそれにあわせてアップデートしていかないといけない。
メーカーによっても型番によってもCPUが異なる
AMD vs Intel vs ARM
AMDはIntelでコンパイルしたやつに意地でも対応しようとするけどARMはそんなに頑張ろうとしてない
クロスコンパイラには時間がかかる
シンボル
printf とか main とか、コンパイルしたときに残っている名前のこと
$ v6nm a.out 001116T _exit 001014T _flush 001276B _fout 000030T _main 000054T _printf 000736T _putchar 000320t charac 001146T cret 000000a crt0.o 001130T csv 001130a csv.o 000242t decimal 001116a exit.o 000724a ffltpr.o 001030t fl 000464t float 001266b formp 000640t gnum 000030a hello.o 000362t hex 000256t logical 000100t loop 001272b ndfnd 001274b ndigit 000370t octal 000724T pfloat 000532t prbuf 000054a printf.o 000542t prstr 000724T pscien 000736a putchr.o 000520t remote 001270b rjust 001262B savr5 000502t scien 000000t start 000336t string 001174d swtab 001264b width 000030t ~main
アドレス | 種類 | 名前 |
---|---|---|
001116 | T | _exit |
001014 | T | _flush |
001276 | B | _fout |
000030 | T | _main |
000054 | T | _printf |
000736 | T | _putchar |
000320 | t | charac |
001146 | T | cret |
000000 | a | crt0.o |
001130 | T | csv |
場所と名前がシンボル情報を表示している
- nmコマンドは8進数
- -dコマンドは16進数
ゆえにアドレスの表示が違う
(豆知識)Pythonで16進数 8進数変換
>>> 030 24 >>> 0x18 24 >>> hex(030) '0x18' >>> hex(030) '0x18' >>> oct(0x18) '030'
(当時のCPUは8進数がデフォルト)
ファイルサイズの縮小
シンボルを削ればファイルサイズを縮小できる。というのも、シンボルそのものはファイルの実行に関係ないから。
(あったらあったで便利だけど。読みやすいし。)
シンボル情報 = 名前 シンボルを削ると、nmコマンドが使えなくなる。
☁$ l a.out -rwxrwxrwx 1 shige staff 1186 2 1 14:14 a.out ☁$ v6strip a.out ☁$ l a.out -rwxrwxrwx 1 shige staff 706 2 1 14:55 a.out $ v6nm a.out no name list
逆アセンブルを影響を及ぼす。 シンボル情報が消えて、どこがなんだったのか分からなくなる。
バイナリを読むときに、シンボルがあったほうが圧倒的に楽。
そして、シンボルなしに読めるようにする必要もない。
$ 7run -d a.out 0000: f009 setd 0002: 1180 mov sp, r0 0004: 1226 mov (r0), -(sp) 0006: 0bd0 tst (r0)+ 0008: 1036 0002 mov r0, 2(sp) 000c: 09f7 0008 jsr pc, 0018 0010: 100e mov r0, (sp) 0012: 09df 024e jsr pc, *$024e 0016: 8901 sys 1 ; exit 0018: 0977 023c jsr r5, 0258 001c: 15ce 0274 mov $274, (sp) 0020: 09df 002c jsr pc, *$002c 0024: 0a00 clr r0 0026: 0100 br 0028 0028: 0077 023a jmp 0266 002c: 0977 0228 jsr r5, 0258 0030: e5c6 007e sub $7e, sp