C で環境復帰

C は動的な LL なので、 Smalltalk みたいに環境を保存したり復帰したりできます。というか C というかアセンブラならなんでもできるとかそんなこんな。

http://shinh.skr.jp/binary/restore.c

i@u ~/src/coredumper-0.2> gcc -Wall restore.c -g .libs/libcoredumper.a
i@u ~/src/coredumper-0.2> ls core*
zsh: no matches found: core*
i@u ~/src/coredumper-0.2> ./a.out
0 0 1
1 1 2
2 3 6
i@u ~/src/coredumper-0.2> ls core*
core0  core1  core2
i@u ~/src/coredumper-0.2> ./a.out core1
1 1 2
2 3 6
i@u ~/src/coredumper-0.2> ./a.out core2
2 3 6

表示されてる1つ目は stack に置いてあるカウンタで、 0,1,3 と遷移している2つ目の数字は global 変数で足し算をしていってまして、3つ目は heap に置いてある変数でかけ算をしています。まぁそれぞれのメモリが復帰してるのがわかるかと。詳細は restore.c の下の方を参照。

ええと goog-coredumper というのがあると気付きまして、これは要するに core を好きな時に吐ける素敵なソフトウェアです。二日酔いとかでも安心。

http://goog-coredumper.sourceforge.net/

でまぁ、このソースがかなり面白かったんですが(メモリ保護属性の調べかたとかはこれで知りました)、それはまた今度の話として、これ見ると core がどういう構成かとかが一目瞭然だったので、前から気になっていた undump(1) というのを作ってみたくなったのでした。 undump(1) ってのは Perlラクダ本にのってたから知ったのですが、 core からプログラムの状況を復帰させるコマンドらしいです。手元の Linux とかには無いので、今のシステムにはもう無いものなのかもしれません。

それでまぁ、アセンブラはスタックフレーム自由にできるのが偉いというお言葉なんかと関係があったり無かったりしつつ、 core の情報を見てスタックだのヒープだの適当に戻しまくって、スタックフレーム適当に戻して、レジスタとか戻して、適当に元の EIP より少し進んだところに jmp するものを作ったのでした。

これからのゲームのセーブロードは core 吐いて restore が定番になることでしょう。

悪いことは後で書くメソッドLinux && x86 に激しく依存。スレッドとか忘れました。ファイルハンドルとかはたぶん戻りません。 kernel が stack の位置を毎回変えてくれるシステムでは最後に落ちます。原因は追い切れなかったので仕様です。

sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'

などとすればその機能をオフにできるようでした。

あとまぁ、本当は外部から復帰できるようにした方がいいんでしょうけど、それはまぁ次回の課題というか。つまりたぶんやりませんね。

というかまぁ、既存の undump(1) のコードを探すとか全くしてないのでいつも通り完全な我流というか。なんかスタックとレジスタを復帰させるタイミングでは既にヒープを戻しちゃってるから使えなくて、そのへんで alloca とかでどうこうしてるとかなんとか。

いや忘れないうちに完全にやったことを書いておこう。

適当に core を読む。エルフヘッダ => プログラムヘッダ => ノートヘッダ => データ みたいな感じで入ってるみたい。メモリの状態はデータのところに入ってる。プログラムヘッダからデータのサイズとかがわかるので、とりあえずそれを取得。あとノートヘッダのところにレジスタの状態が入ってるからそれも取得。

とりあえずスタックを core から取り出してヒープに置く。スタックを終端(スタックは上向きに伸びるので、つまり若いアドレス)から順に見ていって、 0 じゃないところまで読み飛ばす。そんで alloca した空間に 0 じゃないところから記録する。これは、スタックを全部スタックの上に置くのはたぶん無理じゃね、と思ったから。真偽のほどはよくわかりません面倒で調べてないから。

そんで、 randomize_va_space が有効になってるとスタックの位置が変わってやがるので 適当に ebp と、そこからたどれるスタックフレームの値を補正。

次にレジスタを alloca で確保した空間に置いておく。わざわざスタック内でコピーするのは、レジスタはスタックをぶっつぶした後に復帰させるので、スタックの深いところに置いておく配慮。よく考えるとこのへん全部 pipe でやれば問題無い気もする。

まぁとにかく復帰開始。

適当にヒープとかを読み込み。たぶん sbrk(0) より小さいメモリ空間だけコピーすればいい気がした。たぶん .bss と heap をコピーすれば十分なんじゃね、と適当に判断。

ここで core のファイルを閉じてこっからアセンブラで。スタックのコピー領域はかぶってるのでちゃんとおしりの方からコピーしてやって、適当にレジスタ戻して、最後に EIP に飛んで復帰終了。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h