Erlang でモジュールをディスアセンブルする

自分用メモ。全然ドキュメントがないので困っている。OTP のソースを眺めていたら beam_disasm というモジュールを発見した。どうもこれをつかえばいいようだ。

test.erl

-module(test).
-export([add/2, add/3]).

add(X, Y) -> X + Y.

add(X, Y, Z) -> add(add(X, Y), Z).

上記を作成したあと、erl で

1> c(test).
{ok,test}
2> import(beam_disasm).
ok
3> beam_disasm:df(test).
ok
4> q().
ok

とかすると、test.beam と一緒に test.dis というファイルができている。

test.dis の中身:

{file,"/tmp/test.beam"}.

{function,add,2,2}.
  {label,1}.
    {func_info,{atom,test},{atom,add},2}.
  {label,2}.
    {gc_bif,'+',{f,0},2,[{x,0},{x,1}],{x,0}}.
    return.

{function,add,3,4}.
  {label,3}.
    {func_info,{atom,test},{atom,add},3}.
  {label,4}.
    {allocate,1,3}.
    {move,{x,2},{y,0}}.
    {call,2,{test,add,2}}.
    {move,{y,0},{x,1}}.
    {call_last,2,{test,add,2},1}.

{function,module_info,0,6}.
  {label,5}.
    {func_info,{atom,test},{atom,module_info},0}.
  {label,6}.
    {move,{atom,test},{x,0}}.
    {call_ext_only,1,{extfunc,erlang,get_module_info,1}}.

{function,module_info,1,8}.
  {label,7}.
    {func_info,{atom,test},{atom,module_info},1}.
  {label,8}.
    {move,{x,0},{x,1}}.
    {move,{atom,test},{x,0}}.
    {call_ext_only,2,{extfunc,erlang,get_module_info,2}}.

見たところ、

{function,module_info,1,6}.
  • 2 番目の要素が関数名
  • 3 番目の要素が arity
  • 4 番目の要素が開始ラベル番号

となっているようだ。

頻繁に出てくる {x,0}、これは多分レジスタだろう。そうだとすると、スタックマシンじゃないのが意外。いや、もしかしてバイトコードレベルではレジスタで、VM はスタックマシンってことがあるかもしれないけど (あるのか?)

で、

    {move,{x,2},{y,0}}.
    {call,2,{test,add,2}}.
    {move,{y,0},{x,1}}.
    {call_last,2,{test,add,2},1}.

この部分から類推するに {y,0} はローカル変数。

  1. {x,2} (3 番目の引数を) {y,0} に待避する (次の call で破壊される可能性があるので)
  2. {x,0} {x,1} はそのままに add/2 を呼び出す
  3. 2. の結果は {x,0} に入っているので、{x,1} に {y,0} の内容を代入し
  4. もう一度 add/2 を呼び出す

ということをしてるんじゃないかと。

時間ができたらもっと調べてみる。