質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

2回答

5056閲覧

アセンブリとC言語

strike1217

総合スコア651

アセンブリ言語

アセンブリ言語とは、機械語を人間にわかりやすい形で記述した低水準言語です。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2016/09/09 09:58

編集2016/09/09 10:00

以下のようなプログラムを作ってみました。

#include <stdio.h>

int main(){
int i = 9;
printf("%d", i);
return 0;
}

これを逆アセンブルしたところ以下のようになりました。

0000000100000f50 <_main>:
100000f50: 55 push %rbp
100000f51: 48 89 e5 mov %rsp,%rbp
100000f54: 48 83 ec 10 sub $0x10,%rsp
100000f58: 48 8d 3d 47 00 00 00 lea 0x47(%rip),%rdi # 100000fa6 <_main+0x56>
100000f5f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
100000f66: c7 45 f8 09 00 00 00 movl $0x9,-0x8(%rbp)
100000f6d: 8b 75 f8 mov -0x8(%rbp),%esi
100000f70: b0 00 mov $0x0,%al
100000f72: e8 0d 00 00 00 callq 100000f84 <_main+0x34>
100000f77: 31 f6 xor %esi,%esi
100000f79: 89 45 f4 mov %eax,-0xc(%rbp)
100000f7c: 89 f0 mov %esi,%eax
100000f7e: 48 83 c4 10 add $0x10,%rsp
100000f82: 5d pop %rbp
100000f83: c3 retq

int i = 9; の部分が movl $0x9,-0x8(%rbp) に相当するのはわかるのですが、
この -0x8 は何ですか??

それと、前後の
lea 0x47(%rip),%rdi # 100000fa6 <_main+0x56>
movl $0x0,-0x4(%rbp)

mov -0x8(%rbp),%esi
mov $0x0,%al

この4つは何をしているのでしょうか??
教えてください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

昔コンパイラを作っていた経験から、説明します。
このCコンパイラは、コードの最適化を図っているようで、若干、出力されたアセンブラとC言語プログラム行が一致せず、一部が前後しているようです。そこで、元のC言語の順にどのように出力されているか説明します。

前の方が書かれているように
0000000100000f50 <_main>:
100000f50: 55 push %rbp
100000f51: 48 89 e5 mov %rsp,%rbp
100000f54: 48 83 ec 10 sub $0x10,%rsp
は、関数に入った時に自動的に出力されるプロローグ処理と呼ばれている部分です。%rbp(ベースポインター)で示されるアドレスから0x10離れた位置に%rsp(スタックポインタ)に設定しています。この間には、関数内で利用する変数などの領域が格納されます。このような変数の取り方を行うことで、再帰呼び出しをしてもそれぞれの関数内で値を保持することができます。通常、スタックポインタは、上下することがあるので、変数を参照する場合には、このベースポインタからの相対位置でアクセスします。

したがって、「int i = 9」は、
100000f66: c7 45 f8 09 00 00 00 movl $0x9,-0x8(%rbp)
となります。

次の「printf("%d",i);」は、
100000f58: 48 8d 3d 47 00 00 00 lea 0x47(%rip),%rdi # 100000fa6 <_main+0x56>
100000f6d: 8b 75 f8 mov -0x8(%rbp),%esi
100000f70: b0 00 mov $0x0,%al
100000f72: e8 0d 00 00 00 callq 100000f84 <_main+0x34>
100000f79: 89 45 f4 mov %eax,-0xc(%rbp)
となります。
先頭のleaは、”%d”の文字列アドレスを%rdiレジスタ格納するものです。%ripは、命令を実行するメモリ位置を保持するレジスタであり、 0x47(%rip)とは、実行している命令から0x47先であり、具体的には100000fa6 <_main+0x56> となります。"%d"などのようにプログラム中に現れる文字列は変更さることがないので、命令コードが書かれている領域に格納するケースがあります。
次の
100000f6d: 8b 75 f8 mov -0x8(%rbp),%esi
で、i変数「-0x8(%rbp)」の値を%esiに格納しています。
100000f70: b0 00 mov $0x0,%al
については、ちょっとわかりませんが、可能性があるとするならば、callによって別のサブルーチンを呼び出した後に取得する返却値をあらかじめクリアすることで、サブルーチン側で alレジスタに値を設定していない場合に不正が値が返却されるのを防いでいるのかもしれません。
最後の
100000f79: 89 45 f4 mov %eax,-0xc(%rbp)
で、printfからの返却値を「-0xc(%rbp) 」に格納しているようです。

