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

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

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

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

C

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

CPU

CPUは、コンピュータの中心となる処理装置(プロセッサ)で中央処理装置とも呼ばれています。プログラム演算や数値計算、その他の演算ユニットをコントロール。スマホやPCによって内蔵されているCPUは異なりますが、処理性能が早いほど良いとされています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

8回答

1412閲覧

アセンブリ出力と逆アセンブルのプログラムの比較について。

carnage0216

総合スコア194

アセンブリ言語

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

C

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

CPU

CPUは、コンピュータの中心となる処理装置(プロセッサ)で中央処理装置とも呼ばれています。プログラム演算や数値計算、その他の演算ユニットをコントロール。スマホやPCによって内蔵されているCPUは異なりますが、処理性能が早いほど良いとされています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2018/03/07 15:24

以下のC言語をアセンブリプログラムとオブジェクトプログラムから逆アセンブルしました。

/* hoge.c */ int func(int n) { int a = 2; int b = 3; int ret; if(n == 0){ ret = a + b; }else{ ret = a * b; } return ret; }

こちらがアセンブリプログラムです。

.text .globl _func .def _func; .scl 2; .type 32; .endef _func: LFB0: .cfi_startproc pushl %ebp # .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp #, .cfi_def_cfa_register 5 subl $16, %esp #, movl $2, -8(%ebp) #, a movl $3, -12(%ebp) #, b cmpl $0, 8(%ebp) #, n jne L2 #, movl -8(%ebp), %edx # a, tmp93 movl -12(%ebp), %eax # b, tmp94 addl %edx, %eax # tmp93, tmp92 movl %eax, -4(%ebp) # tmp92, ret jmp L3 # L2: movl -8(%ebp), %eax # a, tmp96 imull -12(%ebp), %eax # b, tmp95 movl %eax, -4(%ebp) # tmp95, ret L3: movl -4(%ebp), %eax # ret, _7 leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc LFE0: .ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"

こちらが逆アセンブルしたプログラムです。

Disassembly of section .text: 00000000 <_func>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 10 sub $0x10,%esp 6: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%ebp) d: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%ebp) 14: 83 7d 08 00 cmpl $0x0,0x8(%ebp) 18: 75 0d jne 27 <_func+0x27> 1a: 8b 55 f8 mov -0x8(%ebp),%edx 1d: 8b 45 f4 mov -0xc(%ebp),%eax 20: 01 d0 add %edx,%eax 22: 89 45 fc mov %eax,-0x4(%ebp) 25: eb 0a jmp 31 <_func+0x31> 27: 8b 45 f8 mov -0x8(%ebp),%eax 2a: 0f af 45 f4 imul -0xc(%ebp),%eax 2e: 89 45 fc mov %eax,-0x4(%ebp) 31: 8b 45 fc mov -0x4(%ebp),%eax 34: c9 leave 35: c3 ret 36: 90 nop 37: 90 nop

あの扱う値は同じなのにmovlの部分が逆アセンブリではmovになっていたりしますが、これはどちらのアセンブリプログラムが正しいというかちゃんとコードとして良いものなのでしょうか?
movlは4バイトの値を扱う際の命令ですが、逆アセンブリではmovとなっています。データや数値の扱いなどでバイトやbitは正確な方が良いと思うのでどうかと思うのですが。
あるいは逆アセンブリのほうの最初で

6: c7 45 f8 02 00 00 00 movl $0x2,-0x8(%ebp) d: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%ebp)

と数値$0x2と$0x3を4バイトとして扱うよとmovlで各レジスタへコピーしたため、今後のレジスタでも4バイトとして扱われるためわざわざmovlとは書かず、mov命令となっているのかなと素人の私なりに考えてみましたが実際はどうなのかいまいちわかりません。
なぜ逆アセンブルすると変わるのでしょうか?またどっちのアセンブリソースのほうが良い?というか正確なものなのでしょうか?
どうかよろしくお願いいたします。

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

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

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

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

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

guest

回答8

0

逆アセンブルの方は、必要のないサフィックスを付けていないだけでは?

