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

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

ただいまの
回答率

90.85%

  • Linux

    3368questions

    Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

  • C

    3334questions

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

ELFファイルのレイアウトとメモリへの読み込まれ方がわからない

解決済

回答 2

投稿

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

wanna-be

score 15

ELFファイルのレイアウトとそのメンバについてわからないことがあります。

OS情報です。

Linux linux-mint 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

以下は単純なCプログラム(main関数からsleep()を呼び出してスリープするだけです)からreadelf -l a.outでELFの情報を出したものです。

(図1)
![イメージ説明](4b76bc5cfd748cd1f78b9fe247285cc9.png)

Program Headersのところには、プログラムヘッダの一覧が出ています。

typedef struct {
    uint32_t   p_type;   /* セグメントのタイプ */
    uint32_t   p_flags;   /*  セグメントに関係するフラグ */
    Elf64_Off  p_offset; /* ファイル先頭からセグメントまでのオフセット */
    Elf64_Addr p_vaddr; /* セグメントの最初のバイトが格納される仮想アドレス */
    Elf64_Addr p_paddr; /* セグメントの最初のバイトが格納される物理アドレス */
    uint64_t   p_filesz; /* セグメントのファイルイメージサイズ */
    uint64_t   p_memsz; /* メモリ上に配置されるセグメントのサイズ */
    uint64_t   p_align; /* アライメント */
} Elf64_Phdr;

プログラムヘッダを表現する構造体は上記です。

また、先ほどのCプログラムを実行した時の/proc/pid/mapsの出力を見てみると以下のようになっています。

(図2 !! ASLRを無効化してコンパイルしてあります)
イメージ説明

わからないことの1つ目なのですが、Elf64_Phdrのメンバであるp_vaddrは、manページによるとセグメントの先頭バイトがある仮想アドレスを保持しているはずです。例えば、図1の上から4番目のLOADタイプのセグメントはDATAセグメントだと思われます。また、そのp_vaddrは0x0000000000600e10となっていますが、図2の3行目では開始アドレスは0x006010000となっており、ELFファイルが保持する情報と一致していないように思われます。

しかし、図1の上から3番目のLOADタイプのTEXTセグメントと思われるセグメントのp_vaddrは0x0000000000400000となっていますが、図2の一行目の開始アドレスは0x00400000となっており、こちらは一致しているように見えます。

これはなんなのでしょうか?

2つめですが、セグメントの位置と大きさについてです。図1にあるDATAセグメントの開始アドレスが0x0000000000600e10であり、 メモリ上のセグメントサイズを表すp_memszは0x230となっています。とすると、データセグメントの終了アドレスは以下のようになると思います。(1つめの疑問からいろいろと間違っているかもしれませんが、一応自分の頭の中で考えていることとして書きます)

0x0000000000601040 = 0x0000000000600e10 + 0x230

ですが、文字通りにとれば、DATAセグメントが0x0000000000601040で終わるとすると、その下のDYNAMICセグメントの開始アドレス0x0000000000600e28と被ってしまうはずです。

ELFはどのようにメモリにロードされているのでしょうか?

よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

0

1つ目の質問ですが、実メモリ上のdataセグメントの開始アドレスは、ldで使われるデフォルトのリンカースクリプトの指定で並べ替えられているようです。こちらのブログの記事を読ませていただいて、分かり易かったので案内させていただきます。
ELF実行ファイルのメモリ配置はどのように決まるのか

私の環境(Ubuntu 17.04 64ビット)でも ld --verboseでリンカースクリプトを確認したところ、全内容の精査まではしていないものの、それっぽくなっていました。

以下の指摘事項ですが、

図2の3行目では開始アドレスは0x006010000となっており、ELFファイルが保持する情報と一致していない

(※0x00601000 の間違いですよね?)これはOracleのサイトでのELFの説明(ただし、Solaris/SunOS用)にヒントとなる記述がありました。
Program Header (Linker and Libraries Guide)

ここの「Base Address」の節で、以下の記述があります。

