c-lesson 第二回「簡易アセンブラとディスアセンブラを作ろう」を終えた


TL;DR

  • c-lesson でC言語の勉強をしている
  • 第二回も第一回に負けず劣らずの出来の良さで、こちらも良い勉強になった
  • 簡易的な逆アセンブラとアセンブラを実装することで、低レベルからC言語の理解を深めることができる良コンテンツ

以前 c-lesson第一回の感想 を書いた。 c-lesson とは何かとか第一回の概要を知りたい人はこれを読んでいただければ。

第一回では PostScript のインタプリタを実装したわけだが、第二回は簡易的な逆アセンブラとアセンブラを実装し、低レベルの観点からC言語をさらに理解(する素地を作ることが)できるようになっている。 第三回ではコンパイラの処理の追跡やスタックウォーキングまで合わせる(第三回の感想ブログも全部終えたら書く)と、実に広範な内容をカバーしている。

これだけの内容を構造化して適切に課題などを散りばめながらコンテンツとしてまとめるのは凄い。 自身がある領域のいっぱしのプロフェッショナルと言うならば、必要とあればその領域に関してこれくらいの内容をガッと準備できるようにならないといけないな、と身が引き締まる思いだ。

自分が実装した逆アセンブラとアセンブラの本体部分はそれぞれ ここここ にある。 第二回もテスト込みで3,000行に満たないくらいのコード量になっている。 ちなみにアセンブリは32ビットARMをターゲットにしている。理由に関しては資料から抜粋すると以下。

最初x86でこの回を作る気だったのですが、x86のアセンブリ言語は歴史的事情からいろいろ複雑な仕組みになっている為、説明がややこしくなります。

一方ARMはいろいろな事が単純になっている為、低レベルの事を勉強するには手頃なターゲットとなっています。 また、C言語を書く必要があるシチュエーションではARMをターゲットにする方が今ではむしろ多いと思うので、実用性という観点からもARMを覚えておくのは悪くない、と思いARMをターゲットとしました。

別件で x86-64 のアセンブリを使う本を読んだりしていて、もちろん違うところは色々あるのだけど、一度アセンブリをある程度学んでおいたおかげでかなりすんなり理解することができたので、やはりどれか特定のターゲットでちゃんと学んでおくのは役に立ちそう。

commit log を見ると、最初に QEMU の環境を Dockerfile で準備したのが 20190420 になっている。 間はちょこちょこ空きながらも進めて、第二回の最後の commit は 20190625 であった。 無職のくせに結構時間掛かってしまったな〜、まあ他のことをやりながらだとこれくらい掛かってもおかしくないくらいのボリュームである、ということなのだが。

ということで、第二回の内容を少し振り返ってみる。

C言語に必要な程度だけ話す、ということの有り難さ

低レベルプログラミングに馴染みのない人にとって、抽象度の低いアセンブリの内容をガッと浴びせられてそれを咀嚼していくのはなかなかにしんどい。 自分も昔ちょろっとだけ眺めてみたことがあったけど、そもそもどういうところに注目していけばいいんだっけとなったり自分にとって興味ある部分なのか否かの判断が難しく、大したモチベーションもなかったのでほとんど何も身につかない状態で止めてしまっていた。

高機能なプログラミング言語で何かちょろっとしてものを作ってみようかな、というものと比べると取っ付きにくさがあるが、それをC言語の理解に必要な部分だけに着目して整備した上で解説してくれるのはかなり有り難い。

ということで、そもそもアセンブリとはみたいな話から始まり、環境構築から(擬似)命令やラベルの解説、レジスタやメモリの扱いなどを経て、Hello World 的な内容とプラスαくらいで必要な部分をさらいつつ興味のある内容に入っていける。 とはいえアセンブリをまともに書いたことがないので、最初は読み書き共になかなか慣れなかった。 例えば自分が書いたアセンブリは こんなの とか。 まあ素直で短いやつの読み書きならそんなに不自由なくできるようにはなったかな、というくらいにはなった。

書き方にルールなどはありません。アセンブリは野蛮な荒くれ者どもの跋扈する荒野なのです。

これは笑った。

バイナリの理解のために ARM7DI DataSheet を眺めたりもするが、この辺も必要なところだけを都度説明してくれるので助かる。 とりあえず読めと言われたら結構手を出しづらいが、ある程度勘所が分かってからだと自分で読んでいけるので、c-lesson のおかげでデータシートを結構楽しく眺めることができるようになった。