movl $0x2,-0x8(%ebp)は付けないとサイズが分からない。
mov -0x8(%ebp),%edxは付けなくてもサイズが確定している。

投稿2018/03/08 00:55

fuzzball

総合スコア16731

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

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

carnage0216

2018/03/08 10:32

回答どうもありがとうございます。 movl $0x2,-0x8(%ebp)のmovlは扱うデータ量がわからないので4バイトのデータで扱ってしまおうという考えでmovlを使ったのでしょうか? それとは違いmov -0x8(%ebp),%edxはmovにデータ量の情報を付けなくてよいのでサイズが確定しているということですか? ただ、mov -0x8(%ebp),%edxのどこを読んでサイズが確定しているというかサイズがわかったかはわかりませんが。
fuzzball

2018/03/08 11:13

代入するのが32ビットレジスタでしょ?
rubato6809

2018/03/08 11:21

> 扱うデータ量がわからないので4バイトのデータで扱ってしまおうという考えでmovl 違います。 この「2」という値は int a = 2; の「2」ですよ。「a」という変数を -8(%bp) から始まる4バイトに割り当てた、なぜ4バイトなのかといえば、int型だから。 2という値を4バイトのメモリに格納するから、movl としなければならなかった。 > mov -0x8(%ebp),%edxのどこを読んでサイズが確定している オペランドに %edx があるから4バイトのデータ転送だと確定する。
carnage0216

2018/03/08 11:32

どうもありがとうございます。
guest

0

昔、8086アセンブラを作ることになるかもしれないということで、Intel記法を調べてみたことがあります。

Intel記法では、参照するデータがWORDなのかBYTEなのかなどについて、データセグメントに定義している情報が必要となります。Intel記法として、これらデータセグメントの定義がコードセグメントに前に必ずなければならないのであれば、問題ないのですが、そうでないとすると、一度全部のソースを調べて各データのタイプ(WORDかBYTEか)を調べてから、コードセグメントのニューモニックを機械語に変換することが必要となります。たしか、データのタイプによっては、命令コードのサイズが変わったかと思います。このため、確実に翻訳するには、2回アセンブラソースを走査する必要があり、アセンブルに時間がかかってしまいます。逆にデータのタイプがWORDなのに、BYTEでアクセスするようなミスを防ぐことができます。

一方、AT&T方式では、アセンブラソースを上から順に操作するだけで、ニューモニックからWORD用の命令なのか、BYTE用の命令なのか即座にわかるので、そのまま機械語に変換することができます。あとは、アドレス部分の書き方ですが、これは、一連の作業が終わってから芋づる式にポインターを使って書き直せべ簡単に機械語翻訳が可能となります。

つまり、インテル方式では、プログラムミスを避ける可能性が高くなりますが、アセンブルに時間がかかる。逆にAT&T方式では、プログラムのチェックは甘くなるがアセンブルは高速にできるということになります。

昔は、ソースコードを何回も走査と時間がかかると言うことで、できるだけ走査回数を少なくすることが良しとされていました。とくに、コンパイラでもPascalなどのワンパスコンパイラ(一回走査するだでコンパイル)が有名です。詳しくは、
https://ja.wikipedia.org/wiki/コンパイラ#ワンパスとマルチパス
に書かれています。
また、アセンブラについてもいいWikiを見つけました。以下のリンクを見てください。
https://ja.wikipedia.org/wiki/アセンブリ言語#アセンブラ
参考になると思います。

投稿2018/03/14 14:42

diracpaul

総合スコア157

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

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

catsforepaw

2018/03/14 17:00

> Intel記法では、参照するデータがWORDなのかBYTEなのかなどについて、データセグメントに定義している情報が必要となります。 そんなものは不要ですよ。おそらく思い違いをされているのだと思いますが、Intel記法では、扱うデータサイズを`word ptr`とか`byte ptr`などをオペランドに付けて判別しています。 例えば、 mov dword ptr [EDI], 123 これだと、EDIの示すアドレスに32bit値で123を格納するという処理になります。
diracpaul

2018/03/14 22:00

