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

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

ただいまの
回答率

87.78%

アセンブリの基本を教えてください

受付中

回答 5

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 2,734

score 19

c言語のアセンブリの基本について教えてください。
質問は#のあとに書いてあります。
よろしくお願いします。

CPU他情報
description: Computer
width: 32 bits
*-core
description: Motherboard
physical id: 0
*-memory
description: System memory
physical id: 0
size: 1988MiB

Architecture:          i686
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 15
Model name:            Intel(R) Core(TM)2 Duo CPU     L7100  @ 1.20GHz
Stepping:              11
CPU MHz:               1201.000
CPU max MHz:           1201.0000
CPU min MHz:           800.0000
BogoMIPS:              2394.07
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              4096K

ソース

include <stdio.h>

int main()
{    
int i;
for (i=0; i<10; i++)
{
printf("Hello, World!\n");
}
return  0;

}

コマンド
gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2)
$ gcc -o firstprog firstprog.c 
$ objdump -D firstprog | grep -A70 main.:

0804840b <main>:
1 804840b:    8d 4c 24 04              lea    0x4(%esp),%ecx 
2 804840f:    83 e4 f0                  and    $0xfffffff0,%esp #なぜこれをしないといけないのか?
3 8048412:    ff 71 fc                  pushl  -0x4(%ecx) ##これと対になるのはなにか?
4 8048415:    55                        push   %ebp
5 8048416:    89 e5                     mov    %esp,%ebp
6 8048418:    51                        push   %ecx ##これと対になるのはなにか?
7 8048419:    83 ec 14                  sub    $0x14,%esp ##なぜ10行目があるのに、これをしないといけないのか?
8 804841c:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp)
9 8048423:    eb 14                      jmp    8048439 <main+0x2e>
10 8048425:    83 ec 0c                   sub    $0xc,%esp
11 8048428:    68 d0 84 04 08           push   $0x80484d0
12 804842d:    e8 ae fe ff ff            call   80482e0 <puts@plt>
13 8048432:    83 c4 10                  add    $0x10,%esp
14 8048435:    83 45 f4 01              addl   $0x1,-0xc(%ebp)
15 8048439:    83 7d f4 09              cmpl   $0x9,-0xc(%ebp)
16 804843d:    7e e6                     jle    8048425 <main+0x1a>
17 804843f:    b8 00 00 00 00           mov    $0x0,%eax ##これはなにをしてるのか?
18 8048444:    8b 4d fc                  mov    -0x4(%ebp),%ecx##これはなにをしてるのか?
19 8048447:    c9                        leave  
20 8048448:    8d 61 fc                 lea    -0x4(%ecx),%esp
21 804844b:    c3                       ret

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • ikedas

    2017/03/27 12:41

    元のCのソースコードも示して下さい。また、アセンブラの種類、対象アーキテクチャも明記していただけませんか。

    キャンセル

回答 5

+6

そんなに詳しいわけではないのですが、読み解いてみました。なお、回答時点では元のCのソースコードを参照できなかったため、処理の詳細について認識違いがあるかもしれません。

1 804840b:    8d 4c 24 04              lea    0x4(%esp),%ecx  
2 804840f:    83 e4 f0                 and    $0xfffffff0,%esp #なぜこれをしないといけないのか? 

こうする理由のひとつは、スタックの位置をキャッシュラインサイズに合わせるためだと思います。スタックがキャッシュラインサイズ (x86では64バイト) の境界をまたぐように配置されると、スタック操作の際にキャッシュミスが多発して速度が落ちます。スタックを16バイト境界に揃えれば、速度低下を回避しやすくなります。が、なぜ最大の64バイト境界で揃えないのかはわかりません (メモリの節約のためでしょうか)。

もうひとつ。SSE命令では128ビットのレジスタを使う関係から、全てのデータが16バイト境界にそろえてある必要があります。スタックもこれに合わせて、4バイトや8バイトではなく16バイト境界で揃えているのだと考えられます。

いずれにせよ、スタックポインタを書き換えてしまうとretしたときに呼び出し元に戻れません。書き換える前に、スタック最上部に積んであるもの (戻り先のアドレス) のアドレス0x4(%esp)を、%ecxに保存しています。

