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

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

ただいまの
回答率

90.51%

  • アセンブリ言語

    114questions

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

アセンブリ言語の基本part3

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 902

strike1217

score 568

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

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

        .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 です。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • rubato6809

    2017/07/14 19:58

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

    キャンセル

  • strike1217

    2017/07/14 20:26

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

    キャンセル

  • strike1217

    2017/07/14 20:36

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

    キャンセル

回答 2

checkベストアンサー

+1

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

Yes. 

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

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

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

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

5. 代入している即値が「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 23:51

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

    ・・・・・
    あ、16進数じゃないのか!!

    キャンセル

  • 2017/07/14 23:54 編集

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

    キャンセル

  • 2017/07/15 07:22

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

    キャンセル

  • 2017/07/15 08: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

    キャンセル

  • 2017/07/15 10:11

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

    キャンセル

  • 2017/07/15 11:10

    「as -a acode.s で、どんな機械語になるかはわかるだろう。それを .byte (かな?)で機械語を16進数直打ちする。」

    アセンブルされた2進数の機械語を・・・どうするんです??
    2進数を16進数に変換するんです?

    キャンセル

  • 2017/07/15 16:51 編集

    まず、今の 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 !

    キャンセル

  • 2017/07/15 16:51

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

    デスティネーションによって決まるのですね!

    キャンセル

  • 2017/07/15 17:18

    回答の方を先に考察します。
    シンボルの場合はシンボルテーブルに追加されますが・・・ラベルは・・・どうでしょう。
    シンボルとラベルは区別されたほうが良いのか、全く同じものなのか・・・

    mov $subr, %rdxがどうなっているか今調べますね。

    キャンセル

  • 2017/07/15 17:24

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

    キャンセル

  • 2017/07/15 17:26 編集

    mov $subr, %rdx
    objdumpの結果は、

    mov $0x0, %rdx
    callq *%rdx

    この場合は、デスティネーションがrdxなのに、ニーモニックにqは付かないんです?

    キャンセル

  • 2017/07/15 17:27

    > シンボルとラベルは区別されたほうが良いのか、全く同じものなのか

    さあ、最終的にはどちらも同じものなんじゃないの?程度の認識しかない。

    キャンセル

  • 2017/07/15 17:27 編集

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

    キャンセル

  • 2017/07/15 17:29

    > objdumpの結果は、

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

    キャンセル

  • 2017/07/15 17:32

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

    objdumpで逆アセンブルしたものを載せたのです。

    キャンセル

  • 2017/07/15 17:33

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

    脊髄反射w

    キャンセル

  • 2017/07/15 17:34

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

    キャンセル

  • 2017/07/15 17:37

    > objdumpで逆アセンブルしたものを載せたのです

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

    キャンセル

  • 2017/07/15 17:39

    mov $0x0, %rdx
    callq *%rdx

    これなんですが・・・

    キャンセル

  • 2017/07/15 17:54

    できない方の環境で、エラーが出る行を消して、a.outを生成。
    デバッグ情報を付与して、objdump -t a.out | grep xxをやりましたが、なにも出てきません。

    念のため、readelf -x 16 a.out (rodataのダンプ)もやってみました。
    「subr() has been called %d times...」 とmain()の方の文字列が出現するだけですね。

    キャンセル

  • 2017/07/15 17:57 編集

    やはり、ラベルはアセンブルの段階でアドレスに変換されます。
    シンボルはリンカが解決するものです。

    なので、ダンプ後に情報が載らないのは当然なのではないでしょうか?

    キャンセル

  • 2017/07/15 18:03

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

    キャンセル

  • 2017/07/15 18:07 編集

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

    キャンセル

  • 2017/07/15 18:21

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

    キャンセル

  • 2017/07/15 18:31

    movq $0x7a4, %rdx
    call *(%rdx)

    と即値としてxxのアドレスを代入しましたが、segmentation faultです。

    キャンセル

  • 2017/07/15 19:13

    再度ファイルを作り直し、コンパイルしてやり直しました。
    objdump -t a.out | grep xx

    000000000000744 l .rodata 00000000000000 xx
    となってました。

    キャンセル

  • 2017/07/15 19:23

    「と、直打ちするのだ。そして、これをコンパイル・リンクする。これならエラーにならんだろう。」

    なるほど!なるほど!!
    ・・・結果はsegmentation faultでした。
    GDBで調べてみましたが、744というアドレスと実際のアドレスは異なっていました。
    0x555555554744というのが実際のアドレスのようです。

    キャンセル

  • 2017/07/15 19:27

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

    ・・・・入らない??

    キャンセル

  • 2017/07/15 19:44 編集

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

    キャンセル

  • 2017/07/15 19:47

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

    キャンセル

  • 2017/07/15 20:02

    32bit以下にすれば良いなら、静的リンカはどうだろ・・・と思い

    ダイナミックリンカではなく静的リンカを使用したら、
    call *(xx) # 1
    call *xx # 2

    できました!!

    キャンセル

  • 2017/07/15 20:23

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

    ・・・はぁ。
    それで・・・次は文法の方ですね。

    キャンセル

  • 2017/07/15 20:29

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

    脊髄反射w

    ・・・・
    ちょっと本当にわからないです。
    なんでqが付かないんです?

    キャンセル

  • 2017/07/20 09:26

    落ち穂拾いw

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

    キャンセル

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 *label call *$label
ポインタ labelと変わらない ラベルを即値とする(?) ポインタのポインタ  X (*なしでも無理)

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

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

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

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

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/07/15 15:32

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

    キャンセル

  • 2017/07/15 15:34

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

    法則を教えてほしいのです。

    キャンセル

  • 2017/07/15 20:34

    -8(%rbp)も%rbpに括弧がついているので、%rbpのポインタの要素に-8しているなら納得なのですが・・・
    オフセットが付いただけで、括弧の本来の意味が無くなる・・・んん〜〜。

    C言語に例えると・・・
    *p++ が、++が付いているから[*]の意味を本来のものと変えてしまっているようなイメージです。

    非常にわかりにくく、混乱の原因です。

    キャンセル

  • 2017/07/15 20:35

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

    キャンセル

  • 2017/07/15 23:56 編集

    > *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

    キャンセル

  • 2017/07/16 00:31 編集

    *labelはポインタのポインタでよろしいのではないでしょうか?

    segmentation faultになるので、ポインタのポインタだと予測しました。

    キャンセル

  • 2017/07/16 01:01

    自分が最初に思ったのは、C言語風に書きます。

    (%rax) : *rax (要素)
    -8(%rax) : *rax - 8 (要素-8)

    なら納得です。
    しかし実際は、
    -8(%rax) : rax - 8 (アドレス -8)という意味です。

    括弧の意味が変わってる!!?

    キャンセル

  • 2017/07/16 11:14 編集

    > segmentation faultになるので、ポインタのポインタだと予測

    漠然としていて、ホントの所を理解できてないように思えます。
    どの時点で、どこのアドレスをアクセスしてセグメンテーションフォールトになるのか、貴方の手元のa.outの機械語コードを例示して、具体的に説明できますか?

    これ、なんとなく、ダメなんだろう…とかではなく、「このコードは"call *subr"命令が〜〜番地を(or ~~番地から)〜〜するので例外になる」と極めて具体的に言えるんですよ。
    以前私は「"call *subr"がセグメンテーションフォールトにならなかったら、奇跡」と書きました。その意味も説明できなくてはならない。

    キャンセル

  • 2017/07/16 12:18

    call *subr はポインタではなく(関数のアドレスではなく)、ポインタのポインタをcallしているのでsegmentation faultになる。

    という説明ではダメでしょうか?

    キャンセル

  • 2017/07/17 19:20 編集

    ダメですね。及第点を与えられない。
    「ポインタのポインタをコールしているから」??? それだけじゃ、"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 *ラベル」という形式の命令は、どんな動作をする命令なのか
    ・私が「奇跡」と言ったわけ
    を、この逆アセンブル表示をもとに、番地など具体的な値を用いて、説明しなさい。

    キャンセル

  • 2017/07/17 19:33 編集

    callq *0x400ae0 の方はラベルのアドレスが指定されています。
    callq *0x400ad8 こちらは、関数のアドレスが指定さています。

    call *ラベルの動作は・・・ラベルのアドレスが指しているメモリ領域のアドレスをcallするという事です。
    これ以上の説明はできません。

    callq *0x400ad8 これは・・・
    ラベルのアドレスが指しているメモリ領域のアドレスがない・・・と言えば良いのでしょうか?

    * + ラベルは間接参照という事ですかね。

    キャンセル

  • 2017/07/17 22:28 編集

    > これ以上の説明はできません

    そうか。くどいと思うだろうが、読んで・図に描いて・トレースしてみてほしい。

    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で終わる、実行可能な機械語列である
    という条件が成り立つなら動作するのだから、絶対に動作しないとは言い切れない。でもそれは奇跡です。

    キャンセル

  • 2017/07/17 23:24 編集

    関数内のプログラムがアドレスとして解釈されているんですね!

    大変分かりやすいです!!
    アセンブリ言語の文法(括弧について)は再度質問するかもしれません(しなかもしれないです)
    長々付き合っていただきありがとうございます。

    キャンセル

  • 2017/07/17 23:45 編集

    > 関数内のプログラムがアドレスとして解釈されている

    そういうこと。
    ・「call *ラベル」という形式だけで決まるのではない事。
    ・ひとつひとつの機械語の動作は、極めて単純。「call *ラベル」は、ちょっと複雑な部類か。プログラマの思惑や思い込みに合わせてくれる…なんてことはなく、決められた通りに、機械語をアドレスと見ることだってやる。

    不可解なバグを調査するときは、人間の思い込みを排除して、CPU(機械)になったつもりでコードを調べる必要がある。
    「神は細部に宿る」・・・コードの流れを大雑把に把握することも大事だが、ひとつひとつの動作の、隅っこの細部まできちんと理解しておくことも大事である。

    キャンセル

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

  • アセンブリ言語

    114questions

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