2

この短い C コードを取得しました。

#include <stdint.h>
uint64_t multiply(uint32_t x, uint32_t y) {

uint64_t res;
res = x*y;
return res;
}

int main() {

uint32_t a = 3, b = 5, z;
z = multiply(a,b);
return 0;
}

上記の C コードのアセンブラー コードもあります。私はそのアセンブラ コードのすべてを理解しているわけではありません。各行にコメントしました。各行のコメントに私の質問があります。

アセンブラー コードは次のとおりです。

.text
multiply:
     pushl  %ebp  // stores the stack frame of the calling function on the stack
     movl   %esp, %ebp // takes the current stack pointer and uses it as the frame for the called function
     subl   $16, %esp // it leaves room on the stack, but why 16Bytes. sizeof(res) = 8Bytes
     movl   8(%ebp), %eax // I don't know quite what "8(%ebp) mean? It has to do something with res, because
     imull  12(%ebp), %eax // here is the multiplication done. And again "12(%ebp).
     movl   %eax, -8(%ebp) // Now, we got a negative number in front of. How to interpret this?
     movl   $0, -4(%ebp) // here as well
     movl   -8(%ebp), %eax // and here again.
     movl   -4(%ebp), %edx // also here
     leave
     ret
main:
     pushl  %ebp // stores the stack frame of the calling function on the stack
     movl   %esp, %ebp // // takes the current stack pointer and uses it as the frame for the called function
     andl   $-8, %esp // what happens here and why?
     subl   $24, %esp // here, it leaves room for local variables, but why 24 bytes? a, b, c: the size of each of them is 4 Bytes. So 3*4 = 12
     movl   $3, 20(%esp) // 3 gets pushed on the stack
     movl   $5, 16(%esp) // 5 also get pushed on the stack
     movl   16(%esp), %eax // what does 16(%esp) mean and what happened with z?
     movl   %eax, 4(%esp) // we got the here as well
     movl   20(%esp), %eax // and also here
     movl   %eax, (%esp) // what does happen in this line?
     call   multiply  // thats clear, the function multiply gets called
     movl   %eax, 12(%esp) // it looks like the same as two lines before, except it contains the number 12
     movl   $0, %eax // I suppose, this line is because of "return 0;"
     leave
     ret
4

3 に答える 3

2

%ebp に対する負の参照は、スタック上のローカル変数用です。

 movl   8(%ebp), %eax // I don't know quite what "8(%ebp) mean? It has to do something with res, because`

%eax = x

 imull  12(%ebp), %eax // here is the multiplication done. And again "12(%ebp).

%eax = %eax * y

 movl   %eax, -8(%ebp) // Now, we got a negative number in front of. How to interpret this?

(u_int32_t)res = %eax // res の下位 32 ビットを設定します

 movl   $0, -4(%ebp) // here as well

res の上位 32 ビットをクリアして、32 ビットの乗算結果を uint64_t に拡張します。

 movl   -8(%ebp), %eax // and here again.
 movl   -4(%ebp), %edx // also here

retを返します。//64 ビットの結果は 32 ビット レジスタのペアとして返されます %edx:%eax

メインについては、何が起こるかを理解するのに役立つかもしれないx86 呼び出し規約を参照してください。

 andl   $-8, %esp // what happens here and why?

スタック境界は 8 で整列されています。ABI 要件だと思います

 subl   $24, %esp // here, it leaves room for local variables, but why 24 bytes? a, b, c: the size of each of them is 4 Bytes. So 3*4 = 12

8 の倍数 (おそらくアライメント要件による)

 movl   $3, 20(%esp) // 3 gets pushed on the stack

a = 3

 movl   $5, 16(%esp) // 5 also get pushed on the stack

b = 5

 movl   16(%esp), %eax // what does 16(%esp) mean and what happened with z?

%eax = b

z は 12(%esp) にあり、まだ使用されていません。

 movl   %eax, 4(%esp) // we got the here as well

b をスタックに置く (multiply() の 2 番目の引数)

 movl   20(%esp), %eax // and also here

%eax = a

 movl   %eax, (%esp) // what does happen in this line?

a をスタックに置く (multiply() の最初の引数)

 call   multiply  // thats clear, the function multiply gets called

乗算は 64 ビットの結果を %edx:%eax で返します

 movl   %eax, 12(%esp) // it looks like the same as two lines before, except it contains the number 12

z = (uint32_t) 乗算()

 movl   $0, %eax // I suppose, this line is because of "return 0;"

うん。0 を返します。

于 2013-11-14T18:55:26.560 に答える
1

これをコメントとして入力し始めましたが、長すぎて収まりませんでした。

-masm=intelアセンブリをより読みやすくするために、例をコンパイルできます。pushまた、とのpop命令を と混同しないでくださいmov。アドレスを逆参照する前にpushpop 常にそれぞれインクリメントおよびデクリメントしますが、そうではありません。espmov

値をスタックに格納する方法は 2 つあります。push各アイテムを一度に 1 つずつ追加するか、必要なスペースを前もって割り当ててから、またはmovから + 相対オフセットを使用して各値をスタックスロットにロードすることができます。espebp

espあなたの例では、最初の方法とは異なり、値をスタックに保存する前に常にインクリメントしていないため、gcc は 2 番目の方法を選択しました。

コメントで他の質問に対処するために、x86命令セットには、メモリ位置から別のメモリ位置に値を直接コピーするための命令がありません。次のようなコードを目にすることは珍しくありません。movab

  mov   eax, [esp+16]
  mov   [esp+4], eax
  mov   eax, [esp+20]
  mov   [esp], eax
  call  multiply(unsigned int, unsigned int)
  mov   [esp+12], eax

Registereaxは、2 つのスタック ロケーション間でデータをコピーするのに役立つ中間一時変数として使用されています。上記を次のように精神的に翻訳できます。

esp[4] = esp[16]; // argument 2
esp[0] = esp[20]; // argument 1
call multiply
esp[12] = eax;    // eax has return value

への呼び出しの直前のスタックは、およそ次のようになりますmultiply

lower addr    esp       =>  uint32_t:a_copy = 3 <--.  arg1 to 'multiply'
              esp + 4       uint32_t:b_copy = 5 <--.  arg2 to 'multiply'
    ^         esp + 8       ????
    ^         esp + 12      uint32_t:z = ?      <--.
    |         esp + 16      uint32_t:b = 5         |  local variables in 'main'
    |         esp + 20      uint32_t:a = 3      <--.
    |         ...
    |         ...
higher addr   ebp           previous frame
于 2013-11-15T03:50:39.490 に答える