3 8048412:    ff 71 fc                  pushl  -0x4(%ecx) ##これと対になるのはなにか? 

これは何かを退避しているわけではありません。%ecxに入っているアドレスに入っているもの (つまり、戻り先のアドレス) を、%espを変更した後のスタックに積みなおしているだけです。

ここまでは、スタックのアライメント調整のためのコードです。関数が呼び出されたときにスタックがかならず16バイト境界から始まっているようなアーキテクチャでは必要ないと思われます。

4 8048415:    55                        push   %ebp 
5 8048416:    89 e5                     mov    %esp,%ebp 
6 8048418:    51                        push   %ecx ##これと対になるのはなにか? 
7 8048419:    83 ec 14                  sub    $0x14,%esp ##なぜ10行目があるのに、これをしないといけないのか? 

ここでスタックフレームを作っています。上記のコードを実行した後のスタックをスタックポインタの値を基準に示すと、次のようになっているはずです。

 アドレス(相対)   内容 
 0x00   スタックポインタ (%esp) が指す位置 
  ...   ... 
 0x10   ベースポインタ (%ebp) が指す位置 
 0x14   %ecxの値 
 0x18   呼び出し元のベースポインタ (%ebp) 
 0x1C   戻り先アドレス   
 ...    ... 
 ???    調整前のスタックポインタが指す位置 
 ???    戻り先アドレス 

%espの指す位置より上位で%ebpの指す位置までの間の16バイトは、自動変数に使われます。16バイトの領域全てが使われるとは限りません (下記の処理でも最下位の4バイトしか使っていません)。

呼び出し元のベースポインタ%ebpをスタックに積んでおくのはわかりますが、%ecxを積む意味は私には分かりません。6行目、7行目の疑問についてはそれぞれ後述。

8 804841c:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp) 
9 8048423:    eb 14                    jmp    8048439 <main+0x2e> 
10 8048425:    83 ec 0c                 sub    $0xc,%esp 
11 8048428:    68 d0 84 04 08           push   $0x80484d0 
12 804842d:    e8 ae fe ff ff           call   80482e0 <puts@plt> 
13 8048432:    83 c4 10                 add    $0x10,%esp ##これをするとesp-0x14とesp-0x16の間にアドレス空間ができてしまう気がするのですが? 
14 8048435:    83 45 f4 01              addl   $0x1,-0xc(%ebp) 
15 8048439:    83 7d f4 09              cmpl   $0x9,-0xc(%ebp) 
16 804843d:    7e e6                    jle    8048425 <main+0x1a> 

処理の本体です。ここでスタックポインタを動かしています (10行目)。そしてスタックに引数を積み、putsを呼びます (11-12行目)。スタックポインタを動かす理由は、putsに入ったときにスタックが16バイト境界で揃っているようにするためではないでしょうか。

putsから戻ってきたら、pushした分をpopした上で動かしたスタックポインタを元に戻してもいいのですが、それらをまとめてスタックポインタの値を加算するだけですませています (13行目)。動かした分の12バイト (0xc) とpushした4バイトで、合わせて16バイト (0x10) です。

17 804843f:    b8 00 00 00 00           mov    $0x0,%eax ##これはなにをしてるのか? 

返り値を指定しているのでしょう。お使いのアーキテクチャでは、int型の関数では返り値を%eaxに入れて返すのだと思われます。

19 8048447:    c9                       leave   
20 8048448:    8d 61 fc                 lea    -0x4(%ecx),%esp 
21 804844b:    c3                       ret


leavemov %ebp, %esp; pop %ebpと同じ意味ですから、4-5行目で行ったスタックポインタの調整を元にもどします。これによってスタックフレームは破棄され、自動変数の領域が解放されることになります。またスタックポインタの調整以後にスタックに積まれたものも、popで元に戻す必要はありません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/27 19:17

    > %ecxを積む意味は私には分かりません。

    これはmainの第一引数のアドレスです。main()に引数がありそれをつかっている場合には、破壊されていなければ%ecxを、破壊されていればこの積まれた値を経由して使います。

    アドレス相対0x1Cにある戻り先のアドレスをpushしている意味は私にも謎です。これは使われていませんし、というかこれを使って戻ってしまうと呼び出し側にとってesp破壊です。もしかすると__builtin_return_address()のようなgcc拡張がmain()で使われたときとかに関係するのかも・・。

    キャンセル

  • 2017/03/27 21:09

    なるほど! 引数のことを忘れていました。なんで4を足したり引いたりしているのかと不思議でしたが、確かにこのアドレスは必要ですね。

    使われない戻り先アドレスも謎ですが、sharowさんがおっしゃるようなことかもしれませんね。スタートアップスタブやライブラリ関数はスタックのアライメント調整をやってくれない (そういうことを考慮していない) ようですから、辻褄合わせが必要なのかも知れません。

    キャンセル

+1

こんにちは。

全部は分からないので、解る部分だけ回答してみます。

2 804840f:    83 e4 f0                  and    $0xfffffff0,%esp #なぜこれをしないといけないのか?

ローカル変数のアライメントを調整してローカル変数アクセス速度を最適化することが目的です。
自作エミュレータで学ぶx86アーキテクチャ: コンピュータが動く仕組みを徹底理解!の立ち読みできる部分にも解説がありました。(この本、解りやすそうな感じがします。)

7 8048419:    83 ec 14                  sub    $0x14,%esp ##なぜ10行目があるのに、これをしないといけないのか?

main()関数の一番外側のブロックで有効なローカル変数領域の確保ではないかと思います。
そして、恐らくfor()ループがあるのではないでしょうか? 10行目はそのforループ内で確保しているローカル変数領域の確保ではないかと思います。

6 8048418:    51                        push   %ecx ##これと対になるのはなにか?

この直前でmov %esp,%ebpしているので、6行目終了時、ebp-4のアドレスにecxが記録されています。下記にてecxを回復しているのだと思います。

18 8048444:    8b 4d fc                  mov    -0x4(%ebp),%ecx##これはなにをしてるのか?

17 804843f:    b8 00 00 00 00           mov    $0x0,%eax ##これはなにをしてるのか? 

return 0;の0を設定しているのではないかと思います。
関数の戻り値を返却する際にeaxを使うことは良くあります。

80x86系のアセンブラは大昔に少し触っただけなので外していたらごめんなさい。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

0804840b <main>:
1 804840b:    8d 4c 24 04              lea    0x4(%esp),%ecx 
2 804840f:    83 e4 f0                  and    $0xfffffff0,%esp #なぜこれをしないといけないのか?
3 8048412:    ff 71 fc                  pushl  -0x4(%ecx) ##これと対になるのはなにか? →多分、18行ではないのでしょうか?
4 8048415:    55                        push   %ebp
5 8048416:    89 e5                     mov    %esp,%ebp
6 8048418:    51                        push   %ecx ##これと対になるのはなにか?
7 8048419:    83 ec 14                  sub    $0x14,%esp 
8 804841c:    c7 45 f4 00 00 00 00     movl   $0x0,-0xc(%ebp)
9 8048423:    eb 14                     jmp    8048439 <main+0x2e>
10 8048425:    83 ec 0c                  sub    $0xc,%esp
11 8048428:    68 d0 84 04 08           push   $0x80484d0
12 804842d:    e8 ae fe ff ff            call   80482e0 <puts@plt>
13 8048432:    83 c4 10                  add    $0x10,%esp ##これをするとesp-0x14とesp-0x16の間にアドレス空間ができてしまう気がするのですが?
14 8048435:    83 45 f4 01              addl   $0x1,-0xc(%ebp)
15 8048439:    83 7d f4 09              cmpl   $0x9,-0xc(%ebp)
16 804843d:    7e e6                     jle    8048425 <main+0x1a>
17 804843f:    b8 00 00 00 00           mov    $0x0,%eax ##これはreturn 0でしょうか?
18 8048444:    8b 4d fc                  mov    -0x4(%ebp),%ecx##これはなにをしてるのか? 多分3行目とついになってのでは?
19 8048447:    c9                        leave  
20 8048448:    8d 61 fc                 lea    -0x4(%ecx),%esp
21 804844b:    c3                       ret

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

自分はきちんとした知識を持ってないので個別にちゃんとした解説はできませんが、読み解くうえで参考になるだろう事項があるのでコメントしてみます。これらについてどこかによい教科書なり解説記事なりがあると学びやすいと思いますが、残念ながら自分は知りません。(性能上気になる点を確認するためにアセンブリーソースを見る程度ですので、本格的な知識は持ってないのです)

  • スタックフレームとは何か(そもそもスタックマシンとはなにか)
    Cでmain関数から関数fを呼んでそこからgを読んで・・・という具合にし、各関数の引数やローカル変数のアドレスを調べると大まかなイメージがつかめます。スタックフレームはコンパイラーの種類によって色々変わりますので「このコンパイラーはこういうスタックフレームにしているようだ」というあたりを付けつつコードを見るとよいと思います。
    おそらくC/C++プログラマーの方は(詳細さにばらつきはあるにせよ)スタックフレームがどういう構造かは知っている方も多いと思います。というのはメモリー破壊のデバッグの際に知るはめになるからです。

  • 各レジスターの役割
    今日よく目にするのはx64(?)などと思いますが、espはスタックポインターでebpはフレームポインターというのを知っておけば理解しやすいと思います。C++だとthisがどのレジスターに載っているかなども意識するとよいかと思います。

  • コンパイルの最適化
    最適化レベルが変わると効率が悪い機械語コードがよりよいものに変わります。それを踏まえて読むとよいかも知れません。「なんか無駄なコードがでてる」と思ったとき最適化レベルを変えてコンパイルすることでどのくらい賢い機械語コードに変化するかが観察できると思います。(最適化されすぎて読めなくなることも・・・)

  • プロセッサの初歩的な知識
    時折不自然な順番で命令が並んでいるように見えることがあります。しかしプロセッサが命令をパイプラインで実行していると知っていれば多少不自然にみえても「まぁそういう事情なのだろう」で済ませることができます。しかし本当にアセンブリー言語でバリバリに書こうとする人ならプロセッサーのアーキテクチャーを詳しく知り「なぜこういう順番にするとパイプラインがうまく動くのか」を把握した上で読んだり・書いたりするのかも知れませんね。しかしカーネルプログラミングのようにプロセッサのより詳しい仕様を知らないと全然歯がたたないものもありますね。自分はせいぜい「システムコールはソフトウェア割り込み命令なのかな?」という程度しか知りません。

質問者さんの目的にもよるでしょうが、上に書いた程度を学ぶとそこそこの役には立つ気がします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/27 07:30

    矛盾した順番でコマンドが並ぶことがあるのですか?
    例えばどういうものでしょう?

    キャンセル

  • 2017/03/27 09:11 編集

    「矛盾」じゃなく「不自然」というべきでした。矛盾した順番で並んでいてはおかしいですね!ご指摘ありがとうございます。
    ---
    自分が書こうとしたのはCでの式や文に対する機械語命令がC上で書かれている順番に必ずしも並ばないことがあるというようなことです。

    キャンセル

  • 2017/03/27 10:11

    アセンブラではなく C ソースをコンパイルした際に、パイプラインを考慮した最適化によって命令が前後するという意味でしょうか? どうも私が読み違えていたようです。ありがとうございました。

    キャンセル

  • 2017/03/27 12:40

    > パイプラインを考慮した最適化によって命令が前後するという意味

    そういうものをイメージしていました。自分の表現は適切ではなかったですね。Zuishinさんの表現が端的にポイントをついているように感じました。

    (レジスター・メモリー・ALUなどのアクセスの衝突をなるべく避ける目的でうまいこと順番を入れかている・・・というようなことを書こうとしたのですがどうも自身なくて、簡単な表現に改めようとしたものの結局変な説明になってしまってました。申し訳ないです。)

    キャンセル

0

ご質問のストレートな回答ではないことをお断りしておきます。
アセンブラのプログラミングに当たっては、構文の理解もさることながらレジスタ、メモリ(アドレス)、スタックに関する概念と1部ハードウエアについても理解が必要です。ご質問のような部分的な疑問について回答を得たとしても、謎はさらに深まる恐れが有ります。要領良く理解するケースも無いとは言えませんが。
参考書などお探しになって、体系的に理解することをお勧めします。
自分はZ80(30年以上前ですが)で参考書を何度も読み返して習得し、ハード設計とファームウェアを書きましたがかなりの学習コストを要しました。今の本で紹介できるものに心当たりが無くて済みません。
ご参考までに。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 87.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る