개요
ELF (Executable and Linkable Fromat)이란 유닉스 시스템에서 사용하기 위해서 발명된 Executable Format이다. ELF 포맷은 프로그램의 실행을 크게 TEXT, DATA, BSS섹션으로 나눈다. 현재 ELF포맷은 유닉스시스템에서 표준으로 사용되며 많은 영향을 미치고 있다.
구조
ELF는 프로그램의 부분을 담고 있는 포맷이다. ELF는 여러 섹션으로 나누어지는데, 이 섹션은 헤더에 의해서 어떻게 메모리에 적제될지 결정하게 된다. 이러한 헤더에는 링킹단계에서 symbol 테이블에 의해서 결정된다.
.text | 코드 영역이 작성되는 부분이다. objdump -drS .process.o will show you that |
.data | global tables, variables, etc. 들이 적재되는 공간이다. objdump -s -j .data .process.o will hexdump it. |
.bss | 0으로 초기화 되는 변수들이 저장되는 공간이다. 0으로 초기화 되는 변수들은 여기에 위치함으로써 링커가 로딩과정에서 자동으로 0으로 채우게 된다. |
.rodata | 문자열이 저장되는 공간이다. ReadOnly data의 약자이며, 문자열과 같은 const데이터들은 여기에 저장된다. (! 만약 const를 초기화 하지 않으면 이 또한 bss섹션으로 저장된다.) |
.comment & .note | 컴파일러나 링커가 작성한 커맨트 들이 들어간다. |
.stab & .stabstr | 디버깅 심볼과 디버깅 관련 정보가 들어간다. |
컴파일러와 링커는 이 정보를 해석하여 ELF파일 헤더로 변환하여 저장하게 된다. 각각의 ELF파일 헤더는 위치하는 섹션에대한 offset과 size등과 같은 메모리 로드에 필요한 정보를 포함하여, ELF파일의 어떤 위치에 어떤 정보가 저장되어 있는지 나타내게 된다.
ELF 파일 헤더는 32비트 또는 64비트 주소들이 사용되어야 하는지를 정의한다.
오프셋 | 크기(Bytes) | 필드 | 목적 | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
32-bit | 64-bit | 32-bit | 64-bit | ||||||||||||||||||||||||
0x00 | 4 | e_ident[EI_MAG0] 부터 e_ident[EI_MAG3]
|
0x7F 와 ASCII 코드 ELF ; 이 네 바이트가 매직 넘버를 형성한다.
| ||||||||||||||||||||||||
0x04 | 1 | e_ident[EI_CLASS]
|
이 바이트는 1 또는 2 로 설정되며 32비트 또는 64비트 형식을 나타낸다.
| ||||||||||||||||||||||||
0x05 | 1 | e_ident[EI_DATA]
|
이 바이트는 1 또는 2 로 설정되며 리틀 또는 빅엔디언을 나타낸다.
| ||||||||||||||||||||||||
0x06 | 1 | e_ident[EI_VERSION]
|
오리지널 버전의 ELF인 경우 1 로 설정된다.
| ||||||||||||||||||||||||
0x07 | 1 | e_ident[EI_OSABI]
|
대상 운영 체제 ABI를 구별한다..
이것은 대상 플랫폼과 관련 없이 종종 | ||||||||||||||||||||||||
0x08 | 1 | e_ident[EI_ABIVERSION]
|
ABI 버전을 더 명시한다. 이 해석은 대상 ABO에 따라 달라진다. 2.6 버전 이후의 리눅스 커널은 이것을 정의하지 않는다. 이 경우 오프셋과 크기는 8 이다.
| ||||||||||||||||||||||||
0x09 | 7 | e_ident[EI_PAD]
|
현재 사용하지 않음 | ||||||||||||||||||||||||
0x10 | 2 | e_type
|
1 , 2 , 3 , 4 는 각각 재배치, 실행, 공유 그리고 코어를 명시한다.
| ||||||||||||||||||||||||
0x12 | 2 | e_machine
|
대상 명령어 집합을 명시한다. 예를 들면:
| ||||||||||||||||||||||||
0x14 | 4 | e_version
|
오리지날 버전의 ELF인 경우 1 로 설정된다.
| ||||||||||||||||||||||||
0x18 | 4 | 8 | e_entry
|
이것은 엔트리 포인트의 메모리 주소이다. 즉 프로세스가 어디서 실행을 시작하는지를 말해준다. 이 필드는 위에서 정의한 32비트 또는 64비트에 따라 길이가 다르다. | |||||||||||||||||||||||
0x1C | 0x20 | 4 | 8 | e_phoff
|
프로그램 헤더 테이블의 시작을 가리킨다. | ||||||||||||||||||||||
0x20 | 0x28 | 4 | 8 | e_shoff
|
섹션 헤더 테이블의 시작을 가리킨다. | ||||||||||||||||||||||
0x24 | 0x30 | 4 | e_flags
|
대상 아키텍처에 따라 이 필드의 해석이 달라진다. | |||||||||||||||||||||||
0x28 | 0x34 | 2 | e_ehsize
|
이 헤더의 크기를 가지며 일반적으로 64비트의 경우 64바이트, 32비트의 경우 52바이트이다. | |||||||||||||||||||||||
0x2A | 0x36 | 2 | e_phentsize
|
프로그램 헤더 테이블 엔트리의 크기를 갖는다. | |||||||||||||||||||||||
0x2C | 0x38 | 2 | e_phnum
|
프로그램 헤더 테이블에서 엔트리의 개수. | |||||||||||||||||||||||
0x2E | 0x3A | 2 | e_shentsize
|
섹션 헤더 테이블 엔트리의 크기를 갖는다. | |||||||||||||||||||||||
0x30 | 0x3C | 2 | e_shnum
|
섹션 헤더 테이블에서 엔트리의 개수. | |||||||||||||||||||||||
0x32 | 0x3E | 2 | e_shstrndx
|
섹션 이름들을 포함하는 섹션 헤더 테이블 엔트리의 인덱스. |
64 bit version의 Executable Header.
Position | Value |
0-3 | Type of segment (see below) |
4-7 | Flags (see below) |
8-15 | The offset in the file that the data for this segment can be found (p_offset) |
16-23 | Where you should start to put this segment in virtual memory (p_vaddr) |
24-31 | Undefined for the System V ABI |
32-39 | Size of the segment in the file (p_filesz) |
40-47 | Size of the segment in memory (p_memsz) |
48-55 | The required alignment for this section (must be a power of 2) |
Segment types: 0 = null - ignore the entry; 1 = load - clear p_memsz bytes at p_vaddr to 0, then copy p_filesz bytes from p_offset to p_vaddr; 2 = dynamic - requires dynamic linking; 3 = interp - contains a file path to an executable to use as an interpreter for the following segment; 4 = note section. There are more values, but mostly contain architecture/environment specific information, which is probably not required for the majority of ELF files.
Flags: 1 = executable, 2 = writable, 4 = readable.
ELF파일의 로딩
- ELF파일의 Magic이 일치하는지 확인한다.
- ELF파일의 첫부분에 있는 ELF헤더를 읽어서 ELF파일을 해석할 준비를 한다.
- ELF파일 헤더에 나타난 Executable헤더를 찾아가서 그 정보를 읽는다.
- 프로그램 헤더를 파싱해서, 프로그램 세그먼트를 몇개 로딩해야 하는지 구한다. 이떄 PT_LOAD type만이 로딩할수 있는 헤더이다.
- 각각의 로더블 세그먼트를 로딩한다.
- 각각의 세그먼트에 대한 Virtual memory를 할당하고 (p_vaddr)길이는 p_memsz만큼 할당한다.
- 세그먼트 데이터를 파일 오프셋 p_offset부터 p_filesz만큼 copy 한다.
- p_filesz와 p_memsz가 다르면 0으로 padding되었다는 것이다.