Ibotenic by t-sin


C言語の勉強: 簡易逆アセンブラ・アセンブラ編

プログラミング C言語 言語処理系

2021-09-10T10:16:31.467047+09:00

karino2さんが公開しているC言語教室の第二回「簡易アセンブラとディスアセンブラを作ろう」を完走したので、感想やら反省やらを書きました。

c-lesson第二回を完走しました🥳

本日、karino2さんが公開しているkarino2の暇つぶしプログラム教室 C言語編 (脳内通称c-lesson) の第二回「簡易アセンブラとディスアセンブラを作ろう」を完走しました。

c-lessonの概要や第一回の内容・感想、開始した動機や挑戦者t-sinのレベルなどはこちらの記事を読んでいただくこととして、ここでは完走した第二回のことを振り返ったりします。

第二回の内容

第二回ではARM7を対象に以下のことを学びます:

  1. アセンブリ言語入門
  2. アセンブルされたバイナリの解読
  3. 逆アセンブラの実装
  4. アセンブラの実装

逆アセンブラやアセンブラは1のアセンブリ言語入門で書いたアセンブリのコードおよびバイナリのみを対象にした簡単なものですが、アセンブリ入門時に書いたコードのバイナリを解読したりその逆をしたりするので、前半でやってたことを後半でより深く、実践つきで学べる構成になっており秀逸です。

時間もそのぶんかかりますが (第一回完了記事)の公開日は2021年7月21日なので、だいたい1ヶ月半) 、とにかく楽しい内容となっております。

第二回での学び

第二回ではそのテーマであるアセンブリ言語について逆アセンブラ・アセンブラを書くことで相当量の学びが得られ、アセンブリ言語への恐怖が完全に払拭された気配があります。

そしてもちろん、karinoさんにコードを添削していただいたためよいコードの書き方の部分もたくさんの学びがありました。

アセンブラ入門

この回では、ARM7の評価ボードArm Versatile boardsの上でアセンブリ言語をやります。

ぼくは以前ラズパイでアセンブリ言語をちょこっと触ったことがあった (アセンブリ言語に入門したときのメモ - octahedron) のですが、たしか「Forthつくるぞ!!」と息巻いたもののechoするプログラムが書けなくて挫折していました。c-lessonでアセンブリを学んだいま見ると、サブルーチンを書いたつもりがblではなくbしており、「サブルーチンを超えてレジスタの整合性を気にしなきゃならないどうして……」と感じて涙した記憶もあるのできっとblとかリンクレジスタ (r14, サブルーチンの戻り先を入れておくところ) のことわかってなかったんだな、としみじみした気持ちになれます。この勉強時読んでた本をいまみるとbl命令のこととかちゃんと書いてあるので、たぶん気が逸ってかっ飛ばして読んだ結果なにも身につかなかったパターンでしょう。

c-lessonのアセンブリ入門部では、UART (なんか入出力できるバス? メモリにマップされているのでここにstrすると画面に文字が出る) に文字列を出力したりサブルーチンを書くための基礎を知ったり、ということをします。アセンブリ言語はいきなりハードウェアが露になるので、c-lessonでは必要なものを必要なだけ、すこしづつ導入・解説していくスタイルをとります。これがいま思うと絶妙な匙加減で、読み通すといつのまにかちょっとしたサブルーチンを書けるくらいにはアセンブリ言語が読み書きできるようになります。

この状態で、むしろ最初の入門時に読んだ本を読み直すといろいろ学びが多そうですね (ソフトウェア割り込みの処理の仕方とか書いてある。あと本の最後のほうでversatilepbが登場してたけで読んだ記憶がない…)。

バイナリデコードの愉しみ

バイナリ解読ってたのしいですよね。もう何年まえだかわかりませんが人生で初めてバイナリハックしたときのこと - shinchoku-advent-calendar-2020で書いたようにたのしいことは知っていました。

が、c-lessonではなんとアセンブラ入門で実行していたバイナリをデコードしていきます。そして提示されるARM7のデータシート150p。うへあ。冷静に考えるとx86のマニュアルは1000p超え (もっと上でしたっけ?) なので、だいぶやさしいほうです。c-lessonでの解説を読み、データシートの対応するところだけを拾い読み、なんとかすすめていきます。だんだんobjdumpとも仲良くなれた気がしてきます。そうすると、データシートがだんだん読めるようになってくる。超たのしい。

ただしバイナリの全ワードを手で解読していくのは、だんだんだるくなってきます (え? そんなことはない?)。そこで逆アセンブラの登場というわけです。

逆アセンブラ・アセンブラ

逆アセンブラの実装では、アセンブリ入門で書いたコードをarm-none-eabi-xxx系コマンドでqemu向けに変換したバイナリを読み込んでデコードし、アセンブリコードを出力します。だんだんと最後まで読み込めるバイナリが増えていくことの喜びを何と言えばよいでしょう。快☆感。アセンブラの実装ではそれに加えて、生成物がqemu上でほんとに動くという快感がついてきます。これは大きい。特に、サブルーチンをblできるようになってくるあたりから、格段に楽しくなってきます。

