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

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

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

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

C

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

C++

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

Q&A

解決済

2回答

3580閲覧

アセンブリ言語の基本

strike1217

総合スコア651

アセンブリ言語

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

C

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

C++

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

0グッド

3クリップ

投稿2017/07/04 08:53

編集2017/07/04 09:10

疑問に思ったところがあります。

前回の質問とちょっと前に質問したものなんですが・・・
アセンブリのコンパイルができません。
スタック領域の順番が同じになってしまいます。

**スタックの領域に入る変数の順序は決まってはいない。**ということのですね。

レジスタに値を入れる場合、
・即値をレジスタに直接代入する。
・スタック領域のポインタをレジスタに入れる。

の2通りでしょうか??
下の方の場合なんですが・・・
1)、スタック領域につまれる順序が決まっていないなら、レジスタに代入したいポインタを探すのが困難ですよね?
(つまり・・・スタック → レジスタに入れるとき、スタックの場所がわからないということです。)
順番を定めた方がよいのではないかと思います。
(配列や構造体は除く)
(前回の質問では、スタック領域に入っている値がどのアドレスに入ってるかがまるで分かりませんでした。)

2)、前回の質問でも、出てきたのですが、システムコールを呼ぶ時のパラメータをいれるレジスタは必ず固定なのでしょうか??
「どの値をどのレジスタに代入しなくてはならない」というのは決まっているのでしょうか?

raxやeaxはそれぞれ、64bit, 32bitですが、コンパイラが出力するレジスタは混合して出力されます。
raxとeaxの使い分けはどのようにすれば良いのでしょうか?

3)、前回の質問を見てもらえば分かるのですが、
mov 8(%rsp), %rbx
mov 12(%rsp), %rcx
mov 16(%rsp), %rdx

スタックポインタを4倍しているんですか?毎回4の倍数が指定されているのはなぜでしょうか??
char = 1B
int = 4B
なので、変数が違えば、8,12,16などの値も変わるんですか?

4)、前回の質問のアセンブリを載せます。文字列が見当たりません。文字列のポインタを代入しているような命令も見当たらないのですが・・・

0x555555554690 <main> push %rbp │ │0x555555554691 <main+1> mov %rsp,%rbp │ B+ │0x555555554694 <main+4> mov $0xe,%edx │ │0x555555554699 <main+9> lea 0xcc(%rip),%rsi # 0x55555555476c │ >│0x5555555546a0 <main+16> mov $0x1,%edi │ │0x5555555546a5 <main+21> mov $0x0,%eax │ │0x5555555546aa <main+26> callq 0x555555554732 <_write> │ │0x5555555546af <main+31> mov $0x0,%eax │ │0x5555555546b4 <main+36> pop %rbp │ │0x5555555546b5 <main+37> retq x555555554732 <_write> push %rbp │ │0x555555554733 <_write+1> mov $0x1,%rax │ │0x55555555473a <_write+8> syscall │ │0x55555555473c <_write+10> pop %rbp │ │0x55555555473d <_write+11> retq

lea命令で、アドレスのロードが行われているっぽいですが・・・スタックポインタではないんですかね???

アセンブリ言語初心者です。
お願いします。

Linux 64bit, intel CPU, GCCコンパイラです。

[追記]
毎回、C言語のファイルを逆アセンブルすると、出てくるんですが、
上記のアセンブリにも出てきていますね。

call命令の前後なのですが、mov $0x0,%eax・・・これですね。
EAXレジスタを0にしているようですが、なぜこんなことをする必要性があるのでしょうか??

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

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

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

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

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

guest

回答2

0

ベストアンサー

とりあえず一つ。

mov $0x0,%eaxは戻り値でしょう。return 0;です。
「戻り値をeaxレジスタに入れて返す」という決まりごとです。


も一つ。
lea 0xcc(%rip),%rsiは、mov $0x55555555476c,%rsiと書きたいけど(たぶん)書けないのでleaを使っているのだと思います。
rip = 0x5555555546a0なので、0x5555555546a0 + 0xcc = 0x55555555476c です。

投稿2017/07/04 09:13

編集2017/07/04 10:02
fuzzball

総合スコア16731

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

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

strike1217

2017/07/04 09:15

ああ、戻り値ですね!! なるほど!! では、callq命令の前のmov $0x0、%eaxはなんでしょうか?
fuzzball

2017/07/04 09:33

引数にしろ、戻り値にしろ、こういう決まりごとを「呼出規約」と言います。 一度調べてみて下さい。もしかしたら疑問が一気に氷解するかも知れません。
strike1217

2017/07/04 09:54

