ハンズオンでELFファイルの構造学んでみた(備忘録)


みなさんどうも。
ふと、プログラムを書いているとマシンに寄り添いたくなる時ってありますよね？
僕はあります。少しでもマシンと心を寄り添いあって開発してみたいと思います。

例えばC言語で以下のようなコード書いた時とします
```C
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}
```

このコードがどのようにマシンに解釈されてどのような姿に変わっていくのか興味湧きますよね？

  
C言語で書いたプログラムが、マシンにとってどんな「実行ファイル」になるのか。

その変化を readelf というツールを使って**バイナリの視点から観察**してみます。

---

### **使用環境・ツール**

- OS: Ubuntu (Dockerコンテナ内)
    
- コンパイル: gcc
    
- ELF解析: readelf
    
- 補助: hexdump,xxd,objdump など

---

# 環境構築

今回はLinuxで多く使われているフォーマットのELF(Executable and Linkable Format)の勉強も兼ねているのでDockerを用いてやっていこうと思います。

とりあえず。
- VScode
- Docker
この辺の環境が最低限整っているとして環境構築していきます。

 まず適当なプロジェクトフォルダを作成してください。

/ Enjoy_ELF(任意で)

VScodeでコンテナに直接接続できるようにjsonで色々書いていきます。
プロジェクトルートに``.devcontainer``フォルダを作成してください。

その下の階層に``devcontainer.json``を書き

##### /.devcontainer/devcontainer.json
```json
{
  "name": "ELF Hack Dev",
  "build": {
    "dockerfile": "Dockerfile"
  },
  "settings": {},
  "extensions": []
}
```

nameは任意でいいです。
ビルド時にLinux環境を明示的にしたいので以下のDockerファイルを書くようにしてください

#### /.devcontainer/Dockerfile
```Dockerfile
FROM --platform=linux/amd64 ubuntu

RUN apt update && apt install -y build-essential nasm xxd binutils bsdmainutils
```

あとは

Macなら command + shift + p
Winなら Ctr + shift + p(だったはず)

でコマンドパレットを出して
「rebuild」って打てば一番上に出てくるこれを選択して接続してください

![Pasted image 20250507145310.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/6445e217-0871-4324-8c60-df09da37dad6.png)


画面左下にこんなのが出てたら成功です。


![Pasted image 20250507150027.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628177/21f87c7c-2044-4c7d-a79e-5a96c6c1aaf0.png)



ターミナルを開いてやっていきましょう〜〜


## C言語コンパイルとバイナリを読んでみる

まず最初に提示したCのファイルを作っていきましょう

**再掲**
/hello.c
```C
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}
```

シンプルに「Hello World!」を表示するコードですよね

実際に見てみましょう

**コンパイルしてみる**

```bash
% gcc hello.c -o hello
```

プロジェクトルートにあるhelloというバイナリファイルが生成されます。

このバイナリファイルは実行というよりマシンが正しく読み取り実行する構造(フォーマット)を持っています

このことで特にLinuxの実行形式は **ELF（Executable and Linkable Format）** と呼ばれています。

**それを実行してやると**
```sh
% ./hello
Hello, World!
```
当たり前ですが、「Hello, World!」と表示されますね。

一旦、このバイナリの中身を確認してみましょうか。

