TIL: ELF Sections, Segments & Linux VMA
이번 학기에 시스템 프로그래밍을 수강하며 CSAPP를 다시 읽어보고 있는데, 공부를 하며 object file의 section과 segment, 그리고 virtual memory의 page와 area 개념이 혼동되어 한 번 정리해보고자 한다.
ELF Section & Segments
Segment는 file을 execute하기 위해 OS가 runtime에 필요로 하는 정보를 담고 있고, section은 linking 과정에서 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의 page는 virtual 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을 관리하고 있음을 확인할 수 있다.
참고한 링크
- https://web.archive.org/web/20171129031316/http://nairobi-embedded.org/040_elf_sec_seg_vma_mappings.html#ref3
- https://web.archive.org/web/20171130164537/http://nairobi-embedded.org/004_elf_format.html
- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- https://docs.oracle.com/cd/E19683-01/816-1386/6m7qcoblj/
특히 첫 두 링크는 CSAPP에 나오는 linking과 VM에 대한 내용들을 전부 아우르며 깊게 다루는 매우 유익한 글이었다.
Leave a comment