Category Archives: 9-1.ソース・ビルド・インストール

ポインタ引数の割り当てレジスタ

前回C言語のプログラムをディスアセンブルしたソースを参考に、アセンブラを埋め込みました。関数の引数に割り当たるレジスタもおぼろげながら分かってきました。

ポインタ引数の場合、どのレジスタに割り当たるのでしょうか。
配列コピーのプログラムを作って確認してみます。

takk@deb9:~/tmp$ cat -n t3.c
     1  #include <stdio.h>
     2  #include <string.h>
     3
     4  void func2(char* a, char* b, int c)
     5  {
     6          memcpy(b,a,c);
     7  }
     8
     9  int main()
    10  {
    11          char a[10];
    12          char b[10];
    13          a[0] = 10;
    14          a[1] = 20;
    15          a[2] = 30;
    16
    17          func2(a, b, 3);
    18
    19          printf("b[0]=%d, b[1]=%d b[2]=%d\n",b[0],b[1],b[2]);
    20  }
takk@deb9:~/tmp$

func2(a,b,c)は、配列aを配列bにcの数分コピーする処理です。
ビルドして実行します。

takk@deb9:~/tmp$ gcc t3.c
takk@deb9:~/tmp$ ./a.out
a[0]=10, a[1]=20 a[2]=30
takk@deb9:~/tmp$

想定通りですね。
ではディスアセンブル。

takk@deb9:~/tmp$ objdump -d a.out | cat -n

func2のアセンブラを見てみましょう。

   134  0000000000000710 <func2>:
   135   710:   55                      push   %rbp
   136   711:   48 89 e5                mov    %rsp,%rbp
   137   714:   48 83 ec 20             sub    $0x20,%rsp
   138   718:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   139   71c:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
   140   720:   89 55 ec                mov    %edx,-0x14(%rbp)
   141   723:   8b 45 ec                mov    -0x14(%rbp),%eax
   142   726:   48 63 d0                movslq %eax,%rdx
   143   729:   48 8b 4d f8             mov    -0x8(%rbp),%rcx
   144   72d:   48 8b 45 f0             mov    -0x10(%rbp),%rax
   145   731:   48 89 ce                mov    %rcx,%rsi
   146   734:   48 89 c7                mov    %rax,%rdi
   147   737:   e8 84 fe ff ff          callq  5c0 <memcpy@plt>
   148   73c:   90                      nop
   149   73d:   c9                      leaveq
   150   73e:   c3                      retq

文字列のコピーなのでアドレスを扱います。よってrdiやrsiといったインデックスレジスタが使用されますが、rから始まるレジスタ名は、64bitのレジスタになっています。
たとえば、RAXレジスタは以下ような単位でアクセスできます。

+-------+-------+-------+-------+-------+-------+-------+-------+
|                           RAX(64)                             |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                               |            EAX(32)            |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                                               |     AX(16)    |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                                               | AL(8) | AL(8) |
+-------+-------+-------+-------+-------+-------+-------+-------+

さてfunc2のローカル変数を見ていきましょう。
mainからfunc2の呼び出し部分のアセンブラは以下のようになっています。

   159   753:   48 8d 4d ec             lea    -0x14(%rbp),%rcx
   160   757:   48 8d 45 f6             lea    -0xa(%rbp),%rax
   161   75b:   ba 03 00 00 00          mov    $0x3,%edx
   162   760:   48 89 ce                mov    %rcx,%rsi
   163   763:   48 89 c7                mov    %rax,%rdi
   164   766:   e8 a5 ff ff ff          callq  710 <func2>

でfunc2のプロトタイプはこのようになっています。

     4  void func2(char* a, char* b, int c)

func2のaがrdi、bがrsi、cがrdxに割り当たっていますね。
func2では、aからbへコピーするので、レジスタの役割としてはa、b逆に割り当たっています。関数のインターフェースを決める時は、第1引数をdestinationにしておいた方が、rdi/rsiレジスタが一致して読みやすくなりますね。

つまり以下のようなプログラムにする方が、アセンブラが分かりやすくなります。

     1  #include <stdio.h>
     2  #include <string.h>
     3
     4  void func2(char* b, char* a, int c)
     5  {
     6          memcpy(b,a,c);
     7  }
     8
     9  int main()
    10  {
    11          char a[10];
    12          char b[10];
    13          a[0] = 10;
    14          a[1] = 20;
    15          a[2] = 30;
    16
    17          func2(b, a, 3); // aをbに3Byteコピーする
    18
    19          printf("b[0]=%d, b[1]=%d b[2]=%d\n",b[0],b[1],b[2]);
    20  }
takk@deb9:~/tmp$