ELF64 Relocation 처리(1) - ELF64 구조
원문 : http://kkamagui.springnote.com/pages/1709220
들어가기 전에...
0.시작하면서...
이 문서는 ELF64 파일을 Relocation하는 데 필요한 정보가 기술된 문서입니다. 이 문서는 2부분으로 나누어져있으며, 첫번째 문서는 ELF64 파일 포맷에 대한 내용을 다루고 있고, 두번째 문서는 예제 코드를 통해 Relocation을 실제로 수행하는 내용을 담고 있습니다.
1.데이터 타입(Data Type)
ELF64의 경우 데이터 타입은 아래와 같습니다. ELF32와 다른 점은 8Byte 크기의 데이터 타입이 추가되었다는 것입니다.
데이터 타입은 ELF의 각 파트를 설명할 때 값의 표현범위를 나타내기위해 사용할 것이므로, 한번쯤 훓어보면 됩니다. ^^;;;
2.ELF 기본 구조(ELF Basic Structure)
ELF64 또는 ELF32는 크게 아래와 같이 두가지 구조를 가집니다( 이 글에서 다룰 구조는 Relocation File 형태의 구조입니다).
Relocation File은 일반적으로 Relocation에 대한 정보를 포함하고 있으며, 각 Section 마다 해당 Section의 Relocation 정보를 담당하는 Section이 별도로 존재합니다. 따라서 특수한 Section만 골라서 쉽게 Relocation을 수행할 수 있습니다.
3.File Header
File Header는 ELF 파일 포맷의 가장 앞쪽에 위치하는 정보로 ELF 파일 포맷에 대한 각종 정보를 포함하고 있습니다. File Header에 포함된 데이터는 아래와 같습니다.
- typedef struct
{
unsigned char e_ident[16]; /* ELF identification */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Machine type */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point address */
Elf64_Off e_phoff; /* Program header offset */
Elf64_Off e_shoff; /* Section header offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size */
Elf64_Half e_phentsize; /* Size of program header entry */
Elf64_Half e_phnum; /* Number of program header entries */
Elf64_Half e_shentsize; /* Size of section header entry */
Elf64_Half e_shnum; /* Number of section header entries */
Elf64_Half e_shstrndx; /* Section name string table index */
} Elf64_Ehdr;
여러 필드가 있지만, 몇가지 특징적인 부분과 Relocation에 관련된 부분만 살펴보겠습니다.
- e_ident[ 0 ~ 3 ] : 0x7F, 'E', 'L', 'F' 가 각각 들어가있음. ELF 파일 포맷임을 인식하기위함
- e_ident[ 4 ] : 1이면 ELF32, 2면 ELF64를 의미함
- e_ident[ 5 ] : 1이면 Little Endian, 2이면 Big Endian을 의미
- e_type : 1이면 Relocatable Object File, 2이면 Execute File을 의미
- e_shoff : Section Header Table이 존재하는 곳의 파일 Offset을 의미함
- e_ehsize : ELF Header의 크기
- e_shnum : Section Header Table에 포함된 Section의 개수
- e_shstrndx : Section Header에서 Section Name String Table의 Index
우리는 코드가 존재하는 Section(.text)을 돌면서 Relocation을 수행해야하므로, 위에서 언급한 것 중에 파란색으로 마킹한 부분만 유심히 보면 됩니다.
4.Section Header Entries
- typedef struct
{
Elf64_Word sh_name; /* Section name */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section attributes */
Elf64_Addr sh_addr; /* Virtual address in memory */
Elf64_Off sh_offset; /* Offset in file */
Elf64_Xword sh_size; /* Size of section */
Elf64_Word sh_link; /* Link to other section */
Elf64_Word sh_info; /* Miscellaneous information */
Elf64_Xword sh_addralign; /* Address alignment boundary */
Elf64_Xword sh_entsize; /* Size of entries, if section has table */
} Elf64_Shdr;
역시나 많은 필드가 있지만, 특징적인 몇가지와 Relocation에 필요한 부분 위주로 살펴보겠습니다. ^^;;; (자세한 내용이 궁금하신분을은 ELF64 파일 포맷에 대한 Spec 문서를 참조하세요).
- sh_name : Section Name Table 내에서 Section Name이 존재하는 Offset
- sh_type : Section의 타입. 자세한 타입은 아래 테이블 참조
- sh_flag : Section의 속성. 자세한 속성값은 아래 테이블 참조
- sh_addr : Section이 메모리에 로드될 때, Base 주소를 의미함
- sh_offset : 파일 내에서 Section이 존재하는 Offset. 위의 sh_addr의 값과 같을 수도 있으며, 다를 수도 있음
- sh_size : Section의 실제 크기. Byte 수로 표현
- sh_link : 현재 Section과 연결된 Section의 정보. sh_type에 따라서 이 필드는 다양한 의미를 가짐. 자세한 의미는 아래 테이블 참조
- sh_info : 현재 Section에 대한 정보. 위의 sh_link와 마찬가지로 sh_type에 따라 다양한 의미를 가짐. 자세한 의미는 아래 테이블 참조
- sh_entsize : Section에 포함되는 데이터, 즉 여러 개의 엔트리가 동일한 크기를 가진다면 0이 아닌 값으로 설정. 그렇지 않다면 0으로 설정. Symbol table 같은 경우 동일한 크기의 엔트리가 반복되므로 이 값이 0x18을 가짐
4.1 Section Type, sh_type
sh_type은 Section의 타입을 단적으로 보여주는 값입니다. Section의 목적에 따라 아래와 같이 다양한 값을 가질수 있습니다.
일반적으로 코드 및 데이터, 그리고 초기화되지 않은 데이터 Section을 의미하는 .text, .data, .bss의 경우는 SHT_PROGBITS를 가집니다. 즉 Program이 정의한 데이터를 삽입하는 영역이라는 의미입니다.
하지만 Section의 Relocation 정보를 담고있는 Section의 경우는 SHT_RELA를 가지며, Symbol 정보를 포함하는 Symbol Table Section의 경우는 SHT_SYMBTAB의 값을 가집니다.
이 타입에 따라서 sh_link 및 sh_info의 의미가 달라지는데, SHT_PROGBITS가 설정된 Section의 경우는 특별한 의미를 지니지 않습니다. 하지만 SHT_SYMTAB이나 SHT_RELA의 경우는 다릅니다. 다름 챕터를 같이 한번 볼까요?
4.2 sh_link 및 sh_info의 의미
sh_link 및 sh_info는 sh_type 값에 따라 아래와 같은 의미를 가집니다.
그렇다면 우리가 Relocation을 수행할 때는 어떤 정보를 봐야 할까요?
일단 SHT_RELA 타입을 가지는 Section을 검색해서 찾은 후, 해당 Section의 sh_link 필드를 이용해서 Symbol Table Section을 찾습니다. 그리고 SHT_RELA 타입을 가지는 Section의 sh_info 필드를 이용하면 Relocation을 적용해야하는 Section을 찾습니다. 그리고 SHT_RELA 타입을 가지는 Section의 Relocation 정보를 가지고 Relocation을 수행하면 됩니다.
그런데 Relocation 정보를 가지는 Section의 Relocation 정보와 sh_info가 가리키는 Relocation을 적용해야할 Section 정보만 알면 되지, 왜 Symbol 정보를 봐야할까요? 눈치가 빠른 분들은 이미 감을 잡으셨을 겁니다. ^^ 그것은 바로 Relocation Section에 포함되는 Relocation 엔트리 정보는 Symbol Table에 있는 Symbol 엔트리를 참고해서 실질적으로 얼마를 더하고 뺄지가 정해지기 때문입니다. 자세한 내용은 뒤쪽 챕터에서 설명하도록 하고, 지금은 SHT_RELA 타입을 가지는 Section을 찾고 그 Section의 sh_link 및 sh_info 정보를 이용한다는 것만 알고 넘어가겠습니다. ^^
4.3 Section Attribute, sh_flags
속성 값은 아래와 같이 쓰기가 가능한지, 메모리를 할당받아서 실제로 로딩해야하는 Section인지, 코드가 포함되어있어서 실행가능한지에 대한 내용을 포함합니다. 우리가 Relocation 해야하는 Section(.text) 같은 경우, .text Section은 메모리에 로딩되어 실행되야하는 Section이기 때문에 SHF_ALLOC 및 SHF_EXECINSTR 값을 설정합니다. sh_flag는 Relocation 수행 시에는 크게 중요한 필드가 아니므로, 이런 것이 있다는 것만 알고 넘어가겠습니다. ^^;;;
4.4 Special Section
Section의 속성은 위에서 언급한 각가지 필드를 이용해서 설정할 수 있지만, 관습적으로 사용하는 Section 이름들이 있습니다. 예를 들면 실행파일 코드가 있는 Section은 .text, 데이터가 존재하는 Section은 .data, 초기화 되지 않은 데이터는 .bss와 같이 말이지요. ^^
아래는 Elf 파일 포멧에서 정의해놓은 Special Section들의 이름과 Type, 그리고 Attribute를 나타낸 테이블입니다.
주의해서 볼 점은 .relname과 .relaname 라는 이름을 가진 Section이 있다는 겁니다. Section 이름에서 알 수 있듯이 name이 의미하는 Section의 Relocation 정보를 포함하고 있습니다. Section Type 뿐만 아니라 이름에서까지 Relocation Section 임을 알 수 있게 해놓다니... 여차하면 Section Name으로도 Relocation Section을 찾을 수 있을 것 같습니다(물론 권장하지는 않아요~!!! ^^;;;).
5.Symbol Table
Symbol Table은 코드에서 사용된 각종 변수명 및 함수명, 그리고 Section 이름까지 많은 정보를 포함할 수 있습니다. Symbol Table은 아래와 같은 구조체의 배열로 구성되어 있습니다.
역시나 각 필드의 의미를 살펴봐야겠지요? ^^ Symbol Table은 특별히 모든 필드를 살펴보겠습니다.
- st_name : String Table Section 내에서 Section 이름이 존재하는 Offset. String Table은 SHT_SYMTAB의 경우 sh_link 값이 가리키는 Section 임
- st_info : 상위 4bit는 Symbol Binding 정보를 나타내고 하위 4bit는 Symbol Type을 나타냄. Symbol Binding 및 Symbol Type에 대한 정보는 아래 참조
- sh_other : 추후 사용을 위해 예약된 영역. 반드시 0으로 설정
- st_shndx : Symbol이 정의된 Section의 Index. 함수이름 같은 경우 .text Section의 Index를 가리킬 것이고, 변수 이름 같은 경우는 .data Section의 Index를 가리킬 것임
- st_value : Relocatable 파일의 경우 Section 내에서 Symbol이 정의된 Offset을 의미. Executable 및 Shared Object 파일일 경우 Symbol이 정의된 Virtual Address를 의미
- st_size : Symbol의 크기를 의미. 함수의 경우 함수의 크기를 나타내며, 변수의 경우 변수가 차지하는 메모리 공간의 크기를 나타냄
Symbol Table의 경우 각 필드의 의미를 정확하게 알고 있어야 Relocation을 수행할 수 있기 때문에, 모든 필드를 다 살펴봤습니다. 이제 Symbol Table 중에 남은 것은 st_info의 상위 4bit 및 하위 4bit에 대한 내용입니다만, 그리 중요한 부분은 아니니 슬쩍 보고 넘어가겠습니다. ^^;;; Relocation을 수행하는데 Symbol Binding(상위 4bit)과 Symbol Type(하위 4bit)은 그리 중요하지 않기 때문이지요. 일단 Symbol이 있으면 코드 내에 참조하는 곳이 있을테니 Relocation 준비를 (거의 무조건) 해야합니다. 다만 st_shndx의 값이 SHN_ABS 및 SHN_COMMON 일 때는 좀 생각을 해야합니다.
SHN_ABS의 의미는 Value의 값이 Relocation의 영향을 받지않는 절대값이라는 의미이므로 이 Symbol은 Relocation을 하면 안됩니다. SHN_COMMON의 의미는 아직 메모리 공간이 할당되지 않은 Section이라는 의미이므로, 아래에서 언급할 일반적인 방식으로는 Relocation을 수행할 수 없습니다. 만약 하려면 SHN_COMMON으로 설정된 Symbol을 다른 Section에 공간을 할당해주고 Relocation 해야하는데, 이 작업 자체가 쉽지 않고 임의로 할당했다가는 다른 Section의 데이터를 엉망으로 만들 위험이 있습니다.
따라서 수동으로 Relocation하려면 SHN_COMMON을 Section Index로 갖는 Symbol을 줄일 필요가 있는데, 이것은 배열이나 구조체를 "0"으로 초기화 함으로써 해결할 수 있습니다. ^^;;;; 구조체나 배열을 0으로 초기화하면 .bss나 .data 쪽으로 들어가기 때문이지요. 초기화하지 않으면 대용량의 배열이나 구조체 같은 경우는 SHN_COMMON으로 들어갈 확률이 높습니다.
Symbol Binding(st_info의 상위 4bit) 정보는 아래와 같은 값들이 있습니다(하지만 패스~!!).
Global Symbol인지 Local Symbol인지를 나타내는 값이 정의되어있군요. STB_LOCAL이 Object 파일 내에서만 보인다는 것을 보니 static으로 사용한 변수명쯤 되는 것 같습니다.
Symbol types(st_info의 하위 4bit) 정보는 아래와 같은 값들이 있습니다(역시 중요하지 않으므로 패스~!!).
변수명 같은 경우는 STT_OBJECT로 표시되고, 함수명 같은 경우는 STT_FUNC로 표시됨을 추측할 수 있습니다. Section 이름도 Symbol Table에 표시되는데, 이런 경우는 STT_SECTION 타입으로 표시되더군요. ^^;;;
6.Relocations
드디어 Relocation 과정에서 핵심이 되는 Section인 Relocation Section까지 넘어왔습니다. Relocation Section은 Relocation에 대한 정보를 가지고 있는 Elf64_Rel 및 Elf64_Rela 구조체의 배열로 이루어져 있으며, 각각은 아래 그림과 같이 구성되어 있습니다.
Relocation Entry는 아주 중요하므로 전 필드를 다 살펴보겠습니다.
- r_offset : Relocation 파일인 경우, 이 값은 Relocation을 수행해야하는 대상 Section 내에서 Relocation이 적용되어야하는 시작 위치를 나타냄. Executable 파일 또는 Shared Object 파일인 경우, Virtual Address를 의미함
- r_info : 상위 32bit는 Symbol Table 내의 Symbol Offset을 나타냄. 하위 32bit는 Relocation Type을 나타냄. Relocation Type에 대한 정보는 아래 참조
- r_addend : Relocation이 적용될때 더해줘야하는 값
Relocation Entry의 필드중에 r_info를 보시면, Symbol Table의 Symbol 값을 이용하는 것을 볼 수 있습니다. 그럼 Symbol 값과 r_addend 값을 더해서 r_offset이 가리키는 영역에다가 덮어쓰면 될까요? 정답은 "아니요" 입니다. ^^;;;;
Relocation도 단순히 Symbol 값을 대입하는 것부터, r_addend를 더하고 r_offset 값을 빼는 것같은 다양한 방식으로 계산을 수행합니다. 그럼 어떠한 연산을 수행해야 하는지 어떻게 알 수 있을까요? 그렇습니다. 바로 r_info의 하위 32bit 값인 Relocation Type을 보면 됩니다.
Relocation Type은 아래와 같이 정의되어있습니다. ^^
표의 가장 오른쪽에 있는 Calculation 항목에 있는 각 항목들은 아래와 같은 의미를 가지고 있습니다.
- S : Symbol Table에 정의된 Symbol의 값
- A : Relocation Entry의 r_addend 필드 값
- P : Relocation Entry의 r_offset 필드 값
- G : Global Offset Table 안에 Relocation Symbol이 위치한 Offset
- GOT : Global Offset Table의 Offset
- B : 프로그램 실행 시에 Shared Object가 메모리에 로드된 Base Address
- L : Procedure Linkage Talbe 안에 Relocation Symbol이 위치한 Offset
위 항목 중에 G, GOT, B 그리고 L 항목은 공유 라이브러리(Shared Library)나 특수한 옵션(-fPic 등)을 사용하지 않으면 보기 힘든 항목이므로, 일반적으로 많이 사용되는 S, A, P를 위주로 보시면 됩니다. 위 테이블에 나와있는 계산 과정을 가만히 보면 지극히 간단한 규칙을 가지는 것을 알 수 있습니다. 대부분 계산 방법이 Symbol 값에 r_addend 값을 더하고 r_offset의 값을 빼서 Field 항목에 나와있는 크기만큼을 덮어쓰는 것으로 끝입니다. 다음 문서의 Relocation 수행 예제를 보시면 너무 간단해서 허무해 하실지도 모르겠군요. ^^;;;;
다음 챕터로 넘어가기 전에 한마디 덧붙이자면, 정의를 보시면 비슷한 방식으로 계산하지만 Relocation이 수행되어야할 크기에 따라 다르게 분류되어 있습니다.. 이것은 x86 어셈블리어 코드가 다양한 크기의 메모리 및 레지스터를 가지고 연산할 수 있기 때문이며, 그로인해 Relocation을 수행해야하는 영역의 크기도 다양해졌기 때문입니다. 이부분은 값을 실제로 삽입할때 잘라서 넣어주면 처리할 수 있습니다.
7.마치면서...
지금까지 Relocation을 수행하기위해 알아야할 ELF64 파일 포맷에 대한 몇가지 내용들을 봤습니다. Relocation을 수행하기 위해서 위 내용들을 달달 외울 필요는 없습니다. 그냥 "알고" 있으면 되는 것지요. 지금까지 표와 말로 많은 설명을 했습니다만, 아직 감이 잘 안오실 겁니다. ^^;;;; 프로그래머에게는 백마디 말보다 한줄의 코드가 더 좋은 법~!!!! 다음 문서(~!!!)에서는 예제 코드를 작성해서 Relocation을 실제로 해보겠습니다. >ㅁ<)-b
이 글은 스프링노트에서 작성되었습니다.