• 締切済み

引数やスタックについて

引数やスタックについて int型は4バイトだとします。 MapVirtualKey(uc, um);をコンパイルして PUSH EAX PUSH EDX CALL DWORD PTR DS、、、 このようになったとします。 MapVirtualKeyが実行されたときに引数の値が代入される処理は、SPが示している位置から4バイト(EDXに入っていた値)がPOPされてuCodeの値として扱われ、SPが4増えて、SPが示している位置から4バイト(EAXに入っていた値)がPOPされてuMapTypeの値として扱われることに間違いありませんか? 確認したいのはMapVirtualKeyはスタックから8バイトPOPするという認識で間違っていないかということです。 uMapTypeを0にしたい場合にPUSH EAX(50)を68,00,00,00,00に変更すればよいことは分かりますが6A,00とするのは間違いですか?

みんなの回答

  • R32C
  • ベストアンサー率39% (115/290)
回答No.5

#3が正解ですね。(#4は、__cdeclの場合の話) これはコンパイラの方言がどうなっているかということです。 MapVirtualKeyということで、VC++ということだと判断できましたね。 コンパイラによって方言をどう制御するかをなんらかのオプションで指定します。 #pragma であったり VC++の場合は、__cdecl や __stdcall __pascal であったりです。 MapVirtualKeyを使うためのヘッダファイルを見てみましょう。__stdcallが宣言されて いるはずです。 また、 #3のURL先を見てみましょう。#4の疑問もわかると思います。

回答No.4

今のi386、i486系の32ビットコンパイラの場合「スタックは使いっ放し」にします。   何故なら「SPを元に戻す処理が実行速度に影響する」からです。   実際のコードは PUSH EBP ;EBPを退避 PUSH EDI ;レジスタ変数用のレジスタを退避 PUSH ESI ;レジスタ変数用のレジスタを退避 MOV EBP,ESP ;ESPをEBPに退避し、EBPを引数とオート変数のベースに使用 ADD ESP,-0x48 ;オート変数領域を0x48バイト確保 (略) PUSH [EBP-0x10] PUSH [EBP-0x14] PUSH EAX PUSH EDX CALL DWORD PTR xxxxx ;ESPは使い捨て。わざわざ戻したりしない MOV [EBP-0x04],EAX MOV EDX,EAX MOV EAX,[EDX] PUSH [EBP-0x08] PUSH EAX PUSH 0x00432100 CALL DWORD PTR xxxxx ;ESPは使い捨て。わざわざ戻したりしない (略) MOV ESP,EBP ;ESPを一気に元に戻す POP ESI ;レジスタ変数用のレジスタを復元 POP EDI ;レジスタ変数用のレジスタを復元 POP EBP ;EBPを復元 RET ;リターン となります。   >このようになったとします。 >MapVirtualKeyが実行されたときに引数の値が代入される処理は、SPが   「SPが」と言っている時点で「顔洗って出直して来い」です。   コンパイラがSPの値を操作するのは、基本的に「ブロックの終端だけ」です。例えば   for (i=0;i<10;i++) { (略) 関数コールfoo(i,i+1); 関数コールbar(i,i+1); (略) } //ここでブロックを抜ける など(関数の出口も「ブロックの終端」である事に注意)   この場合、コードは   iの初期化部 ループ先端ラベル: iの条件判定部。CMP命令や条件ジャンプ ;ブロックの開始 PUSH 引数 PUSH 引数 CALL foo ;ESPはPUSHしたまま使い捨て PUSH 引数 PUSH 引数 CALL bar ;ESPはPUSHしたまま使い捨て ;ブロックの終端 ADD ESP,+0x10 ;一気にESPを戻す JMP ループ先端ラベル ループ出口ラベル:   のようになります。   このように、コンパイラは「SPはどこ指してるか判らん物なので、最後に一気に元に戻す」ってコードを作ります。   ですので >確認したいのはMapVirtualKeyはスタックから8バイトPOPするという認識で間違っていないかということです。 間違ってます。コンパイラは「関数を呼ぶごとにSPを元に戻す」なんて面倒で遅いコードは生成しません。   SPが元に戻らないとすると「じゃ、スタック上の引数やオート変数にはどうやってアクセスすんの」と思うでしょうが、それは「BPをスタックを指すレジスタにして、関数内で不変にする」と言う方法で解決してます。

snjkrdo
質問者

補足

>コンパイラがSPの値を操作するのは、基本的に「ブロックの終端だけ」 というのはだいたい理解できました。 関数の処理でPOPされたりしてESPの値がCALLした時と変わっていようとも ブロックの終端でESPを一気に戻すから、関数の引数のためのスタックの値に関するESPの値は呼び出し側で気にすることはないということですね。 >;ブロックの終端 >ADD ESP,+0x10 ;一気にESPを戻す >JMP ループ先端ラベル これについてですが、ADDではfooやbarの処理内容によってはESPがずれると思います。 ESPの値をどこかにバックアップしておいて、MOVなどで値を復元した方がよいと思いませんか?

  • titokani
  • ベストアンサー率19% (341/1726)
回答No.3

引数のつまれ方や、スタックの後始末をどうするかは、「呼び出し規約」によって決まります。 MapVirtualKeyの定義を見ると、呼び出し規約は__stdcallとなっています。 http://www.ne.jp/asahi/nagoya/ahomaro/builder/cpb-055.html スタックの後始末に関してだと、__stdcallの場合、スタックに積まれた引数は、関数の側で破棄する規約ですね。 アセンブリコードとしては RET #8 とかが使われます。 printfなどの可変長引数の場合が、呼び出し側で引数の数がわかりませんので、呼び出し規約は__cdeclが使われます。