「呼び出し規約」というんですね!! わかりました。
strike1217

2017/07/04 10:04

rip = 0x5555555546a0とは固定の値なのでしょうか??
fuzzball

2017/07/04 10:16

脊髄反射で質問するのやめません?
strike1217

2017/07/04 10:21

すいません。気をつけます。
rubato6809

2017/07/05 15:36

> 「呼出規約」...疑問が一気に氷解するかも 私もそう思う。次のページが役に立つのでは。 [GDB] Linux X86-64 の呼出規約(calling Convention)を Gdb で確認する http://th0x4c.github.io/blog/2013/04/10/gdb-calling-convention/ このページは、プログラム(機械語)の処理が進む都度、CPUの各レジスタの値とメモリ(スタック)の絵を、繰り返し描いて変化を追っている。 自分でアセンブリコードを書こうというなら、まずはコンパイラが生成した・ちゃんと動作する機械語を見て、一命令ごとにCPUレジスタとメモリの、どこがどう変化するのか、自分の手で絵を描いて、何をしてるか地道にトレースして、動作を把握すること。話はそれからじゃないかな。 それと、strike君が問題にしてる、この奇妙なコードは、どこから出てきたのだろうか? mov 8(%rsp), %rbx mov 12(%rsp), %rcx mov 16(%rsp), %rdx 私が想像するに、32bit用Cコンパイラが生成したコードを元に、strike君が(わけもわからずに?)「64ビット風」に書き変えたものではないだろうか。しかし、32ビットと64ビットでは呼び出し規約が違う。木に竹をつないだものが動かないのは当たり前。
rubato6809

2017/07/06 00:05 編集

lea 0xcc(%rip),%rsi と mov $0x55555555476c,%rsi の違いは、位置独立コード(PIC又はPIE)になるか、ならないか。 どちらも%rsi に文字列のアドレスを代入しています。 movは文字列のアドレスそのもの(0x55555555476c)が機械語に含まれるので、文字列の位置が1バイトでもずれたら、正しく動作できません。プログラムをロードできるアドレスは、リンクした時点で一箇所に固定されてしまう。 lea 命令は Load Effective Address の略で、実効アドレスを取得する、という意味です。上の命令は %rip (命令ポインタ、いわゆるプログラム・カウンタ)に定数(=0xCC)を足して、文字列のアドレスを取得します。この場合、文字列のアドレスが実効アドレスであり、機械語に含まれるのは距離 0xCC です。機械語をロードするアドレスを変えても、この命令と文字列の「距離」は変化しないのだから、何番地にロードしても動作可能です。そこが有利なのです。
strike1217

2017/07/06 02:02

前回の質問のコードは、本を参考にして作りあげたものです。 無理やり64bitに拡張しました。 mov 16(%rsp), %rdx ここがおかしいようでした。
strike1217

2017/07/06 02:09

「lea 0xcc(%rip),%rsi と mov $0x55555555476c,%rsi の違いは、位置独立コード(PIC又はPIE)になるか、ならないか。 どちらも%rsi に文字列のアドレスを代入しています。」 あ!なるほど! 位置独立コードになるか、ならないか!納得いたしました。
strike1217

2017/07/06 10:28

さらに細かい部分については再度質問します。 ありがとうございました。 ベストアンサーにさせてもらいますね。
guest

0

実験してみたところ・・・

call命令直前の、mov $0x0,%eax はなくても良いようです。
削除しても正常に動作しました。

おそらく、システムコール番号を代入するのにアキュミュレータが使用されるようなので、あらかじめ0にしていた方が安全なのだと思われます。
(コンパイラがcall後に何がくるのか分からないので、システムコール番号に変な値が入らないように保証するための機能だと思われます。自動的に入るようです。)

間違っていたらすいません。


追記

1、2番が一番難しいんですが・・・

3番については、4の倍数になっているのは、32bitだからですね。
浮動少数点数の演算やポインタは、8の倍数に変化するようです。

int型は32bitなので、そのまま代入できますが、char型の場合は1Bなので、1つのスタックフレームに4つ入るそうです。
必ずしも4の倍数と決まっているわけではないようです。

ただ、符号の問題がでてきました。
movl 16(%rbp), %edx
movl -16(%rbp), %edx

この2つって全く意味は同じなんですかね??
どうも私の持っている本の中では全く同じなようなんですが・・・
上は足していますが、下は引いていますよね・・・

わけがわからない・・・・

________
追記2

アセンブラに手を出してみる
ここによりますと、関数の呼び出し、システムコールに使用されるレジスタは決まっているようですね。

