2011-09-12

old new thing: 他人の尻拭い、rundll32にまつわる悲壮悲劇

Throwing garbage on the sidewalk: The sad history of the rundll32 program - The Old New Thing - Site Home - MSDN Blogs

Windows Vistaの開発中、アプリケーション互換チームは、rundll32の呼び出し規約の仕様に従わない関数を無理やり呼び出した挙句のスタック破壊の問題を抱えていた。

訳注:Windows上で動く32bitコードにおいては、関数の呼び出し規約が統一されていなかったので、呼び出し規約の誤りによるスタック破壊はよくあることである。もっとも、呼び出し規約が統一されていたからとて、意図的にスタックポインターをずらされてしまってはどうしようもないが。

問題は複雑だ。例えば、rundll32を間違った方法で使っていたバッチファイルが動かなくなった。なぜならば、rundll32プロセスが終了しないからだ。スタックアライメントの誤りは、スタックからのレジスターのリストアの誤りを生じさせ、rundll32の終了時のコードを動かなくさせてしまうからだ。以前のバージョンのWindowsは、この問題を、たまたま、避けることができていた。Windows Vistaで使っているコンパイラーは、最適化方法が異なるので、スタックやレジスターの使い方が異なり、以前のバージョンのWindowsでは、たまたま問題にならなかったスタック破壊が、問題になるのだ。偶然は何度も続かないものだ。

私はこのrundll32の問題を解決して、間違った方法で使っていた人々を救うよう、命ぜられた。つまりは、他人のバグの尻拭いというわけだ。

解決方法とは、関数を呼び出す前に、呼び出された関数がスタックから余計にpopする可能性を考えて、スタックをあらかじめ数百バイトほど余計にpushしておくのだ。そして、スタックポインターをグローバル変数に保存しておく。関数から戻ったら、関数がスタック操作を誤った場合に備えて、そのグローバル変数からスタックポインターをリストアする。たしか、他のレジスターもグローバル変数に保存しておいたような気もするが、何だったかは忘れた。

もちろん、これを悪用してはならない。コンビニの前にゴミを投げ捨ててはいけないのと同じ理由だ。

訳注:アプリケーションの互換性を保つためのworkaroundは、ユーザーのためにあるのであって、プログラマーのためにあるのではない。

No comments: