2

私は最近、バッファとさまざまなアセンブリ演算子の RAW 16 進数に相当するものを使用して、C++ で動的関数を実装しようとしています。簡単なジャンプを説明するには:

byte * buffer = new buffer[5];
*buffer = '0xE9'; // Hex for jump
*(uint*)(buffer + 1) = 'address destination';

私は組み立ての経験はありませんが、非常に単純な機能を作成するのに十分な知識があります。現在、生メモリに cdecl 関数を作成しています。問題は、スタックを(メモリ用に)どれだけプッシュしたいのかわからないことですsub。例として、この関数を見てみましょう:

int MyTest(int x, int y) { return x + y; }

long TheTest(int x, int y)
{
    return MyTest(x, 5);
}

08048a20 <_Z6TheTestii>:
_Z6TheTestii():
 8048a20:   55                      push   %ebp
 8048a21:   89 e5                   mov    %esp,%ebp
 8048a23:   83 ec 18                sub    $0x18,%esp
 8048a26:   c7 44 24 04 05 00 00    movl   $0x5,0x4(%esp)
 8048a2d:   00 
 8048a2e:   8b 45 08                mov    0x8(%ebp),%eax
 8048a31:   89 04 24                mov    %eax,(%esp)
 8048a34:   e8 c2 ff ff ff          call   80489fb <_Z6MyTestii>
 8048a39:   c9                      leave  
 8048a3a:   c3                      ret    

ご覧のとおり、最初は C++ コードで、以下は「TheTest」関数の ASM です。スタックが 24 (0x18) バイトにプッシュされていることがすぐにわかります (前述のように、私はアセンブリの使用経験がないため、正しい用語を使用していないか、完全に正しいとは限りません)。これは私には意味がありません。2 つの異なる整数のみが使用されているのに、なぜ 24 バイトが必要なのですか? 変数 'x' が使用され、これは 4 バイトであり、値 '5' も 4 バイトを使用します (これは cdecl であるため、呼び出し関数が関数の引数に関するメモリを処理することを思い出してください) 24 を補いません... .

ここで、アセンブリ出力について本当に疑問に思う追加の例を示します。

int NewTest(int x, char val) { return x + val; }

long TheTest(int x, int y)
{
    return NewTest(x, (char)6);
}

08048a3d <_Z6TheTestiiii>:
_Z6TheTestiiii():
 8048a3d:   55                      push   %ebp
 8048a3e:   89 e5                   mov    %esp,%ebp
 8048a40:   83 ec 08                sub    $0x8,%esp
 8048a43:   c7 44 24 04 06 00 00    movl   $0x6,0x4(%esp)
 8048a4a:   00 
 8048a4b:   8b 45 08                mov    0x8(%ebp),%eax
 8048a4e:   89 04 24                mov    %eax,(%esp)
 8048a51:   e8 ca ff ff ff          call   8048a20 <_Z7NewTestic>
 8048a56:   c9                      leave  
 8048a57:   c3                      ret    

ここでの唯一の違い (値を除く) は、整数の代わりに「char」(1 バイト) を使用していることです。次にアセンブリ コードを見ると、これはスタック ポインタを 8 バイトだけプッシュします。これは、前の例から16バイトの違いです。徹底した C++ の人間として、何が起こっているのかわかりません。誰かが私にこの件について教えてくれたら本当にありがたいです!

注: ASM の本を読む代わりにここに投稿する理由は、この1 つの関数にアセンブリを使用する必要があるためです。だから、40行のコードのために本全体を読みたくありません...

編集:私はプラットフォーム依存性も気にしません.Linux 32ビットだけを気にします:)

4

4 に答える 4

2

で作成されたスタック フレームは、ローカル (自動) 変数と、 によって呼び出されるやTheTestなどの関数への引数の両方を保持します。フレームは によってプッシュおよびポップされるため、呼び出す関数への引数を保持するのに十分な大きさである限り、サイズはそれほど重要ではありません。MyTestNewTestTheTestTheTest

表示されているコンパイラ出力は、コンパイラのいくつかのパスの結果です。各パスは、必要なフレーム サイズを縮小する変換と最適化を実行する場合があります。初期の段階で、コンパイラは 24 バイトのフレームを必要とし、コードが最適化されたにもかかわらず、それを減らすことはなかったのではないかと思います。

プラットフォーム上のコンパイラの ABI は、従わなければならないスタック アラインメントに関するいくつかのルールを確立するため、フレーム サイズはこれらの要件を満たすように切り上げられます。

これらの関数はフレーム ポインターを使用します%ebp%が、これはコード サイズやパフォーマンスの点で有利ではありません。ただし、これはデバッグに役立つ場合があります。

于 2012-05-05T17:07:49.137 に答える
1

これは、スタックを32バイトの倍数に揃えて、SIMD命令をスタック上の変数で使用できるようにするためです。

于 2012-05-06T11:25:27.117 に答える
1

あなたのコンパイラが最初の関数で間違いを犯しているように見えます (おそらくスタック使用の最適化が欠けています)。また、コンパイラが 1 つのプッシュ命令ではなく 2 つの命令 (事前に割り当てられたスタック スロットへの移動) を使用していることも奇妙です。

最適化せずにコンパイルしていますか? コンパイラのコマンドラインを投稿できますか?

于 2012-05-05T17:19:07.273 に答える
0

これらの関数には、プロローグとエピローグのコードが挿入されています。アセンブリを裸の関数で書いてみてください。

__declspec( naked ) void UsernameIdTramp() // 10 byter, 5 bytes saves + 5 bytes for tramp
{
    __asm 
    {  
        nop; nop; nop; nop; nop;   // 5 bytes copied from target - 
        nop; nop; nop; nop; nop;   // 5 bytes for the jump back.
    }
}
于 2012-05-05T17:07:32.037 に答える