逆アセンブラ・アセンブラの実装のなかで、それぞれ以下の点は学びでした。

  1. 4ビットローテートで表現された大きな即値の表現方法
  2. 二分木による辞書
  3. アセンブラにおけるラベルの解決のしかた

1のビットローテートは発想がとても勉強になりました。大きな数値は細かい指定をすることはないというヒューリスティックにより、そのビット数で表現できるよりも大きな絶対値の数を詰め込む手法です。これを考えたひとは天才なのか…! と感心していました。

3は、以前CPUエミュレータもどきをつくっていたときのアセンブラで解決できなかった問題でした。この当時は自分が抱えている問題を正しく捉えられてもいなかったのですが、今回c-lesson実装してデバッグをしたことによりラベルの解決という定型問題の認識と解決方法が身についたため、いろんな場面でたのしめそうです。

ちなみに逆アセンブラを書くときに大きな失敗と大きな学びがあったのですがそれは後ほど…。

GNU makeおよびMakefile

第一回のときはGNU makeをあまり知らなすぎてこんなシェルスクリプトでプログラムをビルドしていましたが、さすがにMakefileくらい覚えたほうがいいんでないのということで、第二回ではちゃんとGNU makeについて調べ、Makefileを書いてみました。

GNU makeあるのにシェルスクリプトでオプション解析をするの無駄だなと思ってたんですが、サフィックスルールのおかげでかなり楽ができたので、よかったです。これからも使っていこうと思いました。いや、ninja系列にいくかもしれませんが……。

YAGNIふたたび

ぼくはいままでYAGNIの原則とインクリメンタルな開発について理解していなかったようです。なんもわかっとらんかった。それが発覚するのは逆アセンブラをつくっていたときのことです。

逆アセンブラの実装初期、ぼくはすごく苦労していました。そのときの逆アセンブラのコードはこれです。データシートの命令一覧の外形をあるていど理解できるまで読みこんだあと、それをそのままコードに落とし込んでいっています。ここまで書くのにかなり時間を使っていますが、それなのにまだmov命令しかデコードできていません。

これはYAGNIにすごく反している、典型的な失敗パターンに陥っているコードだという指摘をいただきました。そして、知るのです…YAGNIがなぜ大事なのかを…。

YAGNIに反するということは未来を予測した実装をするということで、将来の変更をむずかしくするということです。ぼくの失敗コードでは、命令タイプを判別し、対応する関数でさらにその中でのタイプ判別をし、最後にmov命令として逆アセンブルを開始しています。はたしてこれは最終的に正しいコードとして残るでしょうか。実装している段階ではわからないですよね。データシートを読んで概要を把握したと思ってそれをswitch文として書き下したまま続けたとして、後になってそれが正しくなかったということはありえます (実際ぼくの失敗コードも最初はswitchでしたがif-elseに変更しています) 。そんなとき、全体の構造が既に予想 (しかもたいてい間違っている) で 書かれてしまっていたら、それを正しい形に書き直していくのは大きなコストを発生させます。要はすごくめんどい。小さな範囲を必要なだけ対応していけば、何かあったときも変更は容易です。そこがYAGNIに反しないことの意味でした。

インクリメンタル開発もこの考え方を中心に据えて行うものです。ここをぼくは理解していなかったのです。

なので実装中にYAGNIな気配を感じとり、如何に最小の変化に抑えて問題に対処するかが大事になってくるのです。

そんなわけで、以前にオススメしていただいていたこともあり、ついにぼくは『Code Complete 第2版 完全なプログラミングを目指して』を購入し、すこしずつ読みはじめたのでした。もちろん紙版でな!

感想

第一回完了が2021年7月21日で今日は2021年9月10日なので、第二回にはだいたい1ヶ月ちょっとかかったということになります。いやーC言語たのしい。低レイヤもたのしい。今回のアセンブリ言語の勉強によって以前頓挫したForthの実装を再開できるだけの基礎知識がついたな、という感覚があります。また、versatilepb上でミニミニOSもどきをつくってみるのもアリかもしれない。それってForthでは。まあよし。ともあれ夢がひろがりんぐでございます。

しかしまだ第三回が残っております。こちらはリンカやローダのしくみを勉強してスタックウォーキングをしたりJITしたりするという、c-lessonの集大成な内容になっています。たのしそうなのでそのまま取り掛かってみる所存です。

また2ヶ月とかかかるんじゃないかしら。完走したらまた記事を書きますので、そのときに結果がわかることでしょう。

karino2さんが公開しているC言語教室の第二回「簡易アセンブラとディスアセンブラを作ろう」を完走したので、感想やら反省やらを書きました。

タグ: プログラミング , C言語 , 言語処理系

作成日時: 2021-09-10T10:16:31.467047+09:00

更新日時: -