uftrace を使ってプログラムの中身を解析/リバースエンジニアリングする (2) gnumericへ適用編

uftraceでgnumericの中を探る

前回の記事でuftraceについて紹介しました.

今回はそれを使ってgnumericの中身を探る, 具体的には,

セルに入力したときにそのセルの文字列を読み取っている関数を突き止める

ということを目標にします.

Linux上で表計算と言うと, LibreOfficeの方がよく使われているとおもいますが, gnumericの方が軽く, コンパイルもすぐに終わります (gnumericなら数分で終わりますが, LibreOfficeは数時間です). LibreOfficeにはCalc以外のプログラム(WriterやImpress)も含まれているでしょうから, フェアな比較ではありませんが, リバースエンジニアリングの例題としては gnumericの方が手軽です.

実行環境

gnumericのダウンロードとコンパイル

Gnumeric のホームページから, Donwload Gnumeric に行き, Source Code をダウンロードしてください. 2017年12月12日時点での最新版は バージョン1.12.37です.

解凍
$ tar xf gnumeric-1.12.37.tar.xz
configure
ポイントは唯一, CFLAGS=というオプションで-pg -g -O0を指定することです.
$ cd gnumeric-1.12.37
$ ./configure --prefix=$(pwd)/inst CFLAGS="-O0 -g -pg"
-pgは記録のために必須, -gは後にデバッガで実行を追うために必要です. -O0は必須ではありませんがデバッガでの追跡をわかりやすくするために, つけておくことを推奨します. おそらくconfigure時に, 色々足りないパッケージについて文句を言われます.
  • ./configure: line 3809: intltool-update: command not found
    checking for intltool >= 0.35.0...  found
    configure: error: Your intltool is too old.  You need intltool 0.35.0 or later.
    
  • configure: error: *** bison or equivalent is required
    
  • configure: error: *** zlib is required
    
とか. 足りないパッケージを適当にこれかな, といって入れるだけで解決しますが, たくさんあって面倒なので,
$ sudo apt build-dep gnumeric
でまとめて面倒見てもらえます. ちなみに筆者はこれをやっても
configure: error: itstool not found
というエラーが出たのでこれだけは別途インストールしました.
$ sudo apt install itstool
こうして数分間の格闘の後, configureが成功しました.
$ ./configure --prefix=$(pwd)/inst CFLAGS="-O0 -g -pg"
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk

     ... 中略 ...

Configuration:

	Source code location:	.
	Compiler:		gcc
	Compiler flags:		-O0 -g -pg -Wall -Werror=init-self -Werror=missing-include-dirs -Wsign-compare -Werror=pointer-arith -Wchar-subscripts -Wwrite-strings -Wdeclaration-after-statement -Wnested-externs -Wmissing-noreturn -Werror=missing-prototypes -Werror=nested-externs -Werror=implicit-function-declaration -Wmissing-declarations -Wno-pointer-sign -Werror=format-security -Wstrict-prototypes -Wno-error=format-nonliteral
	Floating point type:	double

	UI:			Gtk

	Perl Support:		yes (using perl)
	Python Support:		yes (using python)

	GDA support:		NO.  libgda problem
	GNOME-DB support:	no
	Psiconv support:        missing dependencies

	PDF documentation:	No, not requested.
make
$ make
とすればビルドできますが, 有用なオプションが二つ
-j n
nプロセスまで同時に起動して並列コンパイル. マルチコア環境で使わない手はないです
V=1
コンパイルするときのコマンドラインを表示してくれます. 少し見にくくなりますが-pg -O0 -gオプションが無事行き渡っているか, などを確認したり, コンパイルに失敗したコマンドがどういうコマンドライン だったかを知るのに有用です
筆者のたった2コア(4ハードウェアスレッド)のラップトップで
$ make -j 3 V=1
で実行すると, 5分ほどで終了します.
make install
$ make install
とすると, configure時に--prefixで指定したディレクトリ(inst) の下にプログラムがインストールされます. なお, make installがエラーで終了しますが, これは--prefixをつけてもなお, 一部のファイルを/usr の下, 書き込みにroot権限が必要な場所へインストールしようとするからで, 放っておいてもプログラムは動作します. inst/bin/gnumeric-1.12.37が実行可能ファイル本体, inst/bin/gnumericはそのシンボリックリンクです. uftraceやデバッガにかけるときは後者を使います.

