C言語のプログラムにアセンブラを書く

rcs学習もそろそろ飽きてきたので、気まぐれにより、Cとアセンブラやります。
C言語のプログラムの中にアセンブラを書きたい時ってたまにありますが、アセンブラってプロセッサによって変わってきますよね。プロセッサはたくさんあるので、全部のアセンブラを覚えるのって大変ですし、たまに書きたい言語をいちいち覚えたくありません。
たまにだけ使いたいアセンブラは、ディスアセンブルしたソースを見て、その場で学習して使えば良いです。

では適当にC言語プログラム書いてみます。

takk@deb9:~/tmp$ cat t.c
#include <stdio.h>

void func1(int a, int b, int c)
{
        printf("a=%d, b=%d, c=%d\n",a,b,c);
}

int main()
{
        func1(10,20,30);
}
takk@deb9:~/tmp$

func1という関数の引数に10,20,30を渡して実行すると、各値を標準出力するプログラムです。
実行結果は分かり切ってますので、さっそくビルドして、ディスアセンブル。

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

func1のアセンブラと、mainのアセンブラのみ抜粋します。

   129  00000000000006b0 <func1>:
   130   6b0:   55                      push   %rbp
   131   6b1:   48 89 e5                mov    %rsp,%rbp
   132   6b4:   48 83 ec 10             sub    $0x10,%rsp
   133   6b8:   89 7d fc                mov    %edi,-0x4(%rbp)
   134   6bb:   89 75 f8                mov    %esi,-0x8(%rbp)
   135   6be:   89 55 f4                mov    %edx,-0xc(%rbp)
   136   6c1:   8b 4d f4                mov    -0xc(%rbp),%ecx
   137   6c4:   8b 55 f8                mov    -0x8(%rbp),%edx
   138   6c7:   8b 45 fc                mov    -0x4(%rbp),%eax
   139   6ca:   89 c6                   mov    %eax,%esi
   140   6cc:   48 8d 3d b1 00 00 00    lea    0xb1(%rip),%rdi        # 784 <_IO_stdin_used+0x4>
   141   6d3:   b8 00 00 00 00          mov    $0x0,%eax
   142   6d8:   e8 83 fe ff ff          callq  560 <printf@plt>
   143   6dd:   90                      nop
   144   6de:   c9                      leaveq
   145   6df:   c3                      retq
   146
   147  00000000000006e0 <main>:
   148   6e0:   55                      push   %rbp
   149   6e1:   48 89 e5                mov    %rsp,%rbp
   150   6e4:   ba 1e 00 00 00          mov    $0x1e,%edx
   151   6e9:   be 14 00 00 00          mov    $0x14,%esi
   152   6ee:   bf 0a 00 00 00          mov    $0xa,%edi
   153   6f3:   e8 b8 ff ff ff          callq  6b0 <func1>
   154   6f8:   b8 00 00 00 00          mov    $0x0,%eax
   155   6fd:   5d                      pop    %rbp
   156   6fe:   c3                      retq
   157   6ff:   90                      nop

mainからfunc1をコールする時、引数に値を設定するアセンブラは、16進数の数値から読み取るとこの部分ですね。

   150   6e4:   ba 1e 00 00 00          mov    $0x1e,%edx
   151   6e9:   be 14 00 00 00          mov    $0x14,%esi
   152   6ee:   bf 0a 00 00 00          mov    $0xa,%edi

3番目の引数から、2番目、1番目の順序でレジスタに設定されています。
esiはsource index、ediはdestination index、edxはdata、というように、
各レジスタ名で用途が決まっていますが、絶対そういう使い方をしないといけないってことはありません。
上のプログラムでは、10,20,30という値は、sourceでもdestinationでもなく、ただのdataなので、分かりやすく書くなら、dx1,dx2,dx3みたいなレジスタ名であれば良いのですが、そんなレジスタはないので、空いてるレジスタが割り当てられています。まあ、コンパイル時にどのレジスタから使うかは優先順位が決まっているのでしょうけど、気にしないことにします。

では、元のソースをコピーして、アセンブラを混入させるソースを作ります。

takk@deb9:~/tmp$ cp t.c t2.c

ディスアセンブルされたソースの、この行と同じアセンブラをC言語のソースに埋め込みたいと思います。

   135   6be:   89 55 f4                mov    %edx,-0xc(%rbp)
takk@deb9:~/tmp$ cat t2.c
#include <stdio.h>

void func1(int a, int b, int c)
{
        asm("mov %edx, -0xc(%rbp)");
        printf("a=%d, b=%d, c=%d\n",a,b,c);
}

int main()
{
        func1(10,20,30);
}
takk@deb9:~/tmp$

main関数の150行目でedxに0x1e、つまり30を代入してるので、func1関数の135行目のアセンブラが実行される時も、edxには30が格納されています。それをrbpの-0xcの位置に上書きするだけの処理になります。

+------+
|  30  |rbp-0xc
+------+
|  20  |rbp-0x8
+------+
|  10  |rbp-0x4
+------+

実行してみます。

takk@deb9:~/tmp$ gcc t2.c
takk@deb9:~/tmp$ ./a.out
a=10, b=20, c=30
takk@deb9:~/tmp$

想定通りの結果ですね。

次は、30をrbpの別の場所に格納してみます。

takk@deb9:~/tmp$ cat t2.c
#include <stdio.h>

void func1(int a, int b, int c)
{
        asm("mov %edx, -0x4(%rbp)");
        printf("a=%d, b=%d, c=%d\n",a,b,c);
}

int main()
{
        func1(10,20,30);
}
takk@deb9:~/tmp$

埋め込んだアセンブラでは、-0x4(%rbp)を指定しているので、以下のように10が入っていたメモリには30が上書きされるはずです。

+------+
|  30  |rbp-0xc
+------+
|  20  |rbp-0x8
+------+
|  30  |rbp-0x4
+------+

実行してみます。

takk@deb9:~/tmp$ gcc t2.c
takk@deb9:~/tmp$ ./a.out
a=30, b=20, c=30
takk@deb9:~/tmp$

うまくいきました。

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA