トップへ戻る
公開日
2017年5月11日
筆者:Curvegrid
ジェフ・ウェントワース

1日目: evmdis、Solidityのディスアセンブラ

SolidityはEthereumブロックチェーンのスマートコントラクト言語です。solcコンパイラによってバイトコードにコンパイルされます。

SolidityはEthereumブロックチェーンのスマートコントラクト言語です。SOLCコンパイラによってバイトコードにコンパイルされます。期待通り、コンパイルされたバイトコードはコンピュータ、つまりEthereumブロックチェーンに参加しているすべてのノードに分散されているEthereum仮想マシン(EVM)によって実行されることを意図しています。バイトコードであるため、人間が読めるようなオリジナルのソースコードのコンテキストが欠けています。

スマートコントラクトの Solidity バイトコードを コンパイルしたものだけがあれば 何をするかわかるのか?それが何をするかについてのドキュメントがあれば、それは素晴らしいことです。しかし、それが欠けていたり、不完全だったり、信用できなかったりするとどうなるでしょうか?しかし、多くのスマートコントラクトは複雑で、他のスマートコントラクトやハードコードされたEthereumアドレスとリンクしています。

ここではevmdis READMEの例に基づいたSolidity, Test1.solの非常に簡単な例を示します。


pragma solidity ^0.4.0;

contract Test {
    function double(uint a) returns (uint) {
        return multiply(a, 2);
    }

    function triple(uint a) returns (uint) {
        return multiply(a, 3);
    }

    function multiply(uint a, uint b) internal returns (uint) {
        return a * b;
    }
}

そしてここでは、Solidity コンパイラである solc を使ってバイトコードに組み立てられています。


$ solc --optimize --bin-rununtime Test1.sol
警告。これはプレリリース版のコンパイラですので、本番では使用しないでください。

======= Test1.sol:Test =======
ランタイム部分のバイナリ。
606060405263ffffffff60e060020a600035041663eee972068114602a578063f40a049d14604c575bfe5b3415603157fe5b603a600435606e565b60408051918252519081900360200190f35b3415605357fe5b603a6004356081565b60408051918252519081900360200190f35b600060798260026094565b90505b919050565b600060798260036094565b90505b919050565b8181025b929150505600a165627a7a723058202ea94b4449362217eab191a18d83fb2fb5e7c432a58cb3f0990ec4306e49b65a0029

バイトコードを逆アセンブルして、各オペコードを人間が読める命令に変換するツールを使ってみることもできます。その結果、自動生成された変数名や、ループや分岐のような簡潔で高レベルな構造体が、冗長な長いアセンブリ命令の形に最適化されているため、かなり分かりにくいものになるでしょう。


$ solc --optimize --asm Test1.sol | head -n 50
Warning: This is a pre-release compiler version, please do not use it in production.

======= Test1.sol:Test =======
EVM assembly:
    /* "Test1.sol":25:300  contract Test {... */
  mstore(0x40, 0x60)
  jumpi(tag_1, iszero(callvalue))
  invalid
tag_1:
tag_2:
  dataSize(sub_0)
  dup1
  dataOffset(sub_0)
  0x0
  codecopy
  0x0
  return
stop

