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

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

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

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

Q&A

解決済

2回答

1138閲覧

アセンブリ言語の基本part3

strike1217

総合スコア651

アセンブリ言語

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

0グッド

1クリップ

投稿2017/07/14 05:48

編集2017/07/14 09:56

細々とした質問なので、複数質問することをお許しください。

アセンブリ言語で読めない箇所があります。
以前の質問で、以下のような例を示してもらいました(ありがとうございます。)

.section .rodata xx: .quad subr .text subr: inc %rax # ++rax ret .global aFunc aFunc: mov $0, %rax call subr call (subr) call *(xx) # 1 call *xx # 2 mov $subr, %rdx # 3 call *%rdx # lea subr(%rip), %rdx call *%rdx lea xx(%rip), %rdx call *(%rdx) ret

ですが、私の環境ではうまく行きませんでした。
1、2、3番と出来なかった所には番号が振ってあります。
(私にとってはこの3つができない方が納得いきます。)

1、*call subrとできない。
subrは絶対アドレスです。 なぜでしょうか?
1番,2番が仮にも出来る環境なら、call *subr これはできないとおかしいのではないでしょうか?

2,call subr 及び call (subr)
の結果が変わらないのは、ラベルに対して括弧を付けても意味が無いということですよね?
(付けても付けなくてもおk)

3,$subr は$は即値に付ける構文ですよね?
ラベルにも付けれるんですか?
(私の環境ではできませんでした。)

4,以上を見ていただければ、わかりますが・・・
アスタリスクは、絶対アドレスが入っているレジスタに対する構文ではないでしょうか?
ラベルに[*]を付ける方が納得がいかないんですが・・・・
(レジスタ以外に対するアスタリスクはすべてダメでした。)

5,もう1つ気になる事があります。

movq -8(%rbp), %rax movabsq $7812735413947559801, %rdx movq %rdx, (%rax) movabsq $7308533390257515808, %rcx movq %rcx, 8(%rax)

calloc関数を使って、ヒープを確保しその領域にstrcpy()で文字列を代入します。
しかしこれはオーバーフローではないでしょうか??

代入している即値が「8Byte」を完全に超えています。
64bitのレジスタに対してはオーバーフローです。
しかし、正常に動作しています・・・なぜでしょうか?

6, mov $1, %rax
最後に、qやl と言った文字をニーモニックにつけますが、
これは即値によって決めるものですか?それともレジスタの容量によって決めるものですか?
あ、言い方が悪かったです。ソースとデステネイションのどちらによって決めるものですか?

7,lea xx(%rip), %rdx | call *(%rdx)
これを1つにして、call *xx(%rip) と記述できます。(正常に実行できます。)

これも変です。
xx(%rip) の中身はアドレスです。
そのアドレスが指す領域内のアドレスが関数のポインタです。

なら、イメージ的には、call *(xx(&rip))こんな感じになります。
実際には文法エラーですが、call *xx(%rip)の記述はおかしいような気がするんですが・・・
これでは、%rip + xx のアドレスを絶対アドレスとしてcallしてしまっているように見えるのですが・・・・
わざわざ、オフセット付きの括弧となしの括弧の使い方を区別しているのに、これでは意味が無くなってしまいます。

[環境]
64bit Linux Debian系 intel CPU です。

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

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

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

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

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

mugicya

2017/07/14 07:12

前から思ってるんですが、デバッガとかないのですか?それでレジスタの値や挙動を追えば、すぐ判明しそうな気がするのですが。
strike1217

2017/07/14 07:14

いえ、リンカの段階でエラーなので、デバッガで追えません。実行ファイルが生成されないので!
strike1217

2017/07/14 07:22

もちろん、objdumpとかも使用していますよ。subrのラベルが絶対アドレスになったり、オフセットに変化したりするのもかなり違和感を感じますが・・・
mugicya

2017/07/14 09:54

これって、x86 なんですか?CPUがよくわからないので、レジスタの特性もわからないまま見てるんですが。ラベルがオフセットになったり、アブソリュートになったりするのは、オフセットで届く場合はオフセットで、と最適化されているだけな気がしますが。
strike1217

