Kotlin Bytecodeを読みたいけど、何から勉強したらいいかわからないあなたへ。ASMのドキュメントを読みましょう。
はじめに
このblog postは半年以上下書き放置していて自分の中では旬がすぎた話題ではありますが、最近Android系日本語技術podcastであるdex.fmのep.66でbytecodeが話題になったので、勿体無い精神で公開します :pray: それではどうぞ。
Kotlin書いているとたまに「これ、JavaのBytecodeだとどう変換されて動いているんだろう?」と思う時がしばしばある。
例えばこれとか
で、Android Studioの "Show Kotlin Bytecode" 機能を使ってbytecodeをのぞいてみるんだけど、完全に雰囲気で読んでいたので、わかるようなわからないようなゆるふわな感じだった😇これの意味は?とか聞かれても説明するなんて無理〜〜と言う程度の理解レベル。
Javaのbyte codeの読み方ちゃんと勉強したいな〜 `L0` とか `L1` とかよくわかってない。byte code勉強会誰かやってくれないかな…
— むーむー/Atsuko FUKUI (@muumuumuumuu) 2018年6月24日
何がつらいかっていうと、ググってもいい感じのリファレンスがないというか、まず何を読むべきかが見つけられないという状況😩😩😩 しかし軽い気持ちで呟いた結果、神々が降臨した。圧倒的感謝 :pray: 1
あの L0 とか L1 は、対応するブロックを表すための表記で Java のバイトコードの仕様とは別な気がします。 javap -c -l <クラスファイル> で表示すると、Bytecode Viewer との対応がすこし分かりやすくなるかもしれません。
— Hiroshi Kurokawa (@hydrakecat) 2018年6月24日
L0とかラベリングが入るのはjavapでなくasmを使ってるときの可視形態なので、https://t.co/sRtLnFl7Np を読むともっとわかりやすいと思います
— おなかすいた (@red_fat_daruma) 2018年6月24日
というわけで、Bytecodeに対してゆるふわな理解しかない人が勉強して行った記録を残していく。間違ったこと書いてたら教えてくださいmm
手取り早くbytecodeを理解した人へ
ASM User GuideのAppendixにBytecode instructionsがまとまっているので、ここを読みましょう。
ASMとは?
まずは公式の定義から。
ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused on performance. Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).
KotlinのコンバイラはASMライブラリを使ってbytecodeを生成している。なのでKotlinで生成されたBytecodeを読みたいだけだったら、ASMが何をしてどういう生成物を吐き出すか知っていれば良いっぽい。
余談だけどASMはOSSで、しかもGithubではなくGitlabでhostingされている。
User Guide
まずは使う側としてUser Guide読むことにする。めちゃめちゃ長いPDFになっている。
https://asm.ow2.io/asm4-guide.pdf
読んでて面白かった部分を書いていく。なんかたまにルー大柴っぽくなった気がする。気になる人は原文読んでください…
1. Introduction
- ASMはできるだけ早くて小さくて堅牢というのを目指している
- ASMライブラリの目的はbyte arrayで表現されるコンパイルずみのJava classesを生成、変換、分析すること
- class loading processは対象外
- ASMという名前に意味はない。Cの
__asm__
keyword からとっただけ - core APIはevent basedなクラス表現
- tree APIはobject basedなクラス表現
- この辺 見ると、Kotlin compilerはtreeの方使ってるのかな?という感じがする
Tree API
(2. Classes
3. Methods
4. Metadata
5. Backward compatibility
の章はCore APIについてだったのでスキップ)
6. Classes
7. Method
- methodの中身はInsnListでinstructionが記述されていく
- こいつが
getOpCode()
とかを持っている getNext()
があるからjumpが簡単にできる
- こいつが
- Label, frame, line numberもinstructionではないけど
AbstractInsnNode
のサブクラスとして表現される- これにより実instructionsの前とかにLabelとかをinsertすることができる
8. Method Analysis
- ASMで使われるcode analysis techniqueには
data flow analysis
とcontrol flow analysis
がある data flow analysis
- methodのexecution frameのstateを計算する。
- forward analysisとbackward analysisがある
- stackから値をpopして計算して結果をstackにpush
- interpreterやJVMのように見えるが、違いは可能性がある全てのpath, argumentについてsimulateする
- manipulated valueは可能性がある値の集合になるのでめっちゃ大きくなる。
- 例)intergerをP="positive or null", N="negative or null, A="all integers" として表すと
IADD
instructionはoperandがどちらもPだったらPを返し、どちらもNだったらNを返し、そのほかのケースは全てAを返す
control flow analysis
- methodのcontrol data flow graphを計算し、このgraphを解析する
- graphはbasic blockにdecompileされる。それぞれのbasic blockは(最初のblockを除き)jumpの対象となりうる。
- Interface and components
- stackからのpopとかpushはframeworkで、valueをcombineしたり集合の計算とかはInterpreterとかValueと行ったuserが定義したsubclassで行われる
- Basic Data Flow Analysis
definition | means |
---|---|
UNINITIALIZED_VALUE | all possible values |
INT_VALUE | all int, short, byte, boolean or char values |
FLOAT_VALUE | all float values |
LONG_VALUE | “all long values” |
DOUBLE_VALUE | “all double values” |
REFERENCE_VALUE | “all object and array values” |
RETURNADDRESS_VALUE | is used for subroutines (see Appendix A.2) |
- Appendix
- 上記にも書いたが、ここにinstructionsとその操作例がまとまっているのでここを見ればだいたいbytecodeが読めるようになっている
Developer Guide読んでいく
こっちの方が短いけど難しい。3割くらいしか理解できていない気がするのでさらっとメモだけ。 ASM - Developer Guide
Main Algorithms
- class loader
- constant poolと(constructorの中の)bootstrap methodをparseする
- classをparseする
- class attributeをparseする
- class attributeに対応したvisit methodをコールする
- fieldについて上記と同じようなことをする
- methodについて同上
- ただしattributeはparseした後local variableにstore
- labelを探してstore
- instructionsをparse
- 3.4.3 An example
- 要約するのがむずいのでこの実例を見てくれたらなんとなくわかる
以上、わかるようなわからないような状態から一歩踏み出すためのリファレンス集でした。