古い記憶ですが、 データセグメントにdwordの変数領域xxxがあった場合、 mov [EDI].xxx と書けませんか?
fuzzball

2018/03/15 00:39 編集

>>diracpaulさん それはラベルを参照しているだけだから、領域のサイズに関わらずdwordなのでは?
catsforepaw

2018/03/15 03:57

diracpaulさん word ptrのようなサイズ指定は省略可能ですから、省略した際はアセンブラが適当に判断して補完してくれるでしょう。`dw`で確保した領域はword ptrとみなしてくれるかもしれません(試していないので確かなことは言えませんが)。 ただ、それは「そういう書き方もできる」というだけの話であって、あなたが回答に書かれているような制限がないことは先にコメントした通りですし、Intel記法とAT&T記法でできることに違いはありません(あったら困ります)。 ちなみに、as(GNU Assembler)は元々はAT&T記法だけでしたが、現在はIntel記法にも対応しています。
diracpaul

2018/03/15 09:12

最初にインテルのアセンブラ仕様を見て気になったのは、MOVS,STOS,SCASです。これらのコマンドは、レジスタが決まっているので、オペランドなしが可能です。しかし、word 単位で操作したり、byte単位で操作することにが可能です。仕様からは.diレジスタやsiレジスタが指すアドレスのタイプをアセンブラで解釈するようにおもわれました。でも、実際には、word ptrやbyte ptrでコーディングしているのかも知れませんね。
fuzzball

2018/03/15 09:22

オペランドなしの場合は後ろにサイズを付けます。 movsb, movsw, movsd
guest

0

fuzzballさんが回答された通りです。
逆アセンブラは必要のないサフィックスをつけずに表示しています。
そして、GNUアセンブラにとって、movもmovlも、どちらも正しい。

質問者は手元に、コンパイラが出力したアセンブリコードがあり、アセンブラ(as)もあるのだから、アセンブリコードに手を加えてアセンブルするだけで確かめられるはずです、こんなふうに。

$ as -a hoge.s (途中省略) 23 0036 89C3 movl %eax, %ebx # 32bit 24 0038 89C3 mov %eax, %ebx # 32bit 25 003a 6689C3 movw %ax, %bx # 16bit 26 003d 6689C3 mov %ax, %bx # 16bit 27 0040 88C3 movb %al, %bl # 8bit 28 0042 88C3 mov %al, %bl # 8bit 29 30 0044 8945FC movl %eax, -4(%ebp) # 32bit 31 0047 8945FC mov %eax, -4(%ebp) # 32bit 32 004a 668945FC movw %ax, -4(%ebp) # 16bit 33 004e 668945FC mov %ax, -4(%ebp) # 16bit 34 0052 8845FC movb %al, -4(%ebp) # 8bit 35 0055 8845FC mov %al, -4(%ebp) # 8bit 36 37 0058 C745F802 movl $2, -8(%ebp) # 32bit 37 000000 38 005f 66C745F8 movw $2, -8(%ebp) # 16bit 38 0200 39 0065 C645F802 movb $2, -8(%ebp) # 8bit

オペランドに書かれたレジスタ名で処理サイズが決まる場合は、movとmovl、どちらも同じ機械語にアセンブルされることがわかります。「手と頭を動かす」とは、例えばこういうこと。

さらに、サフィックスの無い mov $2, -8(%ebp) という行をアセンブルしたらどうなるか、質問者ご自身の手で確かめてください。

ご存知と思いますが、$ cc hoge.c の代わりに$ cc hoge.s でも実行ファイル(MinGWだから a.exe)が作られ、もちろん実行可能です。バグがあれば吹っ飛びます笑。先日お答えした「コンパイラにアセンブリ言語を出力させて雛形・お手本にします」とは、このこと。躊躇せず、どんどん手を入れて、いろいろ試してみることですね。
Enjoy!

投稿2018/03/08 10:23

rubato6809

総合スコア1380

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

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

0

プログラムの正しさとはそもそもなんですか?

「元のC言語のソースを適切に表現する」という意味ならば答えは存在しません。C言語においてはint型の具体的なバイト数が定められていないからです。

投稿2018/03/08 09:58