2017/07/14 09:55

環境を追記しますね。
rubato6809

2017/07/14 10:58

私はUbuntu 16.04で動作確認しましたし、Linux Mint(バージョンは?)で動作したんでしょ。動く環境があることを告知しないとダメでしょ。
strike1217

2017/07/14 11:26

あ、すいません。Linux mintでは動きますね。Linux mintの方はGDBの動作が変なのでデバッグしていません。Linux mintの方とrubato6809さんの方では出来ているようなので、古いバージョンだとできるのかな?・・・予測です。
strike1217

2017/07/14 11:36

自分が理解できないのは文法の方ですね。
guest

回答2

0

ベストアンサー

3,$subr は$は即値に付ける構文ですよね?

Yes.

ラベルにも付けれるんですか?

mov $subr, %rdxは、シンボル"subr"の値を%rdxにロードするのです。できて当たり前でしょう。念の為に言うと、シンボル値が決定するのはリンク時である。
おまけ:ら抜き言葉は止めよう。軽薄に聴こえるw
×付けれる
○付けられる

私の環境ではできませんでした

ひとつ見てみたいのは、君の手元で、アセンブルリストを出して、mov $subr, %rdxの部分がどうなってるか、(エラーにならない、と言うなら)何バイト命令としてアセンブルされるか。
もうひとつは、movq $subr, %rdxとしたら、何か変化があるか。。。

  1. 代入している即値が「8Byte」を完全に超えています

って
movabsq $7812735413947559801, %rdx
movabsq $7308533390257515808, %rcx
のことであれば、strike君の名誉のために、さっさと質問を取り下げるのが
良いと思う。

6, mov $1, %rax
最後に、qやl と言った文字をニーモニックにつけますが、
これは即値によって決めるものですか?それともレジスタの容量によって決めるものですか?

0x01も、0x0001も、0x00000001も、0x00000000000000001 も、値は全て1ですが、デスティネーションとして %rax が指定されてるのだから、その1は64ビットの1である(LSBitのみ1で、それ以外の63bitは0である)と判断できます。
一般的な言い方をすれば「デスティネーションとして指定されたレジスタのビット数(「容量」でも構わないよ、普通そういう言い方しないと思うが)によって決まる」と、容易に想像がつく話だけどねえ。

投稿2017/07/14 12:58

編集2017/07/15 08:49
rubato6809

総合スコア1380

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

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

strike1217

2017/07/14 14:51

いえ、私初心者ですので名誉はありません。 純粋に分からないことを聞いてきます。 ・・・・・ あ、16進数じゃないのか!!
strike1217

2017/07/14 14:55 編集

0x6c6c697720756f79でちょうど8Btyeでした。すいません。変な質問して申し訳ない。 以後、気を付けます。
rubato6809

2017/07/14 22:22

その0x6c6c697720756f79という値の「正体」wは、お分かりですか?
rubato6809

2017/07/14 23:16 編集

Mintでgdbが使えないか。ではDebianのgdbで機械語の動作を確かめるための、荒業を思いついたよ。 as -a acode.s で、どんな機械語になるかはわかるだろう。それを .byte (かな?)で機械語を16進数直打ちする。アドレスとかオフセットは、自分で計算する(笑)。どう、できそう? > 7,lea xx(%rip), %rdx | call *(%rdx) > これを1つにして、call *xx(%rip) と記述できます。(正常に実行できます。) このリサーチは、good jobだ。私は褒めるよ。 考察内容は???だけどねw
strike1217

2017/07/15 01:11

「その0x6c6c697720756f79という値の「正体」wは、お分かりですか?」 それは大丈夫ですよ。
strike1217

2017/07/15 02:10

「as -a acode.s で、どんな機械語になるかはわかるだろう。それを .byte (かな?)で機械語を16進数直打ちする。」 アセンブルされた2進数の機械語を・・・どうするんです?? 2進数を16進数に変換するんです?
rubato6809

2017/07/15 08:22 編集