The number of the syscall has to be passed in register rax. rdi - used to pass 1st argument to functions rsi - used to pass 2nd argument to functions rdx - used to pass 3rd argument to functions rcx - used to pass 4th argument to functions r8 - used to pass 5th argument to functions r9 - used to pass 6th argument to functions A system-call is done via the syscall instruction. The kernel destroys registers rcx and r11.

なるほど!!
rax や eaxの使い分けについては未だ不明のままです。><

投稿2017/07/04 10:50

編集2017/07/05 14:54
strike1217

総合スコア651

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

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

fuzzball

2017/07/05 00:44

_write()のプロトタイプ宣言を書くと消えませんか?(理由は分からないので聞かないで下さいw)
strike1217

2017/07/05 02:03

おお!! 確かにプロタイプ宣言をすると消えますね。 _write(int, char*, int); とやると消えますね! ん~~。ますます謎が増えてしまった・・・
strike1217

2017/07/05 02:44 編集

float型と掛け算した値をprintfで出力するプログラムを作ったのですが・・・ 今度は、call printfの直前に EAXに0ではなく、2を入れていました・・・・ 2??規則性がまるで見えないですね・・・・ ちなみにこの行も削除しても正常に動作しました。
fuzzball

2017/07/05 16:04

>>関数の呼び出し、システムコールに使用されるレジスタは決まっているようですね。 前の質問で私が回答に書きましたが。 2つもリンク貼ったのに‥。
rubato6809

2017/07/06 00:15 編集

movl 16(%rbp), %edx movl -16(%rbp), %edx > この2つって全く意味は同じなんですかね?? さっさと実験してみれば良いじゃないか。どちらでも同じバイナリ(機械語)になるなら、或いは、どちらでも同じ動作をするなら、同じ意味。 ・・・普通、引くと足すじゃ、違う結果になるでしょ笑。 スタック領域のメモリを、絵に描いて動作の流れをつかむ事。Cの変数が、アセンブリ・コードで、どこに割り当てられているか、それを確かめなさい。
rubato6809

2017/07/06 00:16 編集

> rax や eaxの使い分け 簡単な事。その後に動作するところで、raxの64ビット全てに意味があるならraxにするし、32ビットの情報だけに意味があるなら eax で済む。16ビットだけに意味があるなら ax、8ビットだけなら al を使うこともあるだろう。
rubato6809

2017/07/06 00:01

> おそらく、システムコール番号を代入するのにアキュミュレータが使用されるようなので、あらかじめ0にしていた方が安全なのだと思われます 具体的な理由は私もわからないが、おそらく違う。 コンパイラ内部の様々な事情から、不要な(不要に見える)命令が生成される事はいくらでもある。はっきり理解できてないくせに、わかったつもりにならないこと。脊髄反射と同じように、早とちりは君の悪いクセ。最初からすべてわかろうとしてはいけない。 > EAXに0ではなく、2を入れていました・・・・2??規則性がまるで見えない 君のレベルだと、そういうところが気になるんだろうが、今注目すべき点ではないと思う。今は枝葉末節。
strike1217

2017/07/06 01:59

rax や eaxの使い分けについては・・・ raxのみを使用していても問題ないんですよね? eaxやaxを使用している理由は、レジスタの節約のためと考えれば良いのでしょうか?
strike1217

2017/07/06 07:08 編集

mov 8(%rsp), %rbx mov 12(%rsp), %rcx mov 16(%rsp), %rdx 前回の質問のレジスタがおかしいですよね? rdi - used to pass 1st argument to functions rsi - used to pass 2nd argument to functions rdx - used to pass 3rd argument to functions なのに、どうして最初の2つは正常に動作しているんだろ・・・・・ 確かに、奇妙ですね・・・・これ 結局・・・システムコール用のレジスタが決まっているのか決まっていないのかよく分からないですね。
rubato6809

2017/07/06 09:34 編集

> なのに、どうして最初の2つは正常に動作しているんだろ え?まだ、わからないの?自分で情報の整理してますか。 _write(1, "Hello_World\n!!", 14); から syscall命令までをつなぐ、重要な情報は 1. Linux X86-64 の呼出規約(calling Convention) 2. LINUX SYSTEM CALL TABLE FOR X86 64 この2つの情報を、君は既に見ています。 そして、「アセンブリのコンパイルができません」で、mattnさんから動作するコードを示してもらっている。即ち、動作するコード(main.c と write.S)の組み合わせがあり(動作してるんでしょ?)、動作する機械語コードを gdb で逆アセンブルして見ることもできる。 つまり、全ての情報が君の手元にある。さほど奇妙じゃないってわかるはずだけどなあ。。。それとも、アセンブリコードの中に、わからない命令がありますか?
strike1217

