0
0

More than 1 year has passed since last update.

JavaScriptのバイトコード(逆アセンブル疑似コード)の見方

Posted at

Node.jsをインストールしてコマンドプロンプト(もしくはWindows Power Shell)から以下のコマンドを打ってください。

# node --print-bytecode <ファイル名>.js

基本的なことですが、JavaScriptはスクリプト型言語だが、内部ではバイトコードが生成されて、そのバイトコードがさらにネイティブなマシンコードに変換され高速に動作するようになっています。

バイトコードは仮想的なマシンコードでレジスタをもつスタックマシンとして動作します。

まあ堅苦しいことは抜きにして、実際に簡単なサンプルコードで早速バイトコードを読んでみましょう。

なおここで言っているバイトコードとは、V8のバイトコードになります。

V8はGoogleのオープンソースJavaScriptエンジンです。Chrome、Node.js、およびその他の多くのアプリケーションでV8が使用されています。

■簡単なJavaScriptのサンプルコードとそのバイトコードの対応
【JavaScriptコード】

  1 var a=1999;
  2 var b=3121;
  3 var c=659;
  4 function muladd1(x,y,z) {
  5         let r=x+y*z;
  6         return r;
  7 }
  8 function muladd2(x,y,z) {
  9         let r=x*y+z;
 10         return r;
 11 }
 12 function func_example(x,y,z) {
 13         var d=muladd1(x,y,z)+muladd2(x,y,z);
 14         if (d==341) {
 15                 return 333;
 16         }
 17         return 777;
 18
 19 }
 20
 21 var e=func_example(a,b,c);
 22 console.log(e);

【バイトコード】
1行目~3行目、21行目~22行目

1行目~3行目、21行目~22行目
    StackCheck                                     # スタックチェック
    LdaSmi.Wide [1999]                             # 即値1999をアキュムレータレジスタにロードする
    StaCurrentContextSlot [4]                      # アキュムレータレジスタからCurrentContextSlot[4]にストア
    LdaSmi.Wide [3121]                             # 即値3121をアキュムレータレジスタにロードする
    StaCurrentContextSlot [5]                      # アキュムレータレジスタからCurrentContextSlot[5]にストア
    LdaSmi.Wide [659]                              # 即値659をアキュムレータレジスタにロードする
    StaCurrentContextSlot [6]                      # アキュムレータレジスタからCurrentContextSlot[6]にストア
    LdaImmutableCurrentContextSlot [4]             # CurrentContextSlot[4]からアキュムレータレジスタにロードする
    Star r4                                        # アキュムレータレジスタからレジスタr4にストア
    LdaImmutableCurrentContextSlot [5]             # CurrentContextSlot[5]からアキュムレータレジスタにロードする
    Star r5                                        # アキュムレータレジスタからレジスタr5にストア
    LdaImmutableCurrentContextSlot [6]             # CurrentContextSlot[6]からアキュムレータレジスタにロードする
    Star r6                                        # アキュムレータレジスタからレジスタr6にストア
    CallUndefinedReceiver r0, r4-r6, [0]           # func_example関数(レジスタr0にアドレスが入っている)を呼び出す
                                                   # 実引数リストは、r4-r6番に入っている
    Star r1                                        # 返却値が入っているアキュムレータレジスタをレジスタr1にストア
    LdaGlobal [4], [2]                             # グローバル変数eのアドレスをアキュムレータレジスタにロード
    Star r4                                        # アキュムレータレジスタをレジスタr4にストア
    LdaNamedProperty r4, [5], [4]                  # 名前付けられた属性(r4)をアキュムレートレジスタにロード
    Star r3                                        # console.log関数のアドレスをレジスタr3にストア
    CallProperty1 r3, r4, r1, [6]                  # レジスタr4の属性でレジスタr1のある値を引数にしてレジスタr3の関数を呼び出す	
    LdaUndefined                                   # 定数"undefined"をアキュムレータレジスタにロードする
    Return                                         # アキュムレータレジスタの値を返却する
Constant pool (size = 6)

[generated bytecode for function: muladd1]
Parameter count 4
Register count 1
Frame size 8
    StackCheck                                     # スタックチェック
    Ldar a2                                        # 第3引数が入ったレジスタa2をアキュムレートレジスタにロードする。
    Mul a1, [1]                                    # アキュムレータレジスタと第2引数が入ったレジスタa1を掛け算して
                                                   # アキュムレータレジスタに結果を格納する
    Add a0, [0]                                    # アキュムレータレジスタと第1引数が入ったレジスタa0を足し算して
                                                   # アキュムレータレジスタに結果を格納する
    Star r0                                        # アキュムレータレジスタの値をレジスタr0にストアする
    Return                                         # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)