```sh
%  xxd -g 1 hello | head -n 10
00000000: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  .ELF............
00000010: 03 00 3e 00 01 00 00 00 60 10 00 00 00 00 00 00  ..>.....`.......
00000020: 40 00 00 00 00 00 00 00 98 36 00 00 00 00 00 00  @........6......
00000030: 00 00 00 00 40 00 38 00 0d 00 40 00 1f 00 1e 00  ....@.8...@.....
00000040: 06 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00  ........@.......
00000050: 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  @.......@.......
00000060: d8 02 00 00 00 00 00 00 d8 02 00 00 00 00 00 00  ................
00000070: 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00  ................
00000080: 18 03 00 00 00 00 00 00 18 03 00 00 00 00 00 00  ................
00000090: 18 03 00 00 00 00 00 00 1c 00 00 00 00 00 00 00  ................
```

ちなみにVScodeの拡張機能で
「Hex editor」を入れるとVScode上から直接見えます。

```sh
00000000(オフセット): 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00(バイナリ)  .ELF............(ASCII表現)
```
以上なようなもので構成されています。

> ※ **オフセットとは**
> ファイルの0バイト目からどれだけ離れているかを指します。
つまり２行目は0x10なので**16バイト分離れており** その行の先頭は17バイト目ということがわかります。

> ※　今回は詳しく説明しませんが、バイナリファイルには**各種情報が格納されるバイト区間**があり、そこに必要な値が書き込まれています。
　そして、その区間に値がない場合でも、**アライメント（整列）制約**を守るためにパディング（空白バイト）が入っていることがあります。
> 興味がある方は「ELF アライメント制約」などで調べてみてください



ここから読み取れるのは「ELF」という文字列が見えますよね。

そうです。これがELFヘッダーと言われるものです。

このヘッダーの存在によって、**OSはこのファイルがどんなフォーマットで書かれているかを判断できる**ようになっています。

つまり、

 **「このファイルはELF形式です。読み方はこうですよ（＝バイナリの読み方の手順書）」**

という“読み取り説明書”が最初に書いてあるイメージです。




### エントリーポイント

先ほどまでのhelloを片隅まで理解していると日が暮れるので、今回の本題の「このバイナリのどこから実行を始めたらいいのか？」を書いている部分を読んでいこうと思います。


ELFヘッダーのうち、エントリーポイントのアドレスは **0x18（つまり24〜31バイト目）** に記録されています。
（あとで readelf を使えば簡単に確認できますが、ここでは手動で確認してみます）

では、実際に該当箇所を確認してみましょう。
```sh
%  xxd -g1 hello | grep '^00000010:' | cut -d' ' -f10-17
60 10 00 00 00 00 00 00
```

リトルエンディアン形式で格納されているため、上記のバイト列は
```sh
0x0000000000001060
```
このような解釈になります。
ここがコードのエントリーポイント、つまり**OSが最初に実行を始めるアドレス**です。


> ※ リトルエンディアン
> 詳しくは解説しませんが、**メモリ上に格納するときに「下位バイトから順に置いていく」**というイメージです。**
> わかりやすく説明されている以下のサイトをご参考にしていただけると幸いです（いつもお世話になっています）
>   
> [リトルエンディアンとは - IT用語辞典 e-Words](https://wa3.i-3-i.info/word11428.html)
>
   使用しているアーキテクチャ（x86_64）は、OSによらず**基本的にリトルエンディアン**のようです。

 私自身まだそのあたりを理解できているわけではないのですが、
ハードウェア設計や歴史的な理由があるとのことで、いつかちゃんと勉強してまとめたいなぁと思います。

---

## readlefで構造を見てみよう。



```sh
% readelf -l hello

Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1060
There are 13 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000628 0x0000000000000628  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000175 0x0000000000000175  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x00000000000000f4 0x00000000000000f4  R      0x1000
  LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x0000000000000258 0x0000000000000260  RW     0x1000
  DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000030 0x0000000000000030  R      0x8
  NOTE           0x0000000000000368 0x0000000000000368 0x0000000000000368
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000030 0x0000000000000030  R      0x8
  GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
                 0x0000000000000034 0x0000000000000034  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x0000000000000248 0x0000000000000248  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got 
```
まず確認できるポイントとしては

``Entry point 0x1060``
となっており先ほど見たバイナリのアドレスと一致することが確認できます。

#### 見方

```sh
Type           Offset             VirtAddr           PhysAddr
               FileSiz            MemSiz              Flags  Align
```
ここから下に表示されているのは、**ELFのプログラムヘッダ（Program Header Table)の一覧です。  

簡単に言えばどの動作をどこのメモリ領域で展開すべきかを書いている設計書みたいなものです。

- **Type**: セグメントの種類(属性)
- **Offset**: ファイル内でのバイトの位置。セグメントの開始位置がある場所を示してます。
- **VirtAddr**: 仮想メモリ上にマッピングされるアドレス
- **PhysAddr**: 物理メモリアドレス
- **FileSiz**: セグメントのファイルサイズ(バイナリのバイト数)
- **MemSiz**: セグメントのメモリ上に配置される際のサイズ
- **Flags**: セグメントの属性。 R(読み取り可能),W(書き込み可能),E(実行可能)
- **Align**:アライメント制約。セグメントの整列単位


※ 基本現代では仮想アドレスを利用するらしいです。OSとかCPUがマッピングテーブルなるものを参照して仮想アドレスから物理アドレスに変換してアクセス参照しするという手順を踏んでるらしいです。
PhysAddrは無視されることがほとんどらしいです。

以下の記事よりそのことが明記されてたりします。ご興味ある方はどうぞ

[メモリと待機時間に関する考慮事項を管理する(マイクロソフト)](https://learn.microsoft.com/ja-jp/azure-sphere/app-development/memory-latency?view=azure-sphere-integrated&utm_source=chatgpt.com)

[VirtualAddress, LoadAddress, and PhysicalAddress in ELF file?(stackoverflow)](https://stackoverflow.com/questions/38301764/virtualaddress-loadaddress-and-physicaladdress-in-elf-file)

---

#### PHDR

まずPHDRセグメントの説明をしていきます。
```
PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
               0x00000000000002d8 0x00000000000002d8  R      0x8