まず、今の a.outに対して、次のコマンドを叩く。 $ objdump -t a.out | grep xx 000000000040063a l .rodata 0000000000000000 xx と表示される(私の手元では)。この 0040063a という値は、リンクされて、決定した、"xx"シンボルの値である。 この例だと32bitの値に収まっているが、もし君の手元で32bitに収まらない値になっていたなら簡単じゃあない(もしかすると、それがリンクエラーの理由かも?と、ふと思った)。 シンボル"xx"が32bitに収まっているとして、"call *(xx)" がアセンブルされると、どういう機械語になるか、確認しよう。 $ as -a acode.s ... 12 0011 FF142500 call *(xx) # 12 000000 ... ここに表示された 0xFF, 0x14, 0x25, 0x00, 0x00, 0x00, 0x00 という7バイトが目的の機械語であるが、4バイトの 0x00 はリンクした時点でシンボル"xx"の値がセットされる予定なので、今はオール0が表示されるのである。 そこで、acode.s を開き、次のように編集する ... # call *(xx) この行をコメントアウトし、次の一行を追加する .byte 0xFF, 0x14, 0x25, 0x3a, 0x06, 0x40, 0x00 ... と、直打ちするのだ。そして、これをコンパイル・リンクする。これならエラーにならんだろう。 念の為、もう一度 objdump して xx の値が変化してない事を確認すると安心。 $ objdump -t a.out | grep xx ここまでくれば動作すると思うが、君の手元ではいかが?動作すればgdbでトレースも可能なはず。good luck !
strike1217

2017/07/15 07:51

「デスティネーションとして指定されたレジスタのビット数によって決まる」 デスティネーションによって決まるのですね!
strike1217

2017/07/15 08:18

回答の方を先に考察します。 シンボルの場合はシンボルテーブルに追加されますが・・・ラベルは・・・どうでしょう。 シンボルとラベルは区別されたほうが良いのか、全く同じものなのか・・・ mov $subr, %rdxがどうなっているか今調べますね。
rubato6809

2017/07/15 08:24

気づいたと思うがアセンブルリストを出すオプションは -v じゃなく、-a である。すまない、↑直した。
strike1217

2017/07/15 09:46 編集

mov $subr, %rdx objdumpの結果は、 mov $0x0, %rdx callq *%rdx この場合は、デスティネーションがrdxなのに、ニーモニックにqは付かないんです?
rubato6809

2017/07/15 08:27

> シンボルとラベルは区別されたほうが良いのか、全く同じものなのか さあ、最終的にはどちらも同じものなんじゃないの?程度の認識しかない。
strike1217

2017/07/15 08:27 編集

これは・・・リンカのアドレス解決の対象になっていないのでは?? ラベルではなくシンボルだったら、できるかもしれないですね。
rubato6809

2017/07/15 08:29

> objdumpの結果は、 空白???やってもらうのは次だよ。 objdump -t a.out | grep xx
strike1217

2017/07/15 08:32

あ、すいません。 回答に書いてある方を先にやりました。 「ひとつ見てみたいのは、君の手元で、アセンブルリストを出して、mov $subr, %rdxの部分がどうなってるか」 objdumpで逆アセンブルしたものを載せたのです。
rubato6809

2017/07/15 08:33

> mov $0x0, %rdx > callq *%rdx > この場合は、デスティネーションがraxなのに、ニーモニックにqは付かないんです? 脊髄反射w
strike1217

2017/07/15 08:34

objdump -t a.out | grep xx は、できる方の環境で「やれ!」ということですよね。
rubato6809

2017/07/15 08:37

> objdumpで逆アセンブルしたものを載せたのです 何を?どこに?空白行が「逆アセンブルしたもの」???? 君が何をやっているか、何を考えたか、こちらにはわからん。少しは相手の立場になって、どんな情報を出せば伝わるか、考えろ!!! 言い訳は要らない。問題解決が前進する情報を示せ!
strike1217

2017/07/15 08:39

mov $0x0, %rdx callq *%rdx これなんですが・・・
strike1217

2017/07/15 08:54

できない方の環境で、エラーが出る行を消して、a.outを生成。 デバッグ情報を付与して、objdump -t a.out | grep xxをやりましたが、なにも出てきません。 念のため、readelf -x 16 a.out (rodataのダンプ)もやってみました。 「subr() has been called %d times...」 とmain()の方の文字列が出現するだけですね。
strike1217

