TIL: ELF Sections, Segments & Linux VMA

이번 학기에 시스템 프로그래밍을 수강하며 CSAPP를 다시 읽어보고 있는데, 공부를 하며 object file의 section과 segment, 그리고 virtual memory의 page와 area 개념이 혼동되어 한 번 정리해보고자 한다.

ELF Section & Segments

Segment는 file을 execute하기 위해 OS가 runtime에 필요로 하는 정보를 담고 있고, sectionlinking 과정에서 linker가 필요로 하는 정보를 담고 있다. 또한, segment는 0개 이상의 section들을 포함할 수 있다. 예시 코드를 통해 자세한 내용들을 살펴 보자.

Example Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

static uint32_t bss[1024];                     /* include 4KB in ".bss" */
static uint32_t data[1024] = {1024};           /* include 4KB in ".data" */
static const int8_t rodata[8192] = {"rodata"}; /* include 8KB in ".rodata" */

int main(void)
{
    char cmd[64];
    printf("0x%lx\n", (unsigned long)malloc(8192));
    sprintf(cmd, "cat /proc/%d/maps", getpid());
    system(cmd);
    exit(EXIT_SUCCESS);
}

본 글에서 계속 사용할 예시 코드이다. 실행하면 프로그램 자신의 memory mapping 상태를 출력하는 간단한 프로그램이다.

Section header table

각 section은 linking에 필요한 특정 정보들을 담고 있다. 예를 들어, .text section은 instruction들을, .strtab은 symbol들을 위한 string들로 이루어진 table을 담고 있다.

Section header table은 각 section의 주소, size 등의 정보를 담고 있다. 아래와 같이 executable의 section header table을 확인할 수 있다.

root@a440443f2c5a:~/random# readelf -S a.out
There are 31 section headers, starting at offset 0x6aa8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000318  00000318
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.propert NOTE             0000000000000338  00000338
       0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.build-i NOTE             0000000000000358  00000358
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c
       0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0
       0000000000000024  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8
       0000000000000120  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           00000000000004e8  000004e8
       000000000000009f  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           0000000000000588  00000588
       0000000000000018  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          00000000000005a0  000005a0
       0000000000000020  0000000000000000   A       7     1     8
  [10] .rela.dyn         RELA             00000000000005c0  000005c0
       00000000000000c0  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             0000000000000680  00000680
       0000000000000090  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020
       0000000000000070  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001090  00001090
       0000000000000010  0000000000000010  AX       0     0     16
  [15] .plt.sec          PROGBITS         00000000000010a0  000010a0
       0000000000000060  0000000000000010  AX       0     0     16
  [16] .text             PROGBITS         0000000000001100  00001100
       00000000000001d5  0000000000000000  AX       0     0     16
  [17] .fini             PROGBITS         00000000000012d8  000012d8
       000000000000000d  0000000000000000  AX       0     0     4
  [18] .rodata           PROGBITS         0000000000002000  00002000
       0000000000002039  0000000000000000   A       0     0     32
  [19] .eh_frame_hdr     PROGBITS         000000000000403c  0000403c
       0000000000000044  0000000000000000   A       0     0     4
  [20] .eh_frame         PROGBITS         0000000000004080  00004080
       0000000000000108  0000000000000000   A       0     0     8
  [21] .init_array       INIT_ARRAY       0000000000005d90  00004d90
       0000000000000008  0000000000000008  WA       0     0     8
  [22] .fini_array       FINI_ARRAY       0000000000005d98  00004d98
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000005da0  00004da0
       00000000000001f0  0000000000000010  WA       7     0     8
  [24] .got              PROGBITS         0000000000005f90  00004f90
       0000000000000070  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000006000  00005000
       0000000000001020  0000000000000000  WA       0     0     32
  [26] .bss              NOBITS           0000000000007020  00006020
       0000000000001020  0000000000000000  WA       0     0     32
  [27] .comment          PROGBITS         0000000000000000  00006020
       000000000000002b  0000000000000001  MS       0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00006050
       00000000000006d8  0000000000000018          29    49     8
  [29] .strtab           STRTAB           0000000000000000  00006728
       0000000000000265  0000000000000000           0     0     1
  [30] .shstrtab         STRTAB           0000000000000000  0000698d
       000000000000011a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Segment header table

Segment header table은 executable 내의 segment들이 어떻게 virtual memory 상으로 mapping되는지, 그리고 각 segment들에 대한 permission이 어떻게 주어졌는지 등의 정보를 담고 있다. 아래와 같이 executable의 segment header table(program header table)을 확인할 수 있다.

root@a440443f2c5a:~/random# readelf -l a.out