snjkrdo
質問者

お礼

呼び出し規約については知りませんでした。 ありがとうございました。

  • R32C
  • ベストアンサー率39% (115/290)
回答No.2

>質問の意味とは関係ないのですがそれならばオペランドが逆で >MOV EDX,DWORD PTR SS:[ESP+4] >MOV EAX,DWORD PTR SS:[ESP+8] >ではないでしょう? ですね。失礼しました。マイコンによって、左右違うもので... >の後ろを見ましたが、ADD SP,、、、のようは見つからずに、 >次のAPIのためのPUSHの処理があって >、そしてAPIがCALL DWORD PTR DS、、、で呼ばれていました。 コンパイラが最適化しているか、もしくはコンパイラのオプションで #pragma pascal のようなことが有効になっているかいずれかです。 関数呼び出し時のスタックの積み方とその解消方法は、 C言語では、呼び出した側で戻す 一方パスカルなどでは、呼び出された側で戻す。といってもリターン命令で余計に スタックを戻す命令があったかと思います。 これは、C言語ではプロトタイプ宣言なしで呼び出された名残で、引数を可変にできるなど のことからそうなったようです。引数の順番も最初の引数が最後にPUSHされるなどの 違いがあります。 もともとpascal仕様のコンパイラがあっても別におかしくありませんのでなんともいえません。 >MapVirtualKeyの最初の方の引数をスタックから取得するための処理では >SPを変えずに読むのは本当でしょうか? ディスアセンブラで見てみればわかるのではないでしょうか? 機械語をどうこう言われているのでアセンブリ言語コードを確認できる環境 があるように思うのですが >質問の6A,00だと関数が終わって元に戻った時点でESPが2バイトずれるんですね。 どこが戻った時点か不明ですが、ESPがずれると思います。 傾向とか多いとか答えているのは、コンパイラによって、さまざまであるので、 何かわからないコンパイラについては答えようがありません。 またコンパイラにマニュアルには必ず、引数の渡し方について書かれていますので よく読んでみてください。

snjkrdo
質問者

補足

#pragma pascal についはどうなっているか分かりませんが、無効になっていると思います。 引数の順番は、最初の引数が最後にPUSHされたものでした。 MapVirtualKey関数が終わって元に戻った時点というのはMapVirtualKey関数を呼び出すCALLの4バイト先のところに処理が移った時点のつもりでした。 ESPがずれるのでしたら理解できました。 引数に4バイトの値を持っているAPIをCALLするのに、CALLの直前でPUSH 0しているソースを見たことがあって、それでは関数が開始された時に引数をスタックから取得する処理でESPがずれるのではないとか思っていました。そのソースはそこがバグになるのでしょう。

  • R32C
  • ベストアンサー率39% (115/290)
回答No.1

32bit x86限定の話ですね。 ですが、コンパイラによっても、プロトタイプ宣言の有無によっても どのように引数が渡されるかは、変わる場合があります。 PUSH EAX PUSH EDX CALL DWORD PTR DS、、、 上記のようなコードになった場合は、以下のスタック構造になります。 SP-> 戻り番地 +4 EDX +8 EAX 通常のコンパイラの場合は、(#pragma pascalがない場合) もとに戻った時に SP -> EDX +4 -> EAX になる場合が多く、 呼び出し側でスタックを戻すコードを出す場合が多いです。 実際には、 PUSH EAX PUSH EDX CALL DWORD PTR DS、、、 ADD #8,SP ;ニーモニックは怪しいです とコンパイラが出す場合が多いです。 また、呼び出される先(この質問では、MapVirtualKey)で直接POPするような、SPを操作した場合 たとえば POP EAX ;もどり番地分進む POP EDX POP EAX とかすることはありません。たぶん MOV SP[4],EDX MOV SP[8],EAX みたいな、SPを変えずに読みだすかと思います。 SPを操作した場合に割込みされた場合にSPより若い部分が壊されてしまう場合があるからです。 >uMapTypeを0にしたい場合にPUSH EAX(50)を68,00,00,00,00に変更すればよいことは >分かりますが6A,00とするのは間違いですか? 機械語の話ですね。機械語で実機にパッチでもあてるのですか? 6A,00 PUSH imm8 では、SPが、たぶん2バイトしか積まれないため問題がありそうですね。 IA-32 インテルアークテキチャソフトウエアデベロッパーズマニュアル 中巻B 命令セットリファレンスN-Z を参照ください。 仮に以下のようですと、 PUSH #0:imm8 ;ニーモニックが怪しいですが PUSH EDX CALL DWORD PTR DS、、、 とすると SP->戻り番地 +4->EDX +8->0 +10->もとのスタックにあったデータ になり、スタックがずれてしまって、たぶんおかしな動きになると思います。

snjkrdo
質問者

補足

MapVirtualKeyの最初の方の処理で仮に POP EDX POP EAX が行われているとしたら、それを MOV SP[4],EDX MOV SP[8],EAX にすることによって割込みに対応できるようになるということですか? その理由が分かりませんでした。 質問の意味とは関係ないのですがそれならばオペランドが逆で MOV EDX,DWORD PTR SS:[ESP+4] MOV EAX,DWORD PTR SS:[ESP+8] ではないでしょう? MapVirtualKeyの最初の方の引数をスタックから取得するための処理ではSPを変えずに読むのは本当でしょうか? CALL DWORD PTR DS、、、 の後ろを見ましたが、ADD SP,、、、のようは見つからずに、次のAPIのためのPUSHの処理があって、そしてAPIがCALL DWORD PTR DS、、、で呼ばれていました。 質問の6A,00だと関数が終わって元に戻った時点でESPが2バイトずれるんですね。