ためしにuftraceで実行

ともかくuftraceで実行してみます.

$ cd inst/bin/
$ uftrace record ./gnumeric-1.12.37

するとgnumericが立ち上がります. 動作確認が目的なので, 少し何か入力したすぐに終了させます.

$ uftrace report

してみると, 多数(1366)の関数が実行されていることがわかります.

$ uftrace report
  Total time   Self time       Calls  Function
  ==========  ==========  ==========  =================================================
    9.756  s   38.206 us           1  main
    9.557  s    7.510  s           1  gtk_main
    1.659  s   14.529 us           1  wbc_gtk_close
    1.659  s   31.542 us           1  wbcg_close_if_user_permits
    1.606  s    1.533  s           1  wbcg_show_save_dialog
  222.785 ms   51.444 us           2  wbc_gtk_new
  136.786 ms  887.414 us           8  item_bar_draw_region
  133.912 ms  133.912 ms         308  ib_draw_cell
  128.812 ms  120.140 ms        1555  scg_scrollbar_config_real
  93.103 ms   65.638 ms           1  wbc_gtk_init

     ... 以降省略 ...

この関数名だけを眺めているだけでも答えらしきものが発見できる可能性はあります. が, 大量の関数名をただ眺めるだけでは辛いというときもあるでしょう. そこで前回紹介した, scriptを使って, ソースファイル名と行番号を表示させてみます.

$ uftrace script -S show_src_line.py > gnumeric_functions.txt

その結果を見ると, 関数名がひたすら並んでいるのと比べ, ファイル名が一種の分類指標になってくれていることがわかります. また, ファイル名だけを取り出すことも有用です.

$ cut -f 1 -d ' ' gnumeric_functions.txt > gnumeric_files.txt

結果は以下です(フルパス名は相対パスに置き換えて短縮しています).