2017/07/15 08:58 編集

やはり、ラベルはアセンブルの段階でアドレスに変換されます。 シンボルはリンカが解決するものです。 なので、ダンプ後に情報が載らないのは当然なのではないでしょうか?
strike1217

2017/07/15 09:03

objdump -d で載っていた xx のアドレスは、7a4となっていました。 これは、.rodataのアドレス領域に相当していました。 その領域の値は、01000200 c0060000 となっていました。 これが、xxの値ということに・・・なるような・・・
strike1217

2017/07/15 09:08 編集

あ、違う。 xxの値ではなく、リトルエンディアンから変換すると、/xc0/x60なので、0x6c0となります。 これは、subrの絶対アドレスと一致しています。
strike1217

2017/07/15 09:21

nmコマンドでシンボルテーブルを調べたら、xxラベルが登録されていますね。 xxラベルもシンボル扱いということですね。
strike1217

2017/07/15 09:31

movq $0x7a4, %rdx call *(%rdx) と即値としてxxのアドレスを代入しましたが、segmentation faultです。
strike1217

2017/07/15 10:13

再度ファイルを作り直し、コンパイルしてやり直しました。 objdump -t a.out | grep xx 000000000000744 l .rodata 00000000000000 xx となってました。
strike1217

2017/07/15 10:23

「と、直打ちするのだ。そして、これをコンパイル・リンクする。これならエラーにならんだろう。」 なるほど!なるほど!! ・・・結果はsegmentation faultでした。 GDBで調べてみましたが、744というアドレスと実際のアドレスは異なっていました。 0x555555554744というのが実際のアドレスのようです。
strike1217

2017/07/15 10:27

実際のアドレスが32bitを超えてしまっているようですね。 .byte 0xFF, 0x14, 0x25, 0x00, 0x00, 0x00, 0x00 ・・・・入らない??
strike1217

2017/07/15 10:45 編集

callq *0x555555554744 では、アセンブルがエラーを吐き出します。 call命令で指定可能なバイト数が決められている・・・ということになりますね。 32bitまでですね。
strike1217

2017/07/15 10:47

call命令に32bit以上のアドレスはどうやって指定すれば良いのでしょうか?
strike1217

2017/07/15 11:02

32bit以下にすれば良いなら、静的リンカはどうだろ・・・と思い ダイナミックリンカではなく静的リンカを使用したら、 call *(xx) # 1 call *xx # 2 できました!!
strike1217

2017/07/15 11:23

ダイナミックリンカが再配置を行えない理由がわかりませんが、 おそらく論理アドレスの低い領域にロードするようにすればできるかもしれません。 それは、また後ほど!! ・・・はぁ。 それで・・・次は文法の方ですね。
strike1217

2017/07/15 11:29

> mov $0x0, %rdx > callq *%rdx > この場合は、デスティネーションがrdxなのに、ニーモニックにqは付かないんです? 脊髄反射w ・・・・ ちょっと本当にわからないです。 なんでqが付かないんです?
rubato6809

2017/07/20 00:26

落ち穂拾いw qの有無で何が違うか、まだわからないなら call/callq、mov/movqを実際にアセンブルして、アセンブルリストかobjdumpで逆アセンブルするか、どんな機械語になるか、確認してみると良いよ。たった4行じゃないか、自分の手を動かすことが大事。
guest

0

Linux mint でcall *subrとやってみましたが、segmentation faultです。
もぉ~~。わけわからん。

全てのパターンを暗記するしかないのかな?
まとめてみました。

|オスセットなし括弧|オフセットあり括弧|括弧なし+[]|オフセットなし括弧+[]|オフセットあり括弧+[*]|
|:--:|:--:|:--:|:--:|
|(%rax)|-8(%rbp)|call *%rax|call *(%rax)|call *xx(%rip)|
|ポインタの要素 | アドレス+オフセット |ポインタ| ポインタのポインタ | ポインタのポインタ