[generated bytecode for function: muladd2]
Parameter count 4
Register count 2
Frame size 16
    StackCheck                                     # スタックチェック
    Ldar a1                                        # 第2引数が入ったレジスタa1をアキュムレートレジスタにロードする。
    Mul a0, [1]                                    # アキュムレータレジスタと第1引数が入ったレジスタa0を掛け算して
                                                   # アキュムレータレジスタに結果を格納する
    Star r1                                        # アキュムレータレジスタをレジスタr1にストアする
    Ldar a2                                        # 第3引数が入ったレジスタa2をアキュムレートレジスタにロードする。
    Add r1, [0]                                    # アキュムレータレジスタとレジスタr1を足し算して
                                                   # アキュムレータレジスタに結果を格納する
    Star r0                                        # アキュムレータレジスタの値をレジスタr0にストアする
    Return                                         # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)

[generated bytecode for function: func_example]
Parameter count 4
Register count 6
Frame size 48
    StackCheck                                     # スタックチェック
    LdaImmutableCurrentContextSlot [4]             # CurrentContextSlot[4](muladd1の関数アドレス)からアキュムレータレジスタにロードする
    Star r1                                        # アキュムレータレジスタをレジスタr1にストアする
    Mov a0, r2                                     # 第1引数が入ったレジスタa0をレジスタr2に移動する
    Mov a1, r3                                     # 第2引数が入ったレジスタa1をレジスタr3に移動する
    Mov a2, r4                                     # 第3引数が入ったレジスタa2をレジスタr4に移動する
    CallUndefinedReceiver r1, r2-r4, [1]           # muladd1関数(レジスタr1にアドレスが入っている)を呼び出す
                                                   # 実引数リストは、r2-r4番に入っている
    Star r1                                        # 返却値が入っているアキュムレータレジスタをレジスタr1にストア
    LdaImmutableCurrentContextSlot [5]             # CurrentContextSlot[5](muladd2の関数アドレス)からアキュムレータレジスタにロードする
    Star r2                                        # アキュムレータレジスタからレジスタr2にストア
    Mov a0, r3                                     # 第1引数が入ったレジスタa0をレジスタr3に移動する
    Mov a1, r4                                     # 第2引数が入ったレジスタa1をレジスタr4に移動する
    Mov a2, r5                                     # 第3引数が入ったレジスタa2をレジスタr5に移動する
    CallUndefinedReceiver r2, r3-r5, [3]           # muladd2関数(レジスタr2にアドレスが入っている)を呼び出す
                                                   # 実引数リストは、r3-r5番に入っている
    Add r1, [0]                                    # アキュムレータレジスタとレジスタr1を足し算して
                                                   # アキュムレータレジスタに結果を格納する
    Star r0                                        # 返却値が入っているアキュムレータレジスタをレジスタr0にストア
    LdaSmi.Wide [341]                              # 即値341をアキュムレータレジスタにロードする
    TestEqual r0, [5]                              # アキュムレータレジスタとレジスタr0が等しいかテストする
    JumpIfFalse [7] (000000AA7251FD80 @ 58)        # もし比較結果がFALSEなら000000AA7251FD80番地にジャンプする
    LdaSmi.Wide [333]                              # 即値333をアキュムレータレジスタにロードする
    Return                                         # アキュムレータレジスタの値を返却する
000000AA7251FD80 @ 58
    LdaSmi.Wide [777]                              # 即値777をアキュムレータレジスタにロードする
    Return                                         # アキュムレータレジスタの値を返却する
Constant pool (size = 0)
Handler Table (size = 0)

大分、JavaScriptのバイトコードに慣れてきたのではないでしょうか。

JavaScriptのバイトコードに関する仕様書やドキュメントらしきものはないため、何かのお役に立てばよいかと思っています。

■各バイトコード命令の簡単な意味
●StackCheck
スタックポインタがスタックのlimit値を超えているかどうかチェックする命令
・・・工事中・・・

■その他
●意味不明な[数値]
関数のいわゆるフィードバックベクトルのインデックス。
フィードバックベクトルには、パフォーマンスの最適化に使用される実行時情報が含まれているが、バイトコードを読む分にはとりあえずは無視してよい。

■参考文献
Understanding V8’s Bytecode by @fhinkel
Ignition: V8 Interpreter
V8 Iginition Interpreter - abcdefGets
The Journey of the Code

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0