gnumeric-1.12.37/src/application.c
gnumeric-1.12.37/src/auto-format.c
gnumeric-1.12.37/src/cell-draw.c
gnumeric-1.12.37/src/cell.c
gnumeric-1.12.37/src/cellspan.c
gnumeric-1.12.37/src/clipboard.c
gnumeric-1.12.37/src/collect.c
gnumeric-1.12.37/src/colrow.c
gnumeric-1.12.37/src/command-context-stderr.c
gnumeric-1.12.37/src/commands.c
gnumeric-1.12.37/src/complete-sheet.c
gnumeric-1.12.37/src/complete.c
gnumeric-1.12.37/src/dependent.c
gnumeric-1.12.37/src/dialogs/embedded-ui.c
gnumeric-1.12.37/src/expr-deriv.c
gnumeric-1.12.37/src/expr-name.c
gnumeric-1.12.37/src/expr.c
gnumeric-1.12.37/src/func-builtin.c
gnumeric-1.12.37/src/func.c
gnumeric-1.12.37/src/gnm-format.c
gnumeric-1.12.37/src/gnm-pane.c
gnumeric-1.12.37/src/gnm-plugin.c
gnumeric-1.12.37/src/gnm-sheet-slicer.c
gnumeric-1.12.37/src/gnm-so-filled.c
gnumeric-1.12.37/src/gnm-so-line.c
gnumeric-1.12.37/src/gnmresources.c
gnumeric-1.12.37/src/gnumeric-conf.c
gnumeric-1.12.37/src/gnumeric-simple-canvas.c
gnumeric-1.12.37/src/graph.c
gnumeric-1.12.37/src/gui-clipboard.c
gnumeric-1.12.37/src/gui-util.c
gnumeric-1.12.37/src/gutils.c
gnumeric-1.12.37/src/history.c
gnumeric-1.12.37/src/hlink.c
gnumeric-1.12.37/src/io-context-gtk.c
gnumeric-1.12.37/src/item-bar.c
gnumeric-1.12.37/src/item-cursor.c
gnumeric-1.12.37/src/item-edit.c
gnumeric-1.12.37/src/item-grid.c
gnumeric-1.12.37/src/libgnumeric.c
gnumeric-1.12.37/src/main-application.c
gnumeric-1.12.37/src/mathfunc.c
gnumeric-1.12.37/src/mstyle.c
gnumeric-1.12.37/src/number-match.c
gnumeric-1.12.37/src/parse-util.c
gnumeric-1.12.37/src/parser.c
gnumeric-1.12.37/src/parser.y
gnumeric-1.12.37/src/pattern.c
gnumeric-1.12.37/src/position.c
gnumeric-1.12.37/src/print-info.c
gnumeric-1.12.37/src/ranges.c
gnumeric-1.12.37/src/rendered-value.c
gnumeric-1.12.37/src/selection.c
gnumeric-1.12.37/src/session.c
gnumeric-1.12.37/src/sheet-autofill.c
gnumeric-1.12.37/src/sheet-control-gui.c
gnumeric-1.12.37/src/sheet-control.c
gnumeric-1.12.37/src/sheet-filter.c
gnumeric-1.12.37/src/sheet-merge.c
gnumeric-1.12.37/src/sheet-object-cell-comment.c
gnumeric-1.12.37/src/sheet-object-graph.c
gnumeric-1.12.37/src/sheet-object-image.c
gnumeric-1.12.37/src/sheet-object-widget.c
gnumeric-1.12.37/src/sheet-object.c
gnumeric-1.12.37/src/sheet-style.c
gnumeric-1.12.37/src/sheet-view.c
gnumeric-1.12.37/src/sheet.c
gnumeric-1.12.37/src/stf-export.c
gnumeric-1.12.37/src/stf.c
gnumeric-1.12.37/src/style-border.c
gnumeric-1.12.37/src/style-color.c
gnumeric-1.12.37/src/style.c
gnumeric-1.12.37/src/tools/gnm-solver.c
gnumeric-1.12.37/src/undo.c
gnumeric-1.12.37/src/validation.c
gnumeric-1.12.37/src/value.c
gnumeric-1.12.37/src/wbc-gtk-actions.c
gnumeric-1.12.37/src/wbc-gtk-edit.c
gnumeric-1.12.37/src/wbc-gtk.c
gnumeric-1.12.37/src/widgets/gnm-fontbutton.c
gnumeric-1.12.37/src/widgets/gnm-notebook.c
gnumeric-1.12.37/src/widgets/gnumeric-expr-entry.c
gnumeric-1.12.37/src/workbook-control.c
gnumeric-1.12.37/src/workbook-view.c
gnumeric-1.12.37/src/workbook.c
gnumeric-1.12.37/src/xml-sax-read.c
gnumeric-1.12.37/src/xml-sax-write.c
/usr/include/glib-2.0/glib/gstring.h
??
???

これを眺めて検討がつくか, ケースバイケースでしょうが, expr.cなどはいかにも怪しそうです. expr.c 内に並んでいる関数もいかにもという感じで, 半ば答えは 出たと言って良いかもしれません. たとえば do_expr_as_stringとか. ここまでわかったらあとは, デバッガでこの関数にブレークポイントをかけて実行するなどすれば, その周辺の動きがわかっていくこととおもいます.