5番目は、*相対アドレッシングなのに、を付けています。
絶対アドレスに付けるものじゃないの?と思いますが・・・おそらくポインタが指すメモリ領域のアドレスが絶対アドレスだからという屁理屈ですかね?(たぶん)

call *xx(%rip) はポインタかと思っていました。
なぜなら、xx(%rip)は、XXを加算したアドレスだからです。
組み合わせによって意味が全然違う・・・・

$や*はレジスタ以外にも使用可能な構文。
()は、レジスタに対する構文で、ラベルには付けても付けなくてもおk・・・って感じですかね。
call *subrができないという事はおそらく・・・

label(label)$label*labelcall *$label
ポインタlabelと変わらないラベルを即値とする(?)ポインタのポインタX (*なしでも無理)

またラベルはオフセットにも絶対アドレスにも使用可能!

という事ですかね?(間違っていたらご指摘をお願いします。)

「私の環境では、$や*はレジスタに対して使用可能な構文」となっていました。
こちらの方が納得ですが・・・
私の環境(バージョンが新しい)

label(label)$label*label
ポインタlabelと変わらないXX

投稿2017/07/15 01:29

編集2017/07/16 14:36
strike1217

総合スコア651

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

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

mugicya

2017/07/15 06:32

二モニックのアドレッシングモードは全て暗記…というか、一定の法則があると思う
strike1217

2017/07/15 06:34

法則が見えないので、困っているんです。 例えば、組み合わせによって[*]の意味が全然違うものに変貌しているような・・・ 法則を教えてほしいのです。
strike1217

2017/07/15 11:34

-8(%rbp)も%rbpに括弧がついているので、%rbpのポインタの要素に-8しているなら納得なのですが・・・ オフセットが付いただけで、括弧の本来の意味が無くなる・・・んん〜〜。 C言語に例えると・・・ *p++ が、++が付いているから[*]の意味を本来のものと変えてしまっているようなイメージです。 非常にわかりにくく、混乱の原因です。
strike1217

2017/07/15 11:35

まるで法則性が見えないので、覚えるしかないかと・・・・ これ以外のパターンもあるんでしょうか? 教えてください。
rubato6809

2017/07/15 15:29 編集

> *p++ が、++が付いているから[*]の意味を本来のものと変えてしまっている これ、私的には「何を勘違いしてるんだ?」な話。どう本来と違うのか、逆に聞きたい位だw。例えば a = *p++; のように使うが、単独で「*p++;」と書けることを言ってるのかな?単独で書くなら、「p++;」と書くのが普通だと思うよ。ポインタのインクリメントをしたい時に’*’をつける必要はないから。無駄な*を書くのは(君のように)誤解の元。 さて、 全体を見渡せる法則性は、個々の規則を掴んだ上で出てくる(感じ取れる)ものだ。 「”call *xx” と書けるなら "call *subr"もできるはずだ、あれ?segmentation error???」と悩む君は、形式的なシンタックスしか見ておらず、そもそも"call *xx" が、どんな動作をする命令か、(シンタックスに対してセマンティックスを)理解できていない事が明確に見て取れる。形式的文法、或いは字面で理解しようとするのは君の弱点、悪いクセ。 字面ではなく、図を描くこと。(様々な場面で)メモリとCPUレジスタの図を描き、そこに具体的な数値を入れて考察することをお勧めする。 静的リンクすることで ”call *xx” の動作も確かめられるようになったのだから、この命令の動作も図を描いて考察することだ。"call *xx" 命令に関する行だけを抜き出すと、次のようになる。これなら"call *subr"がsegmentation errorになる理由が分かるだろう? xx: .quad subr subr: inc %rax  ret myFunc:  call *xx  ret
strike1217

2017/07/15 15:33 編集

*labelはポインタのポインタでよろしいのではないでしょうか? segmentation faultになるので、ポインタのポインタだと予測しました。
strike1217

2017/07/15 16:01

自分が最初に思ったのは、C言語風に書きます。 (%rax) : *rax (要素) -8(%rax) : *rax - 8 (要素-8) なら納得です。 しかし実際は、 -8(%rax) : rax - 8 (アドレス -8)という意味です。 括弧の意味が変わってる!!?
rubato6809