2017/07/06 09:37 編集

あ、いえ。自分の分からない箇所は、 システムコール用に決められているレジスタを使用しなくても正常に動作してしまっている理由です。 システムコール用のレジスタが決まっているのか決まっていないのかよく分からなくなっている原因です。 質問中の2番に相当します。
rubato6809

2017/07/06 09:49

> システムコール用のレジスタが決まっているのか決まっていないのかよく分からなくなっている原因です。 苦笑。何度、決まっている、と言われたら、納得するのかな? システムコール用に決められているレジスタと、呼出規約に使われるレジスタを並べて 見ましたか? 呼出規約とは、関数呼出の実引数が、どのレジスタ(又はスタック)に割り当てられるか、という決まりですよ。_write(1, "Hello_World\n!!", 14); の、3つの実引数が、どのレジスタに代入されたか、確認しましたか?
strike1217

2017/07/06 09:51

コンピュータアーキテクトの立場からすると、システムコールや関数へのパラメータを渡す時のレジスタを決めて置かないと、どのレジスタからパラメータを受け取るように設計すればよいのかまるでわかりません。 なので、システムコール用のレジスタが決まっているというのは、納得なのですが・・・・ だとしたら、前回の質問のプログラムは正常に動作しないはずです。 にも関わらず、正常に動作しているということは、システムコール用のレジスタを使用しなくても良いとううことですよね?
strike1217

2017/07/06 09:56 編集

!! あ、そういうことか!! main()の中で、システムコール用のレジスタに値が代入されているから正常に動作するのか!!! _write()によって渡されたパラメータはシステムコール用のレジスタを採用していないので、システムコール呼び出しの際には、無視されるということですね! やっとわかりました。 _write()のスタックを指す記述がおかしかったので、システムコール用のレジスタに変更しても正常に動作しないんですね! やっと、ここで1番目の質問が登場するわけですね。ふぅ
rubato6809

2017/07/06 09:57

確認させてください。 ・main.c と write.s の2つをコンパイルして、期待通りの動作してますか? ・(動作してるなら)その main と _write を、逆アセンブルして見ましたか?
strike1217

2017/07/06 10:02

最初は期待通りに動作していませんでした。 しかし、回答をいただいて修正したら、正常に動作しました。 今、わかりました。修正前の_write()のパラメータ設定には、意味が無かったことに! 逆アセンブリはしました。 GDBで、_wirte()の方はアセンブリ言語で記述されているでそのまま、main()のほうも逆アセンブリしたヤツを質問中に貼り付けてあります。
rubato6809

2017/07/06 10:02

「_write()によって渡されたパラメータはシステムコール用のレジスタを採用していないので、システムコール呼び出しの際には、無視されるということですね! 。。。 _write()のスタックを指す記述がおかしかったので、システムコール用のレジスタに変更しても正常に動作しないんですね!」 君の日本語はアヤシイ。何を言ってるのか、意味不明なところがある。本当に理解できたのだろうか。。。。
strike1217

2017/07/06 10:06

大変分かりにくくて、すいません。 mov 8(%rsp), %rbx mov 12(%rsp), %rcx mov 16(%rsp), %rdx これらは、システムコールが呼び出される直前で設定しています。 ですが、システムコール用のレジスタが使われているのは、最後の1つだけです。 本来なら、これは正常に動作しないはずです。
strike1217

2017/07/06 10:14

ようやく納得いきました。 さらに細かい部分の質問は再度行います。 (1)はまだ未解決ですが・・・
rubato6809

2017/07/06 12:51

mov 8(%rsp), %rbx mov 12(%rsp), %rcx は、システムコールに関係無いレジスタなので「意味が無かった」。 mov 16(%rsp), %rdx は、せっかくmain()が文字数を%rdxにセットしてくれていたのに、壊していたのですね。 結論を言えば、_write(1, "Hello_World\n!!", 14); と呼び出してしまえば、あと SYS_WRITE(=1) システムコール番号を %eax にセットして syscall 命令すれば済む問題だった、というわけです。なので、アセンブリ語の入門にはなるけど、面白みには欠けますが、そうできるように、コーリングコンベションも、システムコールのインターフェイスも、決めたんでしょうね、なぜ%rcxを%r10にするのかは知らんけど。 ポインタや構造体でつまずく人は多いですが、アセンブリ言語の経験があれば、だいぶ違うはずです。アセンブリ言語をやれば、漠然とわかったつもりでいたことが、具体的にこういうことだったんだと気づくことがいろいろあると思いますよ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問