人と違うことをすること
P1
熱血バイナリアン十訓
- まずは読め
- 楽しんで読め
- 無理やり読め
- 勘で読め
- 暇を見つけて読め
- 思うままに読め
- 納得行くまで読め
- 明日も読め
- 飽きたら別のを読め
- わからなくても気にせず読め
基礎技術を知っていれば不足の事態でも自力でなんとかできるという強み
- どれだけ高レイヤーですごいものを書けるか
- どれだけ低レイヤーでなんでも書けるか
役に立たない分野のほうが勉強して得する部分がいっぱいある
役に立つ技術はみんな勉強するのでナンバーワンになるのは難しい
はじめに
フィーリング
CPUのアーキテクチャの設計思想や歴史を知る
でもアセンブラは敷居が高い。高すぎる。
というわけで、資料をあまり用意せず調べずにアセンブラを読む
面白いと思ってもらうことが重要
方向性
- 資料をあまり真面目に調べずになんとなく読む
- 知識や経験や解析力ではなく、想像力、推測、ノリで読む
- わからなくても気にせずに読み進める
気楽に読んでもらうことが重要
- アーキテクチャをきちんと理解すること
- CPUやアーキテクチャをきちんと勉強する
- きちんと勉強しないと読んではいけない
- きちんと理解してから次に進んではいけない
上記のようなことは考えない
気楽に読む
命令を解釈するだけではなく、様々なアプローチから読む
従来のアセンブラの読み方
- CPUのドキュメント
- レジスタやスタックの詳細な移り変わりを追う
- わからない命令が出たら都度ドキュメントで確認
- 内部でやっていることを推測
失敗の理由はきちんと読もうとすること。
予備知識無しでアセンブラを読んで勘とフィーリングでなんとなく読む
本書の読み方
「このへんはこう動いているのだろう」というアタリをつけて読む 読み方を工夫する
- Cのサンプルプログラムをコンパイル、逆アセンブルして対比しながら読む
- 短いサンプルで読む
- まとまりごとに処理の目的を意識してブロック単位で読む
- まず適当に読む
- わからなくても気にしない
- 他のCPUと比較する(他の本とは一線を画す部分)
- 手を動かして理解を深める
普通にやるとページ数が多すぎて挫折するので、勉強会を開いた
C言語のサンプルは非常に簡単
アセンブラを知るために、C言語で簡単なプログラムを書いて、逆アセンブルしてアセンブラを確認してみる
多角的な方法で理解する
- 一字一句の解析
- 他のCPUのアセンブラと比較
- サンプルプログラムの書き換え
- オブジェクト解析
目的を意識する
コードから目的を見るのではなく、目的からコードを見る
「何をしているコードなのか」が予め分かっていれば、コードも読みやすい
そもそも全体として何をやっているか > 一字一句正確に解読する
サンプル
P12
sample/powerpc-elf.d
00fe1580 <call_complex1>: fe1580: 94 21 ff f0 stwu r1,-16(r1) fe1584: 7c 08 02 a6 mflr r0 fe1588: 90 01 00 14 stw r0,20(r1) fe158c: 38 60 00 fe li r3,254 fe1590: 4b ff fe cd bl fe145c <return_arg1> fe1594: 38 63 00 01 addi r3,r3,1 fe1598: 80 01 00 14 lwz r0,20(r1) fe159c: 7c 08 03 a6 mtlr r0 fe15a0: 38 21 00 10 addi r1,r1,16 fe15a4: 4e 80 00 20 blr
sample/powerpc-elf.c
int call_complex1() { return return_arg1(0xfe) + 1; }
アセンブラとプログラムを見比べて、アタリをつけてみる
li r3, 254 ! r3 = 254 addi r3,r3,1 ! r3 = r3 + 1
最初と最後にスタックの確保と解放をやっているんだろうなあという「アタリ」
推測とノリでやる
この時点で「スタック」ってなんだよって話
00fe1580 <call_complex1>: ! スタック準備 fe1580: 94 21 ff f0 stwu r1,-16(r1) ! スタックへの退避 fe1584: 7c 08 02 a6 mflr r0 fe1588: 90 01 00 14 stw r0,20(r1) ! 引数0xfeの準備 fe158c: 38 60 00 fe li r3,254 ! 関数return_arg1()の呼び出し fe1590: 4b ff fe cd bl fe145c <return_arg1> ! 戻り値の加工 fe1594: 38 63 00 01 addi r3,r3,1 ! スタックからの復旧 fe1598: 80 01 00 14 lwz r0,20(r1) fe159c: 7c 08 03 a6 mtlr r0 ! スタックの解放 fe15a0: 38 21 00 10 addi r1,r1,16 ! 呼び出し元から戻る fe15a4: 4e 80 00 20 blr
推測点
- スタックを操作しているのはr1
- r1がスタックポインタ
- 引数はr3で渡すのか
- 関数の戻り値もr3に格納されている
本書の構成
P14
第一部 PowerPCをメインにアセンブリを適当に読む→レジスタやスタック利用のパターンを身につける
Macintoshとかゲーム機とかで使われてるやつ
MIPSを読みつつ、PowerPCと対比する→「アセンブラはなんとなくでも読める」ことを体感する
PS1 PS2とかで使われていた
遅延スロットが初見殺しなのでMIPSではなくPowerPCを出したのではないかと。
そしてSHやARMとかいろいろ出してみて、「アセンブラはなんとなくでも(ry」を実感する
「わかりやすいから」という理由でPowerPCとかMIPSを出したのだと思われる。
これらを踏まえて、「きちんと調べてから読む」のではなく、勘と想像で「アセンブラはなんとなく(ry」を実感する
アセンブラはi386だけじゃないっ
第二部はいろいろなCPUのアセンブラを読む
第三部は環境構築
コツを覚えてみよう的なノリで始まる
本書のサポートについて
P16
サポートサイト http://kozos.jp/books/asm/
著者はよくOSCに行くらしい
確実な情報はなく、「こうではなかろうか」という推測でものを書いていることもあるので、不確実な記述もある。
本書の読み進め方
この本は技術書というより読み物。丁寧に読んでもいいし、飛ばし飛ばし読んでもいい。環境構築してガッツリやってもいいし、環境構築せずにソースコードを眺めながら読んでもいいし、ソースコードもなく本をパラパラ読んでもいい。
初心者で読み物として読み進めたい人
順番に読む
初心者でPCに環境を構築して読み進めたい人
P656→P36→順番に読む
ある程度自身のある人
本節→好きなところから
環境構築
遅いPCだとすごく遅い。
newlibをコンパイルする必要はない
binutilとgccがあればとりあえず使える。ガチの組み込み開発の場合はnewlibが必要だけど、ちょっと実験でコンパイルや逆アセンブラをやりたい場合はnewlibは必要ない
CPU
他のメーカーだとMPUとかマイクロプロセッサとか呼ばれる部分だが、厳密に言うとプロセッサとかアーキテクチャと呼ばれるけども、「中央演算装置」としての意味合いとして、CPUという呼称で統一する。
馬鹿でかいコンピュータだとマイクロですらないので、中央演算装置部分はすべてCPUに統一する
サンプルプログラムについて
サンプルプログラムはすべてsample.cで統一する→補遺Aを参照のこと
本書の内容を実践するためにはバイナリエディタや16進電卓などを使う必要がある→補遺Bを参照のこと
目次
第一部
「アセンブラ入門」といえばi386入門書みたいなイメージあるけど、この本はそこから始まらない
PowerPC→MIPS→ARM→SH→H8→i386
アセンブラ入門の場合、ARMとi386がメインになるわけだが、マイナーなCPUを並列で勉強している
第二部
多分作者はRISCが好み
SPARC→PA-RISC→64ビットプロセッサ→32ビットプロセッサ→マイコン→昔ながらのマイコン→CISC→メインフレーム→その他
そしてVAXとかPDP-11とかも出てくる
第三部
環境構築
シミュレータ対応
シミュレータ(タイミングはともかく入出力さえ同じになっていればいい) エミュレータ(タイミングも含めて完全再現)
だいたい800ページ
第一部 基礎編: まずはアセンブラに慣れよう
P35
PowerPC→MIPS→ARM→SH→H8→i386
まずはPowerPC
PowerPCを選んだのは、アセンブラが素直で初心者にも読みやすいと思ったから。
サンプルコード http://kozos.jp/books/asm
いきなりサンプルコードを全部説明しようとすると死ぬので、小出しに説明してく。
- 何もしない
- 0を返す
- 1を返す
void null() { return; } int return_zero() { return 0; } int return_one() { return 1; }
コンパイル→逆アセンブルしたもの
00fe1400 <null>: fe1400: 4e 80 00 20 blr 00fe1404 <return_zero>: fe1404: 38 60 00 00 li r3,0 fe1408: 4e 80 00 20 blr 00fe140c <return_one>: fe140c: 38 60 00 01 li r3,1 fe1410: 4e 80 00 20 blr
クロスコンパイラ 別のCPU用の実行モジュールを作成するためのコンパイラ
- 開発環境と本番環境が同じなのをセルフ
- 開発環境と本番環境が違うのをクロス
別の環境でPowerPC用のアセンブラを作った→クロスコンパイル
目的を意識して読む
P39
アセンブラの内容を詳細に説明しません
blrはreturn r3というレジスタに1やら0やらを代入(li)してからblrするというアタリがつく
r3というレジスタに値を代入して、blrしてreturnする
とりあえずliとblrという命令が存在するんだろうなあという推測
- レジスタ CPUが持っている固定の記憶領域=変数
- 命令セット(ISA) CPUがもっている命令の種類のこと(徐々にたくさんでてくる)
推測して想像力で読む
アセンブラの読み方を知る
アドレス 機械語が配置されている場所
4e 80 00 20 8桁の16進数は機械語のバイナリコード 実際のメモリ上に命令コードとして配置されるバイト列
0123456789abcdef 16進数(C言語の場合は小文字で書く)
00fe1400 <null>: fe1400: 4e 80 00 20 blr 00fe1404 <return_zero>: fe1404: 38 60 00 00 li r3,0 fe1408: 4e 80 00 20 blr 00fe140c <return_one>: fe140c: 38 60 00 01 li r3,1 fe1410: 4e 80 00 20 blr
- 00fe1400
: fe1400: 4e 80 00 20 blr 関数のアセンブラ - 00fe1400 関数のアドレス
- 38 60 00 00 機械語のバイナリ(16進数文字列の羅列)
- li r3,1 (アセンブラのニーモニック)
ニーモニックは人間に読みやすくしたやつで、実際に機械が読んでいるのはバイナリのほう
バイナリを直接修正したりハンドアセンブルしたりすることも、ある
命令が少なくて単純なので、至極読みやすい
アドレスが4つずつ進んでいるのは機械語が4バイトずつ入っているから。アドレスは1バイト単位で書かれている。
fe1400: 4e(0) 80(1) 00(2) 20(3) blr fe1404: 38(4) 60(5) 00(6) 00(7) li r3,0 fe1408: 4e 80 00 20 blr fe140c: 38 60 00 01 li r3,1 fe1410: 4e 80 00 20 blr
機械は16進数文字列を順次実行している。実際に解釈しているのは2進数文字列だけれども、人間により読みやすくするために16進数表記にしてある。
これが、「コンピュータは0と1でできた命令しか理解出来ない」ということ
16進数文字列だけで表記するとすごく分かりづらいので、4e 80 00 20はblrと表記するようにしよう、というのがニーモニック
4e 80 00 20 blr は1体1
機械語の命令とニーモニックは通常1体1なので、人間が機械語のプログラムを書くときはニーモニックで表記してて、ニーモニックを実際の機械語に変換することを「アセンブラ」という。
アセンブリ言語のことをアセンブラということもあるし、ニーモニックを機械語に変換するプログラムのことをアセンブラということもある。
固定長の命令
P43
4e 80 00 20
38 60 00 01
すべて固定長4バイト命令であることに気づく。
1つの命令が固定長なのは全てのCPUに共通することではない。
1つの命令の長さが2バイトだったり3バイトだったりする可変長命令もありうる
固定長命令がRISCで、可変長命令がCISC というステレオタイプな分類でいってみる。