To compute the base address, you determine the memory address associated with the lowest p_vaddr value for a PT_LOAD segment. You then obtain the base address by truncating the memory address to the nearest multiple of the maximum page size. Depending on the kind of file being loaded into memory, the memory address might not match the p_vaddr values.

この文の後半、You then obtain...の文で「メモリアドレスの最大のページサイズの倍数に切り捨てることでベースアドレスを取得できる。メモリーにロードされたファイルの種類によっては、メモリーアドレスはp_vaddr の値に合致しない」とのことです。linuxのページサイズは4KiB(0x1000)なので、その倍数単位でベースアドレスになっていることに合致します。

また、android のlibcの代替であるCランタイムbionic のローダーのコード中の説明で、
linker/linker_phdr.cpp - platform/bionic

Finally, the real load addrs of each segment is not p_vaddr. Instead the
  loader decides where to load the first segment, then will load all others
  relative to the first one to respect the initial range layout.

との記述があります。いずれもlinux 64ビット版そのものではないですが、メモリ上にセグメントを割り当てる上でページサイズは意識せざるを得ないでしょうから、ELF形式の実行ファイルのローダーの挙動として参考になるかと思います。

まとめると、Elf64_Phdr->p_vaddr は 0x00600e10 なのに 実アドレス 0x00601000 からロードされるのは、ページサイズを意識した境界にローダーが配置する為で、更になぜ 0x00601000なのかと言えば、リンカースクリプトでそのように指定しているから、と言うことが言えるかと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/05/17 21:18 編集

    回答ありがとうございます。
    やはり、アドレスの決定はリンカーが深く関与しているということですね。詳しく調べてみます。
    ローダがどのように配置するか知りたく、いっそのことカーネルのコードを見てしまえと思ったので読んでみることにしました。リンカスクリプトも少しいじってみようと考えています。
    ありがとうございました。

    キャンセル

0

読み込み方だけについてならば
TypeがLOADになってるこの場合2つだけ見ます。

0x006000000 - 0x006010000の属性が書き込み不可なのだけ引っかかりますが
後ろで宣言されてるGNU_RELROによって属性が上書きされてるのかもしれませんね

その下のDYNAMICセグメントの開始アドレス0x0000000000600e28と被ってしまうはずです。

FILEオフセットを計算すると同じ場所を示しています。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/05/16 11:16

    回答ありがとうございます。

    > FILEオフセットを計算すると同じ場所を示しています。
    セグメントのp_offsetとp_vaddrはそれぞれファイル内で格納されている場所と実際にメモリにロードされる場所であり、それらは関係がない(お互いに影響を与えるものでない)と思っていたのですが、p_vaddrとp_offsetの関係はどうなっているのでしょうか?

    また、セグメントはPT_LOADタイプだけがメモリにロードされるということを思い出しました。自分でも混乱してよくわからないのですが、ではなぜ、ロードされないPT_NOTEやPHDRにもp_memszやp_vaddrなどの属性の値が指定してあるのでしょうか?
    リンカの都合でしょうか?

    キャンセル

  • 2018/05/16 11:53

    > それぞれファイル内で格納されている場所と実際にメモリにロードされる場所であり、それらは関係がない
    アドレス 0x0000000000600e28について
    LOADのVAから辿る場合: 0x0000000000600e10 + 0x18
    DYNAMICのVAから〃〃: 0x0000000000600e28 + 0
    ファイル上の位置では
    LOAD: 0xe10 + 0x18 = 0xe28
    DYNA: 0xe28 + 0x00 = 0xe28
    で同じ位置から読み込んでいます。

    PT_LOADタイプで読み込んで、その他のタイプのセグメントによって用途をOS等に伝えている。
    それだけだと思いますよ。

    > p_memszやp_vaddr
    VAおよびファイルオフセットどちらからでもアクセスできるようにだと思いますよ
    他のツールから特定セグメントのみ閲覧したい場合もありますし、PT_LOADの場合よりもわざわざ情報量を減らすメリットもなさそうです。

    キャンセル

  • 2018/05/16 12:20

    ありがとうございました。
    もう少し詳しく調べて見ます。

    キャンセル

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

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

関連した質問

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

  • Linux

    3368questions

    Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

  • C

    3334questions

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