miau's blog?

CHKSTK.ASM

最近いろんなプロジェクトを転々としてるんですけど、今月頭までやってたのはいつか係わった VC++ の案件の改修。
思わぬところでハマったりしたのでネタにしてみます。




■現象

・どこかの研究所から DLL が提供されているけど、この中の特定関数を呼び出すと、
「例外 unknown software exception (0xc00000fd) が
 アプリケーションの 0x01778735 で発生しました。」
 みたいなエラーになって異常終了する

・DLL の自体の問題ではないらしい。
 というのも、DLL の関数を呼び出すだけのシンプルなアプリケーションでは問題が起きないから。
 業務アプリケーションから DLL を呼び出した時だけエラーになるみたい。

・DLL のソースを提供してもらって print デバッグ(正確には OutputDebugString + DebugView デバッグ)しようとしたけど、その関数の呼び出し直後に落ちているらしい

■サンプル

仕事のソースをそのまま使うわけにもいかないので、適当なサンプル。

#include "stdafx.h"

void func()
{
char files[16][255];

printf("func() called!\n");

// 色々な処理

return;
}

int main(int argc, char* argv[])
{
printf("calling func() ...\n");
func();

return 0;
}

こんな感じのソースとして。

実行すると「calling func() ...」まで表示させたところで異常終了。
関数の呼び出しで何が起きてるの?

■原因究明

なんかライセンスの関係で VC++ をインストールできないらしいので、OllyDbg で処理を追ってみた。

00401078 |. 68 AC2F4200 PUSH OFFSET stacksiz.??_C@_0BE@CGCB@call>; /format = "calling func() ...\n"
0040107D |. E8 5E000000 CALL stacksiz.printf ; \printf
00401082 |. 83C4 04 ADD ESP,4
00401085 |. E8 7BFFFFFF CALL stacksiz.00401005

func() の呼び出し前までは問題なし。
問題は func() の処理。

0040D740 >/> 55 PUSH EBP
0040D741 |. 8BEC MOV EBP,ESP
0040D743 |. B8 30100000 MOV EAX,1030
0040D748 |. E8 6339FFFF CALL stacksiz.__chkstk
0040D74D |. 53 PUSH EBX
0040D74E |. 56 PUSH ESI
0040D74F |. 57 PUSH EDI
0040D750 |. 8DBD D0EFFFFF LEA EDI,DWORD PTR SS:[EBP-1030]
0040D756 |. B9 0C040000 MOV ECX,40C
0040D75B |. B8 CCCCCCCC MOV EAX,CCCCCCCC
0040D760 |. F3:AB REP STOS DWORD PTR ES:[EDI]
0040D762 |. 68 1C204200 PUSH OFFSET stacksiz.??_C@_0O@FEEI@Hello>; /format = "func() called!\n"
0040D767 |. E8 7439FFFF CALL stacksiz.printf ; \printf

なぜか関数冒頭の「func() called!」出力までの間に怪しい関数(ここでは stacksiz.__chkstk と出てるけど、このタイミングではそこまでの情報は無かった)を呼び出す。
この処理がなにをやってるかと言うと・・・。

004010B0 >/$ 51 PUSH ECX ; stacksiz.00424A60
004010B1 |. 3D 00100000 CMP EAX,1000
004010B6 |. 8D4C24 08 LEA ECX,DWORD PTR SS:[ESP+8]
004010BA |. 72 14 JB SHORT stacksiz.004010D0
004010BC |> 81E9 00100000 /SUB ECX,1000
004010C2 |. 2D 00100000 |SUB EAX,1000
004010C7 |. 8501 |TEST DWORD PTR DS:[ECX],EAX
004010C9 |. 3D 00100000 |CMP EAX,1000
004010CE |.^73 EC \JNB SHORT stacksiz.004010BC
004010D0 |> 2BC8 SUB ECX,EAX
004010D2 |. 8BC4 MOV EAX,ESP
004010D4 |. 8501 TEST DWORD PTR DS:[ECX],EAX
004010D6 |. 8BE1 MOV ESP,ECX
004010D8 |. 8B08 MOV ECX,DWORD PTR DS:[EAX]
004010DA |. 8B40 04 MOV EAX,DWORD PTR DS:[EAX+4]
004010DD |. 50 PUSH EAX
004010DE \. C3 RETN

ECX のアドレスを順次引きながら、そこに EAX の値を書き込んでる。
で、その値はどこでも使ってない・・・?
何の意味があるんだ?この処理。

このループ中で落ちてるっぽいんだけど、何がなんだか・・・。
と、周りを巻き込みつつかなり悩んだんだけど、OllyDbg のステータスバーをよく見ると「stack overflow」とかなんとか表示されてて。ようやくピンときました。あの時 と同じ問題だと。

この関数内でやたら巨大な構造体配列を使っていたので、これを static 変数にすることで解決。

■どういうこと?

普通は関数が呼ばれると、その先頭で

0040D740 push ebp
0040D741 mov ebp,esp
0040D743 sub esp,0F34h
0040D749 push ebx
0040D74A push esi
0040D74B push edi

こんなことをやって、関数で使う領域をスタックに確保します。
でも、これが 1KB(たぶん 1 ページに相当)を超える場合、この処理がちょっと変わるようで。

0040D740 push ebp
0040D741 mov ebp,esp
0040D743 mov eax,1030h
0040D748 call $$$00001 (004010b0)
0040D74D push ebx
0040D74E push esi
0040D74F push edi

単純に ESP を減算する代わりに、怪しげな関数が呼ばれるようになります。その関数ってのがこれ。(さっきのと同じだけど VC++ 風に表記)

004010B0 push ecx
004010B1 cmp eax,1000h
004010B6 lea ecx,[esp+8]
004010BA jb lastpage (004010d0)
004010BC sub ecx,1000h
004010C2 sub eax,1000h
004010C7 test dword ptr [ecx],eax
004010C9 cmp eax,1000h
004010CE jae probepages (004010bc)
004010D0 sub ecx,eax
004010D2 mov eax,esp
004010D4 test dword ptr [ecx],eax
004010D6 mov esp,ecx
004010D8 mov ecx,dword ptr [eax]
004010DA mov eax,dword ptr [eax+4]
004010DD push eax
004010DE ret

たぶんプロセス毎にスタックサイズに制限があって、そのプロセスが使える最後の(アドレスが一番小さい)ページにアクセスしようとすると、OS が例外を吐くような仕組みになっている。
単純に ESP を減算するとこのチェックが行われないので、わざわざ 1 ページずつアクセスを試みてるってことみたい。
(この辺はこないだ OS 本読んでてなんとなく理解した。レッドゾーンとか呼ばれるものみたい。)

この関数については VC++ 付属の chkstk.asm がいい感じに well commented なので、これ見ていただいたほうがいいかと。

■どうでもいい感想

前回 CHKSTK.ASM の問題に遭遇してたにもかかわらず、OllyDbg で見たときにそれと同一の処理だと気づかなかったのがちょっと悲しい。あの時処理をもっと詳細に追ってれば、こんなにハマらなかったのに。

あと 以前 このプロジェクトに係わったときもそうだったけど、やたら特殊な知識を利用して問題を乗り切ってる気がする。
なかなか他の人に任せられないな、これじゃ。
posted at 09:03:13 on 2007-06-29 by miau - Category: General No Trackbacks - Permalink

TrackBack

このエントリにトラックバックはありません
現在トラックバックは受け付けていません。

Comments

No comments yet

Add Comments

現在コメントは受け付けていません。
お手数ですが、 こちら のコメント欄にでも記載していただければと思います。