sub_0: assembly {
        /* "Test1.sol":25:300  contract Test {... */
      mstore(0x40, 0x60)
      and(div(calldataload(0x0), exp(0x2, 0xe0)), 0xffffffff)
      0xeee97206
      dup2
      eq
      tag_2
      jumpi
      dup1
      0xf40a049d
      eq
      tag_3
      jumpi
    tag_1:
      invalid
        /* "Test1.sol":45:122  function double(uint a) returns (uint) {... */
    tag_2:


特に読みやすいというわけではありません。

スマートコントラクトのソースコードを持っていて、ブロックチェーン上にあるコンパイル済みのスマートコントラクトと同じか類似しているかどうかを確認したいとします。ソースコードをバイトコードにコンパイルして、バイト単位で比較することができます。なぜなら、コンパイラは最適化が導入されるとバージョン間で非決定的になる傾向があるからだ。同じシステム上で2つの異なる実行を行った場合でも、コンパイルされたバイトコードは異なるものになる可能性があります。Solidity は決定論的であることを目標としていますが、常にそうではありませんでした。The DAO の場合、配置された DAO バイトコードがコンパイラの非決定性によって困難になったソースコードと一致しているかどうかを検証するための大規模な努力が行われました。

Etherscanには、デプロイされたスマートコントラクトのバイトコードとソースコードを検証するための便利なオンライン機能があることは注目に値します。これにより、コンパイルを試みるための solc タグの全範囲を選択することができます。

オフラインでのスマートコントラクト検証のために、Nick Johnson氏のevmdisはSolidityバイトコードのディスアセンブラであり、ディスアセンブルとは少し異なるアプローチをとっています。evmdisは抽象解釈と呼ばれる静的解析技術を実装しており、Solidityバイトコードのサンプルの実行をシミュレートします(このテーマに関する別の有用なチュートリアル[PDF]はこちら)。evmdis readmeには良い要約がありますが、基本的にはプログラムを実行し、プログラムのスタックのユニークな順列を追跡します。また、プログラムを論理的な基本ブロックに分割し、一連の単純な式を複合式に変換します。出力されるのは、論理ブロックに整理されたアセンブリ命令とジャンプラベルのより簡潔なシリーズで、一種の「要約されたアセンブリ」であり、人間がより簡単に分析したり推論したりすることができます。

このことからいくつかの教訓を得ました。まず、Solidity コンパイラの solc に渡すべき重要なオプションがいくつかあります。


solc --version
solc, solidity コンパイラのコマンドラインインターフェイス
バージョン:0.4.11-develop.2017.4.26+commit.c3b839ca.Darwin.appleclang

$ solc --bin-run-time --optimize -o .Test1.sol
警告。これはプレリリース版のコンパイラですので、本番では使用しないでください。

# 出力ファイルに名前を付ける際に、Solidityのソースファイル名の末尾の数字を省略することに注意してください。
cat Test.bin-run-time
606060405263ffffffff60e060020a600035041663eee972068114602a578063f40a049d14604c575bfe5b3415603157fe5b603a600435606e565b60408051918252519081900360200190f35b3415605357fe5b603a6004356081565b60408051918252519081900360200190f35b600060798260026094565b90505b919050565b600060798260036094565b90505b919050565b8181025b929150505600a165627a7a72305820d37021dfa166ba3f7f8d592355b8a9313292e2e008f24cbd45bf273c269f059f0029$

もし--bin-run-timeの代わりに--binフラグだけを指定していれば、solcはスマートコントラクトを自動的にラップして、スマートコントラクト自体をブロックチェーンにロードしていたでしょう。このバイトコードの実行をevmdisでシミュレーションしようとすると、スマートコントラクトではなく「ローダー」コードを実行することになるため、有用な出力は得られません。

optimize フラグは gcc の -O フラグのようなもので、コンパイルされたコードを最適化しようとします。

フラグはカレントディレクトリを出力ディレクトリとして指定します。そうでない場合は、標準出力で余分な出力を削除する必要があります。

次にevmdisをダウンロードしてインストールします。


$ go get github.com/Arachnid/evmdis
$ go install github.com/Arachnid/evmdis/evmdis
$ evmdis
/Users/curvegrid/golang/bin/evmdis
$

evmdisは、solcが出力する生の16進データ(SolidityバイトコードのASCIIベース16表現)がパイプされることを期待しています。


cat Test.bin-run-time | evmdis > Test1.disasm
猫のテスト1.disasm $ cat Test1.disasm
# Stack.[]
0x4MSTORE(0x40, 0x60)
0x13PUSH(CALLDATALOAD(0x0) / 0x2 ** 0xE0 & 0xFFFFFFFF)
0x19DUP1
0x1DJUMPI(:label0, POP() == 0xEEE97206)


まだ超コンパクトではありませんが、生のデコードされたアセンブリよりはマシです。

テスト用のスマートコントラクトに些細な変更を加えて、コンパイルして比較してみましょう。これをTest2.solに入れます。


$ cat Test2.sol
pragma solidity ^0.4.0;

contract Test {
    function double(uint a) returns (uint) {
        return multiply(a, 2);
    }

    function triple(uint a) returns (uint) {
        return multiply(a, 4);
    }

    function multiply(uint a, uint b) internal returns (uint) {
        return a * b;
    }
}

# because solc strips trailing numbers off of source code file names when it outputs them
$ mv Test.bin-runtime Test1.bin-runtime

$ solc --bin-runtime --optimize -o . Test2.sol
$ mv Test.bin-runtime Test2.bin-runtime
$ diff Test1.bin-runtime Test2.bin-runtime
1c1
< 606060405263ffffffff60e060020a600035041663eee972068114602a578063f40a049d14604c575bfe5b3415603157fe5b603a600435606e565b60408051918252519081900360200190f35b3415605357fe5b603a6004356081565b60408051918252519081900360200190f35b600060798260026094565b90505b919050565b600060798260036094565b90505b919050565b8181025b929150505600a165627a7a72305820d37021dfa166ba3f7f8d592355b8a9313292e2e008f24cbd45bf273c269f059f0029
\ No newline at end of file
---
> 606060405263ffffffff60e060020a600035041663eee972068114602a578063f40a049d14604c575bfe5b3415603157fe5b603a600435606e565b60408051918252519081900360200190f35b3415605357fe5b603a6004356081565b60408051918252519081900360200190f35b600060798260026094565b90505b919050565b600060798260046094565b90505b919050565b8181025b929150505600a165627a7a723058209a386daa605597ff9e13819e908aab2cafdc814ff67c34d318c4e7048eb5b9360029
\ No newline at end of file

違いがあることはわかりますが、あまり啓発的ではありません。evmdisの出力を差分してみましょう。


cat Test2.bin-run-time | evmdis > Test2.disasm
diff Test1.disasm Test2.disasm

ラベル7 :ラベル7
# Stack: [@0x58 :label2 @0x13] # Stack.[@0x58 :label2 @0x13] # Stack.[@0x58 :label2 @0x13] # Stack: [@0x58 :label2 @0x13]
0x82 PUSH(0x0) 0x82 PUSH(0x0)
0x84 PUSH(:label6) 0x84 PUSH(:label6)
0x86 DUP3 0x86 DUP3
0x87 PUSH(0x3) | 0x87 PUSH(0x4)
0x8B JUMP(:label8) 0x8B JUMP(:label8)

ここでの違いはかなり明確です。注目すべきは、この時点で配管やmv'ing、ファイルの差分を取ることに飽きてきたことです。私たちはevmdisを拡張したので、このようなことができるようになりました。

のおかげで色が拡散しています。 碁盤目パッケージ.)