```


**PHDR(Program Header Table)** →プログラムのヘッダー自体をメモリ上に展開するためのセグメントになってます。
他セグメント(DYNAMICセグメント：後ほど説明)などがこのヘッダーを参照する場合もあるので非常に大事です。

もう少しアドレスを噛み砕いて説明すると、
セグメントの種類はPHDRであり、**ファイルの先頭から0x40番目にある(Offset)。**
仮想アドレス、物理アドレスともに0x40に配置するように指示されている。
(VirtAddr,PhyAddr)。セグメントのファイル上の容量は0x2d8(FileSiz)であり、 メモリに展開される容量も0x2d8(MemSiz)である。**読み取り可能属性(R)** であり(Flags)、アライメントは0x8(Align)

---

#### INTERP

```sh
INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
               0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
```


**INTERP** → 動的リンカ（外部ライブラリへの依存を、実行時に結びつけてくれるもの）**を指定するパスが格納されたセグメント**です。
ELFファイル内に文字列としてそのパスが保存されていて、OSはこの情報をもとに先にリンカを起動します。

ちなみにですが、今回の場合にはOSは「 /lib64/ld-linux-x86-64.so.2」という動的リンカを利用するように、**INTERPセグメントで指定**　されています。

後の説明はPHDRセグメントとほぼ同じです。メモリのアドレスやファイル容量などが違うだけです(セグメントごとの容量のため)。

---

ここまでコンパイルするための下準備の手順書でした。

これから本格的にC言語が本質的に何を求めているのか実際に見ていきましょう。

#### LOAD
```sh
LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
            　　0x0000000000000628 0x0000000000000628  R      0x1000
                 
LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
            　　0x0000000000000175 0x0000000000000175  R E    0x1000

LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000　　　　　                0x00000000000000f4 0x00000000000000f4  R      0x1000

LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
　　　　　　　　　0x0000000000000258 0x0000000000000260  RW     0x1000
```


LOAD → 「C言語がコンパイルされたバイナリの中身を、**実行時にどの仮想メモリ領域にどう展開し、どう保護するか**」をOSに伝える設計情報です。

ふと「**一気にC言語でコンパイルされたバイナリならば一気に全部メモリに展開してよくないか？**」
と疑問に思いますよね。

しかし実際は、「**どの権限で展開するのか(実行可能か？書き込みOK?読み取りのみ？)**」も含めて指定しているらしく、その場合権限によってメモリへのマッピング方法(配置、保護属性)が変わるためセグメントに分けて定義する必要があるみたいです。


以下にセクション一覧を提示しています。

| **セクション** | **属性** | **内容**          |
| --------- | ------ | --------------- |
| .text     | R,E    | 実行命令（CPUで動くコード） |
| .data     | R,W    | 書き換え可能な変数など     |
| .rodata   | R      | 定数（文字列リテラルなど    |
 **アクセス属性が異なると、メモリ上の展開方法（保護や配置）も異なり、必要なアライメント（4KB単位など）も変わってきます。**
そのため、**異なるアクセス属性を持つセクションは、別々の LOAD セグメントにまとめられることが多いのです。**

以下のリンクを参照させていただきました。
[セクションとセグメントの対応](https://warabanshi.hatenablog.com/entry/2013/03/04/004054)



##### コラム：LOADセクションの命令を覗き見る
では、該当場所にどのような命令が入っているのか実際に確認してみましょう。

**0x1000バイト目から0x175バイト分**(2行目のLOADセクション).text命令が入っている実際のバイナリです。


```sh
% hexdump -Cv -s 0x1000 -n 0x175 hello
00001000  f3 0f 1e fa 48 83 ec 08  48 8b 05 d9 2f 00 00 48  |....H...H.../..H|
00001010  85 c0 74 02 ff d0 48 83  c4 08 c3 00 00 00 00 00  |..t...H.........|
00001020  ff 35 9a 2f 00 00 ff 25  9c 2f 00 00 0f 1f 40 00  |.5./...%./....@.|
00001030  f3 0f 1e fa 68 00 00 00  00 e9 e2 ff ff ff 66 90  |....h.........f.|
00001040  f3 0f 1e fa ff 25 ae 2f  00 00 66 0f 1f 44 00 00  |.....%./..f..D..|
00001050  f3 0f 1e fa ff 25 76 2f  00 00 66 0f 1f 44 00 00  |.....%v/..f..D..|
00001060  f3 0f 1e fa 31 ed 49 89  d1 5e 48 89 e2 48 83 e4  |....1.I..^H..H..|
00001070  f0 50 54 45 31 c0 31 c9  48 8d 3d ca 00 00 00 ff  |.PTE1.1.H.=.....|
00001080  15 53 2f 00 00 f4 66 2e  0f 1f 84 00 00 00 00 00  |.S/...f.........|
00001090  48 8d 3d 79 2f 00 00 48  8d 05 72 2f 00 00 48 39  |H.=y/..H..r/..H9|
000010a0  f8 74 15 48 8b 05 36 2f  00 00 48 85 c0 74 09 ff  |.t.H..6/..H..t..|
000010b0  e0 0f 1f 80 00 00 00 00  c3 0f 1f 80 00 00 00 00  |................|
000010c0  48 8d 3d 49 2f 00 00 48  8d 35 42 2f 00 00 48 29  |H.=I/..H.5B/..H)|
000010d0  fe 48 89 f0 48 c1 ee 3f  48 c1 f8 03 48 01 c6 48  |.H..H..?H...H..H|
000010e0  d1 fe 74 14 48 8b 05 05  2f 00 00 48 85 c0 74 08  |..t.H.../..H..t.|
000010f0  ff e0 66 0f 1f 44 00 00  c3 0f 1f 80 00 00 00 00  |..f..D..........|
00001100  f3 0f 1e fa 80 3d 05 2f  00 00 00 75 2b 55 48 83  |.....=./...u+UH.|
00001110  3d e2 2e 00 00 00 48 89  e5 74 0c 48 8b 3d e6 2e  |=.....H..t.H.=..|
00001120  00 00 e8 19 ff ff ff e8  64 ff ff ff c6 05 dd 2e  |........d.......|
00001130  00 00 01 5d c3 0f 1f 00  c3 0f 1f 80 00 00 00 00  |...]............|
00001140  f3 0f 1e fa e9 77 ff ff  ff f3 0f 1e fa 55 48 89  |.....w.......UH.|
00001150  e5 48 8d 05 ac 0e 00 00  48 89 c7 e8 f0 fe ff ff  |.H......H.......|
00001160  b8 00 00 00 00 5d c3 00  f3 0f 1e fa 48 83 ec 08  |.....]......H...|
00001170  48 83 c4 08 c3                                    |H....|
00001175
```
これが実際のバイナリです。


今回はメモリの展開先、そこに何の命令があるのかを軽く見ていきましょう。

ちなみに0x1060まではエントリーポイントではないので、ELFフォーマットなどのメタ情報です。

長すぎて抜粋しながらでしますが以下のコマンド打つと0x1060から0x175バイト分の命令が全て展開されます

0x100まではエントリーポイントではないので、ヘッダー情報です。

```sh
%objdump -D -M intel -j .text --start-address=0x1060 --stop-address=0x11d5 hello
```

##### エントリーポイントの再確認
[ここの章で](#エントリーポイント)確認したリトルエンディアンで格納されている番地がエントリーポイントになっていることが確認できます。

```sh
1060:       f3 0f 1e fa             endbr64
```

ここから最適化の処理やさまざまな呼び出しなどを踏んで

実際にC言語の処理がアセンブリ化されたものです。
今回
```c
printf("Hello,World!\n");
```
なので最適化されて
```c
puts("Hello,World!);
```
となり

printf(“Hello,World!\n”); は、**最適化により puts(“Hello,World!”) に置き換えられることがあります**


実際に、
```sh
　　 1151:       48 8d 05 ac 0e 00 00    lea    rax,[rip+0xeac]     
	1158:       48 89 c7                mov    rdi,rax
    115b:       e8 f0 fe ff ff          call   1050 <puts@plt>
```
この位置が該当場所となり

1行目で lea 命令により、文字列リテラルのアドレスを rax にロードし、

2行目で puts の第1引数（rdi）にそのアドレスを渡し、

3行目で puts を呼び出しています。

実態はここで呼ばれているわけではなく
```sh
<puts@plt>
```
 **PLT**は実行時リンクを行うためのジャンプテーブルで、puts関数のアドレスは実行時に動的にアドレスを特定されそこへ制御が移ります。


```sh
0000000000001149 <main>:
    1149:       f3 0f 1e fa             endbr64
    114d:       55                      push   rbp
    114e:       48 89 e5                mov    rbp,rsp
    1151:       48 8d 05 ac 0e 00 00    lea    rax,[rip+0xeac]        # 2004 <_IO_stdin_used+0x4>
    1158:       48 89 c7                mov    rdi,rax
    115b:       e8 f0 fe ff ff          call   1050 <puts@plt>
    1160:       b8 00 00 00 00          mov    eax,0x0
    1165:       5d                      pop    rbp
    1166:       c3                      ret
```

実際に``objdump``をしてみると色々と見えますので、他にも読んでいくと面白いと思います。
今回は内容から外れすぎるので、ここまでとします。

#### DYNAMIC
```sh
DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
               0x00000000000001f0 0x00000000000001f0  RW     0x8
```
このセグメントでは、動的リンクに必要な情報(ライブラリの読み込み、シンボルの解決)を記録している領域です。

ELFファイルが「実行時にどのライブラリ(.so)を使うか」や「関数のアドレスをどのようにして解決するか」などこの中のテーブル(.dynamicセクション)に詰め込んであります。

実際に見てみると
```sh
% readelf --dynamic hello

Dynamic section at offset 0x2dc8 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x1000
 0x000000000000000d (FINI)               0x1168
 0x0000000000000019 (INIT_ARRAY)         0x3db8
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x3dc0
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x3b0
 0x0000000000000005 (STRTAB)             0x480
 0x0000000000000006 (SYMTAB)             0x3d8
 0x000000000000000a (STRSZ)              141 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x3fb8
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x610
 0x0000000000000007 (RELA)               0x550
 0x0000000000000008 (RELASZ)             192 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000000000001e (FLAGS)              BIND_NOW
 0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE
 0x000000006ffffffe (VERNEED)            0x520
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x50e
 0x000000006ffffff9 (RELACOUNT)          3
 0x0000000000000000 (NULL)               0x0
```

つまり0x1f0(304バイト)分の内容が上記の情報ってことですね。

#### NOTE,GNU_PROPERTY
```
 NOTE            0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000030 0x0000000000000030  R      0x8
                 
NOTE             0x0000000000000368 0x0000000000000368 0x0000000000000368
                 0x0000000000000044 0x0000000000000044  R      0x4

GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
               0x0000000000000030 0x0000000000000030  R       b0x8
```

このセグメントは補助的な情報が入っています。
詳しい中身は以下の通りで見れます。

```sh
% readelf --note hello

Displaying notes found in: .note.gnu.property
  Owner                Data size        Description
  GNU                  0x00000020       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK
        x86 ISA needed: x86-64-baseline

Displaying notes found in: .note.gnu.build-id
  Owner                Data size        Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 832594bbec3cdd9992fe40755f43ad6e4d7c11b8

Displaying notes found in: .note.ABI-tag
  Owner                Data size        Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0
```


#### GNU_EH_FRAME
```sh
GNU_EH_FRAME   0x0000000000002014 0x0000000000002014 0x0000000000002014
               0x0000000000000034 0x0000000000000034  R      0x4
```
例外を処理するための関数情報や、スタックを巻き戻す処理に使われる情報（DWARF形式）が入っています。


#### GNU_STACK
```sh
GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
               0x0000000000000000 0x0000000000000000  RW     0x10
```

このセグメントでは、**プログラム実行時に使用されるスタック領域に対するメモリ保護属性（読み取り／書き込み／実行可能か）を指定しています。**

上の例では RW（読み書き可能）となっており、**実行権限（E）が付いていない**ため、スタック上のデータは**実行できません**。

これは、セキュリティ対策として **スタック実行禁止（non-executable stack）** を有効にしている状態を意味します。

#### GNU_RELRO
```sh
GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
               0x0000000000000248 0x0000000000000248  R      0x1
```
このセグメントは、**プログラム実行時に一時的に書き込み可能（RW）としてロードされるが、その後ただちに読み取り専用（R）に変更されるメモリ領域**を定義しています。

いわば「**Read-Only After Relocation（RELRO）**」の略で、**再配置（relocation）処理が終わった後は変更されないように保護する**のが目的です。


## 最後に

ここまでのご閲覧ありがとうございました。
ELFの構造を知って色々と機械のお気持ちになることはすごく大事なとこだと感じました。
なかなかに書きごたえのある記事で世界は広いしもっと勉強することがあるんだなとより一層感じさせられました。

少しでも本記事が参考や楽しんでいただけていたら幸いです。

## 参照させていただい本
[Binary Hacks Rebooted: 低レイヤの世界を探検するテクニック89選](https://www.oreilly.co.jp/books/9784814400850/)