$ grep expr.c gnumeric_functions.txt
gnumeric-1.12.37/src/expr-name.c 212 gnm_named_expr_collection_new @ 0xcb7de 4
gnumeric-1.12.37/src/expr-name.c 238 gnm_named_expr_collection_free @ 0xcb863 7
gnumeric-1.12.37/src/expr-name.c 380 gnm_named_expr_collection_lookup @ 0xcbd35 5
gnumeric-1.12.37/src/expr-name.c 425 gnm_named_expr_collection_insert @ 0xcbe6f 6
gnumeric-1.12.37/src/expr-name.c 483 gnm_named_expr_collection_check @ 0xcc115 10
gnumeric-1.12.37/src/expr.c 78 gnm_expr_new_constant @ 0xc17e1 27
gnumeric-1.12.37/src/expr.c 95 gnm_expr_new_funcallv @ 0xc1853 3
gnumeric-1.12.37/src/expr.c 112 gnm_expr_new_funcall @ 0xc18e0 3
gnumeric-1.12.37/src/expr.c 212 gnm_expr_new_binary @ 0xc1c19 4
gnumeric-1.12.37/src/expr.c 465 gnm_expr_free @ 0xc2319 34
gnumeric-1.12.37/src/expr.c 713 handle_empty @ 0xc2cf1 5
gnumeric-1.12.37/src/expr.c 806 bin_arith @ 0xc2f7b 2
gnumeric-1.12.37/src/expr.c 1230 gnm_expr_eval @ 0xc417e 10
gnumeric-1.12.37/src/expr.c 1657 do_expr_as_string @ 0xc5737 3
gnumeric-1.12.37/src/expr.c 2354 gnm_expr_get_range @ 0xc7237 15
gnumeric-1.12.37/src/expr.c 2480 do_expr_walk @ 0xc75e9 27
gnumeric-1.12.37/src/expr.c 2622 gnm_expr_walk @ 0xc7ba5 25
gnumeric-1.12.37/src/expr.c 2836 gnm_expr_top_new @ 0xc8292 21
gnumeric-1.12.37/src/expr.c 2852 gnm_expr_top_new_constant @ 0xc82f4 10
gnumeric-1.12.37/src/expr.c 2858 gnm_expr_top_ref @ 0xc831c 3
gnumeric-1.12.37/src/expr.c 2866 gnm_expr_top_unref @ 0xc836d 21
gnumeric-1.12.37/src/expr.c 2946 gnm_expr_top_get_range @ 0xc85e2 12
gnumeric-1.12.37/src/expr.c 2965 gnm_expr_top_as_gstring @ 0xc869f 1
gnumeric-1.12.37/src/expr.c 2988 gnm_expr_top_equal @ 0xc87ba 1
gnumeric-1.12.37/src/expr.c 3117 gnm_expr_top_eval @ 0xc8d96 6
gnumeric-1.12.37/src/expr.c 3150 cb_referenced_sheets @ 0xc8eec 18
gnumeric-1.12.37/src/expr.c 3187 gnm_expr_top_referenced_sheets @ 0xc8fc5 18
gnumeric-1.12.37/src/expr.c 3260 cb_get_boundingbox @ 0xc9245 3
gnumeric-1.12.37/src/expr.c 3294 gnm_expr_top_get_boundingbox @ 0xc9306 1
gnumeric-1.12.37/src/expr.c 3331 gnm_expr_top_is_array_corner @ 0xc9467 2
gnumeric-1.12.37/src/expr.c 3338 gnm_expr_top_is_array_elem @ 0xc94bf 2
gnumeric-1.12.37/src/expr.c 3419 _gnm_expr_init @ 0xc9669 1
gnumeric-1.12.37/src/expr.c 3451 _gnm_expr_shutdown @ 0xc9756 1
gnumeric-1.12.37/src/gnm-pane.c 2313 gnm_pane_expr_cursor_stop @ 0xdf21b 28
gnumeric-1.12.37/src/parse-util.c 713 gnm_expr_char_start_p @ 0x14772c 58
gnumeric-1.12.37/src/parse-util.c 1595 gnm_expr_conv_quote @ 0x1496c9 3
gnumeric-1.12.37/src/sheet.c 2705 sheet_range_set_expr_cb @ 0x17446c 1

実行の一部だけを記録する --- trigger関数を定義し, LD_PRELOADで注入する

以上で表示されたのはプログラムの開始から終了まで全期間で, 一度でも実行された関数全てです. 従って注目している, 「セル入力, 及びその値の評価」を する以外の部分が多く含まれています. 「セルに値を入力してから表示されるまで」 の期間のみを記録するために, uftraceの --disableおよび--triggerオプションを使ってみましょう. --triggerオプションを使うには, まず記録開始, 終了のきっかけとなる関数を定義しなくては なりません. gnumericのソースコード中に, 適当に空の関数を書き足してやってもいいのですが, ここでは ソースに手を触れないでも行える方法(LD_PRELOAD)を使います.

以下のようなソース(start_stop.c)を用意します.

/* start_stop.c */
void start_recording() { }
void stop_recording() { }
  

以下のようにコンパイルして, 共有オブジェクトファイル(.so)を作ります. ここでも-pgは必須です.

$ gcc -O0 -g -pg start_stop.c -fPIC -shared -o libstart_stop.so

gnumericを実行する際, 環境変数LD_PRELOADを セットして, libstart_stop.soにしておくと, プロセスにlibstart_stop.soが読み込まれ, 結果として, start_recording, stop_recording という二つの関数が定義されます. これらをuftraceの --triggerに指定してやれば良いです.

