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$
うまくいきました。
コメント