ご自由に改造してみてください。solcで自動的にコンパイルされるSolidity、またはASCIIの16進数形式のバイトコードを渡すことができます。必要に応じて、標準入力を解析するオリジナルのevmdis機能が保持されます。1つの入力が提供された場合、それは分解されて表示されます。2つの入力が提供された場合、それらは分解されて比較されます。オプションで、バイトコードとソースSolidity(利用可能な場合)を比較することもできます。


evmdis -h
evmdisの使用法。

evmdis [][ []]

オプションです。
  -感情移入
    	分解されたソリッド性バイトコードを比較します。(デフォルトは true)
  -CMPBC
    	ソリッド性バイトコードを比較します。
  -cmpsol
    	solidityのソースコードを比較します。
  -パッチ
    	パッチフォーマットの違いを色ではなく表示します。
  -solc 文字列
    	solc Solidity コンパイラへのパス。(デフォルトは "solc" です)
  -ソルーション文字列
    	solc に渡すオプション。(デフォルトは "--optimize --bin-run-time")
  -ストディン
    	入力メソッドの一つとして標準入力を強制します。単一のコマンドラインパラメータに加えて標準入力が必要な場合に必要です。

また、Ethereumスマートコントラクトのアドレスを渡す機能を追加し、バイトコードをダウンロードしてそれを入力として使用するようにしました。しかし、これらの修正はサードパーティのサイトをスクレイピングすることに依存しており、彼らの騒音源になりたくないので、公開はしていません。

これにより、以前この記事で述べたことに戻ることができます:デプロイされたDAOスマートコントラクトとそのソースコードを比較します。


evmdis TheDAO.sol 0xbb9b244d798123fde783fcc1c72d3bb8c189413
2017/05/10 16:22:36 ソース 'TheDAO.sol' を解析できませんでした。solidity のコンパイルに問題があります: 終了ステータス 1
警告。これはプレリリース版のコンパイラですので、本番では使用しないでください。
:89:52: Error: Expected token Semicolon got 'RBrace'
    modifier noEther() {if (msg.value > 0) throw; _}
                                                   ^

あ、DAOは2016年4月にデプロイされてsolcバージョンv0.3.1-2016-04-12-3ad5e82でコンパイルされているのに対し、我々はsolc 0.4.11ですね。それ以降、Solidityの前後互換性のない変更があったようです。例えば、以下のようなものです。


$ solc-0.3.2 Test1.sol
Test1.sol:1:1:1: エラー。期待されるインポート指令または契約定義。
プラグマのsolidity ^0.3.0.
^

DAOでは、以前のバージョンのsolcを修正したevmdisで使用することができます。


# 上記の通り、公開したevmdisの修正にはEthereumスマートコントラクトのアドレスを渡す機能が含まれていませんが、これは第三者のウェブサイトに対するノイズの原因になるのを防ぐためです。
evmdis -solc /usr/local/bin/solc-0.3.2 TheDAO.sol 0xbb9b244d798123fde783fcc1c72d3bb8c189413
# Stack.[]
0x4MSTORE(0x40, 0x60)
0xAJUMPI(:label0, !CALLDATASIZE())

# Stack.[]
0x13PUSH(CALLDATALOAD(0x0) / 0x2 ** 0xE0)
0x19DUP1
0x1EJUMPI(:label2, POP() == 0x13CF08B)

# Stack.[@0x13]
0x1FDUP1
0x29JUMPI(:label3, 0x95EA7B3 == POP())

# Stack.[@0x13]
0x2ADUP1
0x34JUMPI(:label5, 0xC3B7B96 == POP())

# Stack.[@0x13]
0x35DUP1
0x3FJUMPI(:label6, 0xE708203 == POP())

# Stack.[@0x13]


# 何行目ですか?
evmdis -solc /usr/local/bin/solc-0.3.2 TheDAO.sol 0xbb9b244d798123fde783fcc1c72d3bb8c189413 | wc -l
    5540

私たちは、出力から、分解されたコードの合計5540行にわたって、多くの変更があることを見ることができます。しかし、これは見てみると多くの変更点がありますが、生の分解コードよりははるかに少ないです。


$ solc-0.3.2 --asm --optimize TheDAO.sol | wc -l
   21513


今のところはここに置いておきます。既存のSolidityソースコードに対してevmdisやsolcをテストする際の課題の1つは、特定のsolcバージョンの実行可能なコピーを見つけることです。私たちはこの演習の一環として solc 0.3.2 を動作させるのに多くの時間を費やしました。