ありがたやありがたや(AA略)

逆アセンブラをつくる

commit log の一覧は こちら。 branch 名に disassembly ではなく inverse_assembly とかを使っている時点でなかなか酷いが、まあええやろの精神。

前準備で得られたバイナリの知識を使い、逆アセンブラを作っていく。 これをやるとバイナリとだいぶ仲良くなれるので個人的には楽しかった。

特定の目的に必要なところは意外と多くなかったりするので、ハードコードも使いながらちゃんと必要な部分だけやっていく、というのも良かった。 不必要にバイナリのビット演算とかをこねくり回して予想外のところでマッチしたりしなかったり、みたいなのを自分だったらやってしまいそうだけど、こんなん一回しか出てきてないからハードコードでいいんや!となるので何を目的に実装しているのかに注力できた感がある。

regression test とか即値のビットローテーションなどの内容も良い頃合いで解説してくれるので、これもうまく本質的な部分に注目しながら必要な知識を獲得していけるという流れになっている。

データシートと突き合わせながらバイナリを眺めてると正に低レベルプログラミングという感じがして良かった。 このブログのタイトルも「原理的には可能」なので、やはり原理的なところから分かるようになっておかないとだな! そんなこと言ったら電磁気とかから理解しないとダメだろと言われそうだが、それは幸いにして物理の研究をしていたのでそこそこ分かっている方だと言って差し支えないだろう。

アセンブラをつくる

commit log の一覧は こちら。 リファクタリングもなかなかに頑張っている様子が伺える。

第二回のラストは簡易アセンブラを作るところで、第一回での学びを活かしつつ、二分木でニモニックとラベルのシンボルを実装したりステートマシンを使って文字列パースを実装したりしつつ進んでいく。

こういう内容を散りばめつつ進めさせるのは教育的な配慮が行き届いていることを感じさせる。 後半になっていくと解説が簡素になってあとは実装してみ、という感じになってくるので解説資料の残り分量からそろそろ終わりかな〜と思ってから結構時間が掛かった(笑)。 第一回の内容があるからこそ自力で実装できる部分が増えていることを実感できるところも教育的な配慮だろう(本当か?)。

コードレビューも色々してもらって、自分の場合はコード量が増えてきてボトムアップな方針だけではうまくいかないくらいの規模になったときに質が落ちがちということも分かった。 詳細な実装は意識して忘れてトップダウンな方針からどういうAPIだったら良いのか、という視点を意識してトレーニングしていこう。

ちなみに内容としては、基本的には入力の文字列をパースしてせっせとアセンブルしていくというものになっている。 第一回でもパーサーを実装してるのでまあまあこなれてきた。 詳細は解説資料と commit log を見れば分かるかなということでまあいいかな。

自分でアセンブルして吐いたバイナリを objdump の結果と比べて、合ってると「おぉ〜」ってなるので楽しい。

ここまでで簡易的な逆アセンブラとアセンブラを作ったので、低レベルプログラミングやっとるなという感覚が得られてきた。 必要に迫られれば独自アセンブリ言語を作るという選択肢が取れるようになったのは武器が増えてみたいでいいね(そんな日が来るのか?ということは置いておいて)

ちなみにC言語の理解という点では第三回の内容を合わせることで本領を発揮するのだが、それはまた第三回の感想で触れるとしよう。

まとめ

c-lesson の第二回を完走した。

今回も素晴らしい内容だった。 どこかで低レベルプログラミングをやりたいなと思っていたので、それが経験できたというのも良かった。

第一回も第二回も内容を褒めてばっかりなので回し者だと思われてそうだが、まあ実際内容は素晴らしいので致し方あるまい。 敢えて言えば、C言語を勉強したいという人がここまで来れるのかというとなかなかに厳しいのではないだろうか、ということくらいか。

ちなみにまだ数人なら教えてくれそうな雰囲気なので、無職とか学生でガッツと興味がある人はいいのではなかろうか(ある程度時間が取れるならもちろん働きながらでもいけると思う)。

思ったより時間が掛かったという事実を考慮すると、今年やれたのが良かったなと思う。 働きながらだとやはり自分にとって割とすぐに必要な内容を勉強しがちなので、色々と基礎が足りてない自分には完走は厳しかったと思われる。 やはり無職最強か?

ということで最終の第三回も張り切ってやっていきたい。 (これを書いている時点で最後の JIT の課題の前までは終わっているのだが)