ためしに, --disable--triggerを以下のようにして実行すると, 何も記録されないということを確かめておきましょう.

$ LD_PRELOAD=libstart_stop.so uftrace record --disable --trigger=start_recording@trace_on --trigger=stop_recording@trace_off ./gnumeric-1.12.37
$ uftrace report
  Total time   Self time       Calls  Function
  ==========  ==========  ==========  ====================
  

--disableによって, 「プログラム開始時に記録を開始しない」 という動作になります. そしstart_recordingはこの実行では呼ばることは ありませんので, 結局記録は全くされないままプログラムが終了することになります. 残る問題は正しいタイミングで, start_recording, stop_recording を呼んでやることです. すなわち, gnumericがセルへの入力待ちになった ところでstart_recordingを, それが終わったところで stop_recordingを呼んでやりたいのですが, そのためにデバッガを使います.

デバッガでuftrace / gnumericを起動

さてここから先は, uftraceというよりも, 主にgdbの使いこなし方の話です.

gdb (デバッガ)を使うと, プログラムを一旦停止させて, 元のプログラムにかかれていないコードを実行させることができます. その仕組みは, 通常変数や式の値を表示させるために使っている, print コマンドです. あれは実は, printコマンドの 引数に与えた式を, プログラムの中で実行しています. 従って, gnumericが入力待ちになったところで実行を止めて,

  
(gdb) p start_recording()

としてやればその時点から記録が開始されます.

ところでこの, 「gnumericが入力待ちになったところで実行を止め」る ために, 少し面倒なのは, 今の場合gnumericを直接起動しているのではなく, uftraceが子プロセスとして起動しているところです. したがって, 直接gnumericをデバッガで(runコマンドで)実行しても, 所望の動作 (トレースの記録)をしてくれません.

そのために, すでに走っているプロセスを, あとからデバッガの 制御下におく(attachする)機能を使います. つまり一旦 uftrace を普通にコマンドラインから実行して, gnumericが立ち上がったらそれに attachします.

  1. # 普通に(コマンドラインから)uftrace/gnumericを起動
    $ LD_PRELOAD=libstart_stop.so uftrace record --disable --trigger=start_recording@trace_on --trigger=stop_recording@trace_off ./gnumeric-1.12.37
    
  2. 立ち上がったところで, gnumericのプロセス番号を調べてから, gdbを立ち上げる(端末か, Emacs内のM-x gud-gdbなど)
    $ ps auxww | grep gnumeric
    tau       9949  0.0  0.0 100368  3420 pts/0    Sl   16:15   0:00 uftrace record --disable --trigger=start_recording@trace_on --trigger=stop_recording@trace_off ./gnumeric-1.12.37
    tau       9950 12.7  0.6 689508 51380 pts/0    Sl   16:15   0:22 ./gnumeric-1.12.37
    tau      10004  0.0  0.0  15264   968 pts/0    S+   16:18   0:00 grep gnumeric
    
    プロセス番号9950とわかったところで,
      (emacsで) M-x gud-gdb
      Run gud-gdb (like this): gdb --fullname ./gnumeric-1.12.37
      (gdb) attach 9950
    Attaching to program: /home/tau/public_html/lecture/dive_to_oss/homepage/public/blog/gnumeric-1.12.37/inst/bin/gnumeric-1.12.37, process 9950
    [New LWP 9952]
    [New LWP 9953]
    [New LWP 9954]
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    0x00007f3ba5f48d8d in poll () at ../sysdeps/unix/syscall-template.S:84
    84	../sysdeps/unix/syscall-template.S: No such file or directory.
    
    ここでは, gnumericが入力待ち状態になったところをデバッガで捕まえていることになります. gnumericが停止しているので, この状態ではgnumericに入力しても反応しませんし, しばらくするとウィンドウがグレーアウトします.
  3. gdb内で
    (gdb) p start_recording()
    
    を実行して, 記録を開始
  4. (gdb) continue
    
    を実行して, gnumericを走らせる. ここでグレーアウトしていた窓が復活して, 入力に反応するようになります.
  5. セルに何か入力 (例: "=1+2")する
  6. gdbにctrl-c を入力して, gnumericを再び止め, gdbに制御を戻す
     ... ctrl-c を入力 ...
    (gdb)    ## gdbに制御が戻り, gdbコマンドが入力可能に
    
  7. あとはcontinueしてgnumericを通常終了させてあげましょう.
    (gdb) continue   # そして, gnumericを普通に終了させてあげる
    

