19

x64アセンブリの学習を始めたばかりですが、関数、引数、およびスタックについて質問があります。私が理解している限り、関数の最初の4つの引数は、Windowsのrcx、rdx、r8、およびr9レジスタ(およびfloatの場合はxmm0-xmm3)に渡されます。したがって、4つのパラメーターを持つ簡単な加算関数は次のようになります。

add:
   mov r10, rcx
   add r10, rdx
   add r10, r8
   add r10, r9
   mov rax, r10
   ret

しかし、私はこれに言及しているドキュメントに出くわしました:

少なくとも、各関数はスタック上で32バイト(4つの64ビット値)を予約する必要があります。このスペースにより、関数に渡されたレジスタを既知のスタック位置に簡単にコピーできます。呼び出し先関数は、入力レジスタパラメータをスタックにスピルする必要はありませんが、スタックスペースの予約により、必要に応じてスピルできるようになります。

では、作成している関数が4つ以下のパラメーターを使用する場合でも、スタックスペースを予約する必要がありますか、それとも単なる推奨事項ですか?

4

2 に答える 2

14

あなたの引用は、ドキュメントの「呼び出し規約」の部分からのものです。少なくとも、アセンブリ コードから他の関数​​を呼び出さない場合は、これについて心配する必要はありません。そうする場合は、引用する推奨事項が確実にすることを意図した、とりわけ「レッドゾーン」とスタックアライメントの考慮事項を尊重する必要があります。

編集:この投稿は、「レッド ゾーン」と「シャドウ スペース」の違いを明確にします。

于 2011-09-12T18:55:04.500 に答える
2

これでさらに遊んでドキュメントを読んだ後、呼び出す関数のために 32 バイトを予約する必要があります。関数が例のように単純で、他の関数を呼び出さない場合は、このスペースを予約する必要はありません。ただし、呼び出す関数はこの 32 バイトを使用する可能性があるため、それらを予約しないと、関数が

また、関数は、ABI に従っている場合、関数を呼び出した関数からスタックに 32 バイトが利用可能であることに依存する場合があります。通常、この 32 バイト領域は、関数で変更されるレジスタを保存するために使用されるため、戻る前にそれらの値を復元できます。これはパフォーマンス上の目的のためだと思います.32バイトが選択されているため、ほとんどのリーフ関数(他の関数を呼び出さない関数)はスタックスペースを予約する必要がなく、レジスタを保存するためにスタックに一時的なスペースがあります戻る前にそれらを復元します。次の例を見てください。

呼び出し機能:

CallingFunction:
  push rbp
  mov rbp, rsp
  sub rsp, 40  // $20 bytes we want to use at [rbp+30],
               // plus $20 bytes for calling other functions
               // according to windows ABI spec
  mov rcx, [rsi+10]     // parameter 1 (xmm0 if non-int)
  mov rdx, 10           // parameter 2 (xmm1 if non-int)
  movss xmm2, [rsi+28]  // parameter 3 (r8 if int)
  mov r9, [rsi+64]      // parameter 4 (xmm3 if non-int)
  call MyFunction
  // ... do other stuff
  add rsp, 40           // free space we reserved
  pop rbp
  xor rax,rax
  ret

呼び出された関数

CalledFunction:
  push rbp      // standard
  mov rbp, rsp  // standard

  // should do 'sub rsp, 20' here if calling any functions
  // to give them a free scratch area

  // [rbp] is saved rbp
  // [rbp+8] is return address
  // [rbp+10] to [rbp+2f] are the 0x20 bytes we can
  //     safely modify in this function, this could
  //     be pushed higher if the function had more than 4
  //     parameters and some had to be passed on the stack
  //     or if returning a structure or something that needs
  //     more space.  In these cases the CALLER would have
  //     allocated more space for us

  // the main reason for the 0x20 is so that we can save 
  // registers we want to modify without having to allocate
  // stack space ourselves
  mov [rbp+10], rsi // save rsi in space allocated by caller
  mov [rbp+18], rdi // save rdi in space allocated by caller
  mov rsi, [rcx+20]
  mov rdi, [rsi+48]
  add rdi, [rsi+28]
  mov rax, rdi
  mov rdi, [rbp+18] // restore changed register
  mov rsi, [rbp+10] // restore changed register
  pop rbp
  ret

元の答え

私は知らないうちにこれに出くわしましたが、そうであるようです。たとえば、GetAsyncKeyState の最初の 2 つの命令は、呼び出し先がパラメーターに使用するために予約する必要がある 0x20 バイト領域の戻りアドレスの上のスタックを上書きします。

user32.GetAsyncKeyState  - mov [rsp+08],rbx
user32.GetAsyncKeyState+5- mov [rsp+10],rsi
user32.GetAsyncKeyState+A- push rdi
user32.GetAsyncKeyState+B- sub rsp,20
于 2015-03-27T22:59:04.717 に答える