Elf file type is DYN (Shared object file)
Entry point 0x1100
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
                 0x0000000000000710 0x0000000000000710  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x00000000000002e5 0x00000000000002e5  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000002188 0x0000000000002188  R      0x1000
  LOAD           0x0000000000004d90 0x0000000000005d90 0x0000000000005d90
                 0x0000000000001290 0x00000000000022b0  RW     0x1000
  DYNAMIC        0x0000000000004da0 0x0000000000005da0 0x0000000000005da0
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x000000000000403c 0x000000000000403c 0x000000000000403c
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000004d90 0x0000000000005d90 0x0000000000005d90
                 0x0000000000000270 0x0000000000000270  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
  • a.out에는 총 13개의 segment가 존재함을 확인할 수 있다.
  • 각 segment들의 mapping 상태permission 등을 확인할 수 있다.
  • 각 segment들의 type의 의미는 아래와 같다:
    • PHDR: 이 type의 segment는 segment header table의 size와 위치 정보를 갖고 있다.
    • INTERP: 이 type을 갖는 segment의 VirtAddr, FileSiz filed는 각각 section .interp의 VA와 size와 같다. .interp section은 program interpreter의 path name을 담고 있다.
    • LOAD: memory에 loadable한 segment들의 type이다. loadable code segment와 그 뒤에 바로 이어지는 loadable data segment로 이루어져있다.
    • DYNAMIC: dynamic linking에 대한 정보를 담고 있는 segment의 type이다.
    • NOTE: ABI에 대한 정보와 같은 추가적인 정보를 담고 있는 segment의 type이다.
  • Section to Segment mapping을 통해 00번 segment와 같이 0개의 section을 포함하는 segment도 있고, 많은 section을 포함하는 segment도 있음을 확인할 수 있다.

Virtual Memory Page & Area

Virtual Memory Page

Virtual memory의 pagevirtual memory의 fixed-length continuous block으로, OS가 virtual memory를 관리할 때 사용하는 가장 작은 단위이다.

Architecture의 종류에 따라, 또한 OS의 종류에 따라 page size는 달라질 수 있다. x86-64에서의 Linux는 4 KB를 사용한다.

Virtual Memory Area

Linux에서, 한 process의 virtual memory는 Virtual Memory Area들의 집합으로 구성된다. 각 area서로 연관된 page들의 chunk이다. 예시 코드를 실행하면 아래와 같이 process의 VMA들을 확인할 수 있다.

555555554000-555555555000 r--p 00000000 08:20 243834                     /root/random/a.out
555555555000-555555556000 r-xp 00001000 08:20 243834                     /root/random/a.out
555555556000-555555559000 r--p 00002000 08:20 243834                     /root/random/a.out
555555559000-55555555a000 r--p 00004000 08:20 243834                     /root/random/a.out
55555555a000-55555555c000 rw-p 00005000 08:20 243834                     /root/random/a.out
55555555c000-55555557e000 rw-p 00000000 00:00 0                          [heap]
7ffff7dcf000-7ffff7df1000 r--p 00000000 08:20 133236                     /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7df1000-7ffff7f69000 r-xp 00022000 08:20 133236                     /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7f69000-7ffff7fb7000 r--p 0019a000 08:20 133236                     /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fb7000-7ffff7fbb000 r--p 001e7000 08:20 133236                     /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fbb000-7ffff7fbd000 rw-p 001eb000 08:20 133236                     /usr/lib/x86_64-linux-gnu/libc-2.31.so
7ffff7fbd000-7ffff7fc3000 rw-p 00000000 00:00 0
7ffff7fca000-7ffff7fce000 r--p 00000000 00:00 0                          [vvar]
7ffff7fce000-7ffff7fcf000 r-xp 00000000 00:00 0                          [vdso]
7ffff7fcf000-7ffff7fd0000 r--p 00000000 08:20 133214                     /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7fd0000-7ffff7ff3000 r-xp 00001000 08:20 133214                     /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ff3000-7ffff7ffb000 r--p 00024000 08:20 133214                     /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffc000-7ffff7ffd000 r--p 0002c000 08:20 133214                     /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffd000-7ffff7ffe000 rw-p 0002d000 08:20 133214                     /usr/lib/x86_64-linux-gnu/ld-2.31.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]

각 line이 하나의 VMA를 의미하며, 각 line의 field는 다음과 같다: start-end permissions offset major:minor inode image

이때 gdb를 이용해 각 section들의 위치를 확인해보면 아래와 같다.

gdb-peda$ elfheader
.interp = 0x555555554318
.note.gnu.property = 0x555555554338
.note.gnu.build-id = 0x555555554358
.note.ABI-tag = 0x55555555437c
.gnu.hash = 0x5555555543a0
.dynsym = 0x555555554410
.dynstr = 0x555555554aa0
.gnu.version = 0x555555554dde
.gnu.version_r = 0x555555554e70
.rela.dyn = 0x555555554ed0
.rela.plt = 0x555555555248
.init = 0x555555556000
.plt = 0x555555556020
.plt.got = 0x555555556340
.plt.sec = 0x555555556350
.text = 0x555555556660
.fini = 0x55555555a424
.rodata = 0x55555555b000
.eh_frame_hdr = 0x55555555c22c
.eh_frame = 0x55555555c4e8
.init_array = 0x55555555ea90
.fini_array = 0x55555555ea98
.data.rel.ro = 0x55555555eaa0
.dynamic = 0x55555555ec38
.got = 0x55555555ee28
.data = 0x55555555f000
.bss = 0x55555555f0c0
  • 555555555000-555555556000에 mapping되어 있는 loadable code segment는 .text, .plt, .init, .fini와 같은 machine instruction을 포함하는 section을 포함하고 있음을 알 수 있다. 이 segment의 경우 permission이 r-xp로, execute가 가능함을 확인할 수 있다.
  • 각 VMA들의 start, end address가 0x1000 byte (4KB) 단위임을 확인할 수 있다. → x86-64 상의 Linux에서는 한 page의 size가 4KB이므로, OS가 page 단위로 VM을 관리하고 있음을 확인할 수 있다.

참고한 링크

특히 첫 두 링크는 CSAPP에 나오는 linking과 VM에 대한 내용들을 전부 아우르며 깊게 다루는 매우 유익한 글이었다.

Categories:

Updated:

Leave a comment