最後に、返却値に0を設定して返却する部分(エピローグ処理)となります。
100000f77: 31 f6 xor %esi,%esi
100000f7c: 89 f0 mov %esi,%eax
100000f7e: 48 83 c4 10 add $0x10,%rsp
100000f82: 5d pop %rbp
100000f83: c3 retq
となります。
xor %esi,%esiで%esiレジスタをクリアし、mov %esi,%eaxで、その値を%eaxレジスタに格納しています。直接、%eaxに0をダイレクトに設定して良いのですが、メモリを少なくしたり、高速に動作するために、xor命令を使うケースは多くあります。
最後にスタックポインターをベースポインター位置に戻り、retq命令を実行することで、呼び出し側命令位置に戻ります。
私自身、インテル系の8bitCPUや16bitCPUのアセンブラしか知りませんが、おそらくこのような動作になっているかと思います。

投稿2016/09/09 14:49

diracpaul

総合スコア157

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2016/09/10 04:17

ありがとうございます。 分かりやすいです。
strike1217

2016/09/10 04:26

lea 0x47(%rip),%rdi mov -0x8(%rbp),%esi この2つはprintf()の第1パラメータと第2パラメータ を設定していますが、rdiとesiのレジスタをどうやって、printfに渡しているのでしょうか?
diracpaul

2016/09/10 06:20 編集

関数は、引数の数が2つとは限りません。引数の数が多くなるとレジスタで渡すことができなくなる可能性があります。このため、通常は、スタックに積んで渡すのが通常です。特に、printf関数は、引数の数が不定なのでなおさらです。 この例では、レジスタ渡しする場合には、その方式(第一パラメータは、%rdiレジスタに格納するなどのルール)に合った関数を呼んでいるのではないでしょうか?
guest

0

ベストアンサー

手元に、同じ結果が得られるコンパイラがないので想像な部分がありますが、
まず、-0x08についてです。
関数の入り口で、

mov %rsp,%rbp
sub $0x10,$rsp

としています。
これは、スタックポインタ(rsp)をベースポインタ(rbp)に代入してから、
スタックポインタ(rsp)から、16をひいています。
この操作で、この関数で使える16バイトのワークエリアを確保しています。

4バイトずつに分けるならば、
-0x4(%rbp), -0x8(%rbp), -0xC(%rbp), -0x10(%rbp)
の四つのintの領域が確保されている感じです。

9という値をiに代入しているので、そのiが-0x8(%rbp)に確保
されている様にみえますね。

次にprintfを呼びだす為の準備を行っている様ですが、
lea 0x47(%rip),%rdi
で、rdiレジスタに "%d" という文字列のポインタを入れている様です。

つぎの、
mov $0x0,-0x4(%rbp)
ですが、この処理では一見意味が無いように見えます。
おそらく、-0x4(%rbp)をクリアしておき、処理によっては
そこを使う事があるのかな?と思います。

その次の、
mov -0x8(%rbp),%esi
mov $0x0,al
ですが、先ほど代入した9をediに入れていて、alに0を入れています。
この事から、printf関数の第一引数は rdi, 第二引数は、ediに入れて呼ぶ
必要があるようですね。

alレジスタを0にしている理由はわかりません。
このコンパイラの関数呼び出しのルールでalを0にする必要があるのかも
しれません。

コンパイラが出力するコードは、コンパイラ屋さん(人間)の考たコードです。
場合によっては、無駄な(に見える)コードが出てくる事もあります。

ただ、色々な場合に対応するために、0でクリアする処理が入っているのでは
ないかと思います。

投稿2016/09/09 11:48

ShinyaAnan

総合スコア241

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問