2017/07/16 02:14 編集

> segmentation faultになるので、ポインタのポインタだと予測 漠然としていて、ホントの所を理解できてないように思えます。 どの時点で、どこのアドレスをアクセスしてセグメンテーションフォールトになるのか、貴方の手元のa.outの機械語コードを例示して、具体的に説明できますか? これ、なんとなく、ダメなんだろう…とかではなく、「このコードは"call *subr"命令が〜〜番地を(or ~~番地から)〜〜するので例外になる」と極めて具体的に言えるんですよ。 以前私は「"call *subr"がセグメンテーションフォールトにならなかったら、奇跡」と書きました。その意味も説明できなくてはならない。
strike1217

2017/07/16 03:18

call *subr はポインタではなく(関数のアドレスではなく)、ポインタのポインタをcallしているのでsegmentation faultになる。 という説明ではダメでしょうか?
rubato6809

2017/07/17 10:25 編集

ダメですね。及第点を与えられない。 「ポインタのポインタをコールしているから」??? それだけじゃ、"call *xx" は動作するのに、"call *subr" が動作できない説明にならない。私が「奇跡」と言ったココロについて、何の説明も無い。 君の回答だけを待っていても隔靴掻痒の感が募るので、私も gcc version 6.3.0 20170516 (Debian 6.3.0-18) を手に入れた。 "call *subr" は確実に・具体的に理解してほしいので、acode.s を簡単にした、次のコードで考えてもらいたい。 .text .align 8 subr: inc %rax ret .byte 0, 0, 0, 0 xx: .quad subr .global aFunc aFunc: xor %rax, %rax call subr # most usual usage call *xx # indirect? call call *subr # Segmentation fault ret 前回 .rodata セクションに配置した xx を、.text セクションに持ってきた。objdump で逆アセンブルすれは、一度に見られる。 ccode.c は前回と同じまま、次のようにコンパイルする(念のため:コンパイルは通るが、動作させればSegmentation faultする)。 $ cc -static ccode.c acode.s $ ./a.out Segmentation fault objdumpで逆アセンブルしてみよう。 $ objdump -d a.out | less less で受けるのは「/aFunc」とタイプして、目的の場所を表示したいから。 400ab2: e8 31 00 00 00 callq 400ae8 <aFunc> 400ab7: 89 c6 mov %eax,%esi (省略) 0000000000400ad8 <subr>: 400ad8: 48 ff c0 inc %rax 400adb: c3 retq 400adc: 00 00 add %al,(%rax) ... 0000000000400ae0 <xx>: 400ae0: d8 0a fmuls (%rdx) 400ae2: 40 00 00 add %al,(%rax) 400ae5: 00 00 add %al,(%rax) 0000000000400ae8 <aFunc>: 400ae8: 48 31 c0 xor %rax,%rax 400aeb: e8 e8 ff ff ff callq 400ad8 <subr> 400af0: ff 14 25 e0 0a 40 00 callq *0x400ae0 400af7: ff 14 25 d8 0a 40 00 callq *0x400ad8 <= Segmentation fault 400afe: c3 retq 君の手元でも同じ結果(少なくとも、大差ない結果)になるはずだ。 ・"call *xx" は動作するのに、"call *subr" が動作できない理由(=どこでsegmentation faultするのか) ・「call *ラベル」という形式の命令は、どんな動作をする命令なのか ・私が「奇跡」と言ったわけ を、この逆アセンブル表示をもとに、番地など具体的な値を用いて、説明しなさい。
strike1217

2017/07/17 10:54 編集

callq *0x400ae0 の方はラベルのアドレスが指定されています。 callq *0x400ad8 こちらは、関数のアドレスが指定さています。 call *ラベルの動作は・・・ラベルのアドレスが指しているメモリ領域のアドレスをcallするという事です。 これ以上の説明はできません。 callq *0x400ad8 これは・・・ ラベルのアドレスが指しているメモリ領域のアドレスがない・・・と言えば良いのでしょうか? * + ラベルは間接参照という事ですかね。
rubato6809

2017/07/17 13:40 編集