HogeAnimalLover

総合スコア4830

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

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

0

こんにちは。

回答依頼が届いていましたので来ました。
私自身はPC用CPUのアセンブラを使った経験は僅かしか無いので具体的なことは分かりません。
ただ、「言語」全般に言えることですが、同じことを表現するのに複数の表現方法があるのは一般的と思います。「プログラミング言語」は曖昧さを嫌うので比較的表記の揺れは少ないですが、全く無いわけではありません。そのような表記の揺れではないかと思います。

ご提示されている表記の差は「センター」と書くのか「センタ」と書くのか程度の差異のように感じます。
つまり、Cコンパイラのアセンブリ出力の開発者と逆アセンブラ開発者のセンスの差であり、かつ、それらの揺れを統一する作業が行われていない、もしくは行われていても漏れたというだけだろうと思います。

投稿2018/03/08 07:24

Chironian

総合スコア23272

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

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

0

x86アセンブリのニーモニックにはIntel記法とAT&T記法があり、マイクロソフトはIntel記法を使っていますが、gnu系のツールでは、なぜかAT&T記法が使われています(ライセンスの問題? その辺はよく判りません)。

なぜ逆アセンブルすると変わるのでしょうか?

AT&T記法では命令の後ろに扱うデータサイズに応じたサフィックス(b,s,w,l,qなど)を付けることになっていますが、レジスターも名前でデータサイズを区別しており、レジスターに対する操作はレジスター名でデータサイズが決まるため、わざわざサフィックスを付けなくても判るだろうということで省略しているのではないかと思います。

またどっちのアセンブリソースのほうが良い?というか正確なものなのでしょうか?

私はIntel記法でしか勉強したことがないので、AT&T記法の作法についてはよく判りません。サフィックスを付けたり付けなかったりでは統一感がないので、自分で書く際は付けた方が良いような気はします。

というか、当たり前ですが、インテルが提供しているCPUマニュアルはすべてIntel記法で書かれていますし、市販の解説書もほぼIntel記法ですので**「そもそもAT&T記法ではなくIntel記法の方が良い」**というのが私の見解です。

gcc(g++)では、-Sオプションに加えて-masm=intelオプションを付けるとアセンブリソースがIntel記法になります。
gdbではset disassembly-flavor intelコマンドで逆アセンブルソースがIntel記法になります。

投稿2018/03/08 01:22

catsforepaw

総合スコア5938

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

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

0

正しさってなんでしょうね……

とりあえずintel資料によるとOpcode C7の命令は
MOV r/m16, imm16
MOV r/m32, imm32
のどちらかです。

よってmovの方が正しいような気もしますが
gnu assemblerに対する正しい入力としてはmovlなのでしょう。

機械語とアセンブリ言語が別な以上アセンブラ次第で、どちらが正しい命令かは変化します。

ちなみに、機械語とアセンブリ言語が別な例として0x90が挙げられます。
機械語としてはxchg eax, eaxです。アセンブリ言語としてはnopです。

投稿2018/03/07 17:12

asm

総合スコア15147

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

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

0

扱う値は同じなのにmovlの部分が逆アセンブリではmovになっていたりしますが、これはどちらのアセンブリプログラムが正しいというかちゃんとコードとして良いものなのでしょうか?

x86のアセンブラには、メジャーな書き方としてIntel記法とAT&T記法があります。

  • Intel記法…命令はMOVとかPUSHとか幅指定なしで、レジスタの幅で表現する(どうしようもないときはDWORD PTRのようなものを入れる)
  • AT&T記法…movlpushwのように、命令に幅指定を入れる

「どちらが正しい」というものではなく、共存しています。

投稿2018/03/07 15:57

maisumakun

総合スコア145183

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

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

fuzzball

2018/03/08 00:56

どちらもAT&T記法ではないですか?
maisumakun

2018/03/08 01:27

たしかに、オペランドの順序はAT&Tですね…こんな折衷的な書き方は初めて見たので、ちょっと頓珍漢になってしまったかもしれませんね。
fuzzball

2018/03/08 01:29

レジスタ名に%付いてますし。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問