これで記録が取れました. 少し手間が増えますが, アプリケーション全体ではない, 少し良質の記録です. 以下に生データを載せますが, 要約すると,

です(カッコ内は, プログラム全体を記録したときの数). ので, 思ったほど劇的な効果はありませんでした. というよりも, セルに入力するだけで, 実行される関数の数が710, それが66のファイルにまたがっているというのは, なかなかドキドキさせられる数字です.

現時点でuftraceが対応していないプログラム

2017年12月12日の時点での情報です. C++の例外を, rethrow するプログラム (catchブロック内で受け取った例外をまたthrowする)が上手く動かないようです.

try {
  ...
} catch (const my_exception& ex) {
  ...
  throw;
}

このissueは ファイル済みで, うまくすればすぐになおるかもしれません. uftraceは関数の先頭と終了で記録のためのコードを実行します. 先頭に関しては, GCC-pgオプションをつけて実行すると, mcount()という関数の呼び出しが挿入されるので, mcount関数をuftrace提供のものに置き換えて, そこで記録を実行しています. 終了時は特にそのようなコードは入れてくれないので, uftraceでは関数の戻り番地を変更して関数が戻るとそこで uftraceに制御が渡るような仕掛けが入っています. そのためデバッガで見るとスタックトレースが正しく表示されませんし, スタックトレースを追うことに依存した関数(backtraceとか)も, そのままでは正しく動きません. 実は例外処理もその一つです. uftraceではbacktrace関数や例外処理を正しく動作させるために, それらの関数が呼び出されたところで戻り番地を, 元の(uftrace 無しで動作しているのと同じ)状態に戻してから実行する, ということをしています.

実はこのissueにより, LibreOffice Calcのトレースに失敗します. 実はこれが, 今回LibreOfficeではなくgnumericを対象にした裏事情です.

追記:

上記issueをファイルしてから数日で, 著者から連絡がありました. rethrowをするプログラムに対応したというものです. 早速やってみました. ファイルしたモデルケースについては治っていましたが, 残念ながらLibreOffice Calcのトレースは依然としてできませんでした (それがrethrowのせいか, 別のissueかも未調査).

おわりに

ソフトの中身を解析するリバースエンジニアリングツールとしては, デバッガを使うのが一般的でしょう. ある機能に関連した関数名が最初からわかっていればそこにブレークポイントを 貼るなどすればよいわけですが, それがわからない場合, デバッガでは, main関数から順に実行していくのが基本になります. しかしそれでは目標とする場所になかなか到達できない場合があります.

  • 純粋にステップ数が多すぎるという場合もあるでしょう
  • また, 既存ライブラリなど, デバッグシンボルのない状態でパッケージ化された関数は追跡できません
  • 従ってそのようなライブラリからコールバックされる関数へ到達するのも困難です (注: これ自身は, デバッグシンボルパッケージとソースパッケージを入れることで原理的には 解決可能だが, ステップ数が多すぎて手間がかかりすぎるという問題は解決できない)
  • 他にも複数スレッドや複数プロセスからなるプログラムなど, デバッガで丁寧に追いかけていくアプローチが苦しい状況が存在します

GUIプログラムはこの典型で, 何しろプログラムの大部分はGUIライブラリからのコールバックとして実行されます. しかもセルへ入力費とするだけでも, やれwindowにフォーカスが来ただの, 外れただの, 無関係なイベントが大量に実行されます. 従ってデバッガでステップ実行して所望の場所を特定するのが困難です. そんな時, 実行された関数を片っ端から記録, 表示してくれるプロファイラ, トレーサは, 非常に有用なツールになります. 一旦関係しそうな関数を突き止めたら, デバッガでブレークポイントを貼るなどして, 詳細調査ができます.

大規模ソフトウェアを手探る という授業をやっており, オープンソースのソフトウェアに機能拡張などの変更を加える, ということを課題にしています. uftraceは, LibreOffice Calcのように上手く行かなかった例もありますが, 上手く動けばそのための強力なリバースエンジニアリングツールとなりそうです.