> これ以上の説明はできません そうか。くどいと思うだろうが、読んで・図に描いて・トレースしてみてほしい。 xx も subr も、ラベルです。どちらのラベルも、メモリアドレスが値です。 call *xx は "callq *0x400ae0" 即ち ff 14 25 e0 0a 40 00、 call *subr は "callq *0x400ad8" 即ち ff 14 25 d8 0a 40 00、 どちらも機械語には、xx と subr の、アドレス(絶対番地)である、0x400ae0と0x400ad8がエンコードされている事を確認しよう。 call *xx = "callq *0x400ae0" の場合。 0x400ae0番地から、メモリには 0xd8 0x0a 0x40 0x00 0x00 0x00 0x00 0x00 という値が並んでいる。 "callq *0x400ae0"命令によって、 1. %rip の値をスタックにプッシュする(CALL命令だから) 2. <メモリから8バイトの値を読み出す>。 (リトルエンディアンなので)読み出した値は 0x0000000000400ad8である。 3. <メモリから読み出した値 0x400ad8 を%ripに代入する>。 ここまでが、"call *xx"命令の動作。 4. CPUは、0x00400ad8番地のメモリにある、0x48 0xff ...を (次の命令として)実行しようとする。 このバイト列は inc %rax 命令であり、さらにret命令が続くので、もちろん問題なく動作する。 call *subr = "callq *0x400ad8" の場合。 0x400ad8番地から、メモリには 0x48 0xff 0xc0 0xc3 0x00 0x00 0x00 0x00 という値が並んでいる。 "call *subr"命令によって、 1. %rip の値をスタックにプッシュする(CALL命令だから)。 2. <メモリから8バイトの値を読み出す>。読み出した値は 0x00000000c3c0ff48である。 3. <メモリから読み出した値 0xc3c0ff48 を%ripに代入する>。 ここまでが"call *subr"命令の動作。 4. CPUは、0xc3c0ff48番地のメモリに命令があるものとして読み出そうとする・・・が、その番地のメモリは、このプロセスに割当られていない。よって、Segmentation faultが起こる。 なぜメモリが割り当てられていないと言えるか。Segmentation faultが起きたから(笑)。 少しだけ検証すると、objdump -d a.out が表示する機械語命令の最後は 0000000000489844 <_fini>: 489844: 48 83 ec 08 sub $0x8,%rsp 489848: 48 83 c4 08 add $0x8,%rsp 48984c: c3 retq "-static"でスタティックリンクしたのだから、ユーザステートで動作する機械語はここまでなんじゃないのかな?他に、スタックや静的データ領域(グローバル又はstaticな変数領域)も割当られているけど、0xc3c0ff48番地は桁が違い、カスりもしないアドレスのようだ・・・。 もし、仮に subr 番地から読み出した8バイトをアドレスとして見た場合、 ・このプロセスに割り当てられたメモリを指している ・OSが、そのメモリ領域を「実行可能領域」に設定してある ・そのアドレスから始まるメモリのバイト列が、retで終わる、実行可能な機械語列である という条件が成り立つなら動作するのだから、絶対に動作しないとは言い切れない。でもそれは奇跡です。
strike1217

2017/07/17 14:26 編集

関数内のプログラムがアドレスとして解釈されているんですね! 大変分かりやすいです!! アセンブリ言語の文法(括弧について)は再度質問するかもしれません(しなかもしれないです) 長々付き合っていただきありがとうございます。
rubato6809

2017/07/17 14:45 編集

> 関数内のプログラムがアドレスとして解釈されている そういうこと。 ・「call *ラベル」という形式だけで決まるのではない事。 ・ひとつひとつの機械語の動作は、極めて単純。「call *ラベル」は、ちょっと複雑な部類か。プログラマの思惑や思い込みに合わせてくれる…なんてことはなく、決められた通りに、機械語をアドレスと見ることだってやる。 不可解なバグを調査するときは、人間の思い込みを排除して、CPU(機械)になったつもりでコードを調べる必要がある。 「神は細部に宿る」・・・コードの流れを大雑把に把握することも大事だが、ひとつひとつの動作の、隅っこの細部まできちんと理解しておくことも大事である。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問