アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

debug_traceTransactionとprogram counterとsource mapping

y-nakajo.hatenablog.com
今回は上記の以前の記事で触れた、Ethereumのdebug機構について調べたことをまとめていきます。
Ethereumでは任意のtxをdebugするための機能が、またSolidityではbyte codeとソースファイルをmappingするための情報が提供されています。
今回はこれらの情報について説明します。

[JSON-RPC] debug_traceTransaction

Ethereumではほとんどのnode実装がdebugのための情報を提供するためにdebug_traceTransactionというJSON-RPCを実装しています。
debug_traceTransactionは引数に指定したtransaction hashのtransactionを実行した時の状態をtraceし、その結果としてstep単位での実行されたopcode、program counter、stackの状態、memoryの状態、storageの状態を返します。

stack, memory, storageの情報は膨大になる可能性があるので、それぞれoptionでdisableにすることもできます。
より詳細な仕様については以下を参照してください。
github.com

注意点

debug_traceTransactionはnodeがフルノードであり、指定されたtransactionが実行された時のstateDBの状態を持っている場合にtrace情報を取得できます。つまり以下のような場合にはtrace情報を取得できません。

  • fast modeで同期した時に切り捨てらてた情報に該当するtransactionを指定した時
  • stateDBに影響を与えないtransaction (constantな情報を取得するだけのtransactionが該当)

現状はsendTransactionで発行されたtransaction(の一部)のみに対してdebug_traceTransactionが発行可能です。

debug_traceTransactionのgo-ethereum上での実装コードはこちら
go-ethereum/api_tracer.go at master · ethereum/go-ethereum · GitHub

[EVM] program_counter

program_counterとはContractをEVMが実行する時の実行コードの位置を指し示すものです。JUMP命令などは引数としてJUMP先のprogram counterを指定します。

program counterは基本的に、bytecodeを1byteずつの配列にした時のindex値になります。ただし、PUSH系命令の場合はその後にdataが続くため注意が必要です。program counterはdata部分も含めて連番で振られるためdata部分に該当するindex値は無効なprogram counterとしなければなりません。

より詳しい説明は以下の公式ドキュメントを参照ください。
Ethereum-Development-Tutorial#state-machineEthereum-Development-Tutorial#state-machine

[Solidity] source mapping

debuggerを作るだけであれば、上述したget_traceTransactionで取得した情報のみで十分です。
ですが、エラーが発生した時にソースコードの該当箇所を表示する といったtoolを作りたい場合はopcodeとsourceコードのmapping情報が必要になります。
ここではSolidity compilerが生成してくれるsource mappingについて説明します。

仕様については以下をご参照ください。
Solidity Documents#source-mappings

source mappingの種類

.solファイルをcompileすることで生成されるjsonファイルに含まれるsourceMapは以下の2種類があります。

  • sourceMap: Contract Creationの時に実行されるbytecodeに対応するsourceMap (Contractをdeployした時に実行されるコード)
  • deployedSourceMap: ContractのMethod呼び出しの時に実行されるbytecodeに対応するsourceMap

ContractをDeployする時と、deploy後のContractのmethodを呼び出す時で実行されるbytecodeが大きく違うため、それぞれのsourceMapが用意されています。(bytecodeも同じように2種類用意されています。)

sourceMapのフォーマット

sourceMap以下のフォーマットを1itemとし、実行されるopcodeの個数分生成されます。

start:length:fileIndex[:jumpSIgnature]
  • start: opcodeが生成された元となるソースコードのファイルないのstart位置
  • length: opcodeが生成された元となるソースコードの長さ
  • fileIndex: opcodeが生成された元となるソースコードが記述されているfileのindex
  • jumpSignature: jump命令だった場合のsignature(o: JUMP, i: JUMPI, -: JUMPDEST)

s:l:f[:j]を1itemとして、実行されるopcodeの個数分 ';'を区切り文字として連結されます。
また、情報量を削減するため以前のopcodeに対するmapping情報が同じ場合は省略されます。

1:9:1;1:9:1;1:9:1;2:7:1;

つまり上記のようなsourceMapsの場合は次のように省略されます。

1:9:1;;;2:7;

sourceMapとopcodeの突きあわせ方法

sourceMapと実際のopcodeを対応させるためにはbytecodeを変換させる必要があります。
理由としては、sourceMapはprogram counterを持っておらずまた、sourceMapのsize = bytecode sizeではないためです。
sourceMapのsizeはbytecode size - data size - 43と一致します。
data sizeとはopcodeがPUSH1 ~ PUSH32の時にその後に続くdataの総byte数になります。
43byteはContract Metadataです。詳細は以下を参照ください。
Solidity Documents#encoding-of-the-metadata-hash-in-the-bytecode

以下のロジックで実行命令のみを持つ配列を生成することではじめてsourceMapとopcodeを1対1でmappingすることができます。

  1. bytecodeの先頭から1byteずつopcodeをチェックする
  2. PUSH以外のopcodeが見つかったら配列に追加
  3. PUSH1 ~ PUSH32のPUSH系のopcodeが見つかったら、PUSHコードだけ配列に追加する。その後、データバイト数だけbytecode indexをずらす
  4. 1-3をSTOPが見つかるまで繰り返す

なお、truffle-code-utilsを使うことで上記処理をやってくれます。

file indexの求め方

次にfile indexから該当のソースファイルを見つける必要があります。file indexは以下のルールに従ってつけられています。

  1. compileで生成されたJSONファイルのsource.idの順番にソースファイルを並べる
  2. 上から順に0から番号をふる

です。素直にfile idでつけてくれていたら探索が簡単なのですがなぜかidではなく上記ルールに従ってソースファイルをソートした時のindex番号が振られています。

なお、開発途中でcompileを繰り返したり、手動で個別に1ファイルずつコンパイルするとfile indexがおかしな値になります。その場合は、build/contract/*以下を全て削除してから solc contracts/*もしくはtruffle compileをすればfile indexが振り直されます。

該当行数の求め方

sourceMapのstartからstart + lengthまでの間のソースコードが該当するopcodeの生成元のコードになります。ただし、このstartとstart + lengthはbyte数を表しています。
そのため、ソースコードの該当行数を求める場合は、別途、'¥n'をカウントするなりして、startからlengthまでの間のデータが何行目に位置しているのかをmappingする必要があります。

まとめ

今回は、Truffle-debuggerやRemixなどで使われているdebuggerの機能を作るために必要となる情報について五月雨式に説明しました。
情報が多岐に渡り、且つそれぞれ分散しているため非常にわかりにくい記事になってしまいました。。。。

Solidity及びEVM周りのデバッグのための機能はかなり作りこまれているということがわかりました。とはいえまだまだ欲しい情報や、改善すべき点も残っています。逆に言えばデバッガーツールはもっと使いやすいものへと改善すべき余地が残っているということです。
今後のスマートコントラクトの開発環境がどのように進化していくのか非常に楽しみです。