ELF(Executable and Linking Format)은 binary file[1]로서, Unix System Laboratory에서 개발되고 발전되어왔다. SVR4와 Solaris 2.X version의 운영체제에서는 기본적인 실행 file의 format으로 사용되고 있다. 실행 file의 format으로는 a.out과 COFF format이 있지만, ELF format이 보다 강력하며, 유연성을 가지고 있다. 적절한 tool과 같이 사용될 때 실행되는 과정을 제어 할 수 있다.현재 리눅스는 kernel차원에서 binary file format에 대한 지원을 가지고 있으며, binary file 자체가 가진 특정한 magic number로 실행할 method를 찾게 된다. 이번 장에서는 먼저 ELF file의 format에 대해서 알아보도록 하자[2].
ELF는 UNIX System Laboratory에서 Application Binary Interface(ABI)의 일부로서 개발되고 발표되었다. Tool Interface Standards committee(TIS)에서 32 bit Intel Architecture 환경에서 동작하는 portable object 파일 포맷으로 ELF 표준을 선택했다. 프로그램머에게 ELF표준은 여러 운영체제 환경으로 확장될 수 있는 binary 인터페이스 정의들의 집합을 제공한다. 따라서, 프로그램머들은 binary 파일의 이러한 인터페이스만을 중심으로 프로그램을 할 수 있는 방법을 제공받을 수 있으며, 더불어 새로이 코드를 재 컴파일해서 기록할 필요가 없게된다.
Object 파일에는 크게 3가지가 존재한다. 각각은 아래와 같이 정의될 수 있다.
l재배치 가능 파일(relocatable file) – 이 파일은 링크를 쉽게 할 수 있는 코드 및 데이터와 함께, 실행 파일을 만들거나 공유 object파일을 생성하기위한 다른 object파일을 가지고 있는 형태의 파일이다.
l실행 가능 파일(executable file) – 이 파일은 실행을 위해서 적합한 프로그램을 가지는 파일로서, exec시스템 콜이 어떻게 프로세스의 이미지를 프로그램을 이용해서 만들지를 기술하는 파일이다.
l공유 object 파일(shared object file)은 두개의 환경(context)상에서 링크에 적합한 형태의 코드와 데이터를 가지는 파일로서, 먼저 LD와 같은 링크 에디터가 이 파일과 함께, 다른 재배치 가능 파일과 공유 object파일을 처리해서 또하나의 object 파일을 생성해주게 되고, 이를 다시 동적 링커(dynamic linker)가 생성된 object 파일을 실행 가능 파일과 다른 공유 object파일을 묶어서 프로세스의 이미지를 생성해 주는 두 단계를 거치는 파일이다.
어셈블러나 링크 에디터에 의해서 생성된 object 파일은 프로세서에서 실행될 프로그램의 이진(binary) 형식을 가지게 될 것이며, 이곳에서의 논의는 쉘 스크립터와 같은 것은 제외할 것이다.
먼저 ELF 파일의 형식(format)을 보면 [그림]와 같다. 즉, object 파일은 크게 두가지의 관점에서 볼 수 있는데, 각각이 프로그램의 링킹(linking)과 프로그램의 실행이라는 측면이 될 것이다.
[그림] ELF 파일의포맷– 관점에따른형식
[그림]에서 중요한 부분만을 훑어보면, ELF 헤더(header)는 파일의 구성을 나타내는 로드맵(road map)과 같은 역할을 하며, 첫 부분을 차지한다. 섹션(section)은 링킹을 위한 object 파일의 정보를 다량으로 가지고 있으며, 이에 해당하는 것으로는 명령(instruction), 데이터(data), 심벌 테이블(symbol table), 재배치 정보(relocation information)등등이 들어간다. 프로그램 헤더 테이블(program header table)은 옵션(option)이며, 시스템에 어떻게 프로세스 이미지를 만들지를 지시한다. 프로세스의 이미지를 만들기 위해서 사용되는 ㅍ일은 반드시 프로그램 헤더 테이블을 가져야하며, 재배치 가능 파일의 경우에는 가지지 않아도 된다. 섹션 헤더(section header) 테이블는 파일의 섹션들에 대해서 알려주는 정보를 가지는데, 모든 섹션은 이 테이블에 하나의 엔트리(entry)를 가져야 한다. 각각의 엔트리는 섹션 이름이나, 섹션의 크기와 같은 정보를 제공해 준다. 만약 파일이 링킹하는 동안 사용된다면, 반드시 섹션 헤더 테이블을 가져야하며, 다른 object파일은 섹션 헤더 테이블을 가지고 있지 않을 수도 있다. 이에 해당하는 정보들은 리눅스의 경우 ~/include/linux/elf.h에서 찾을 수 있다.
코드를 직접 보기 전에 이하의 내용에서 사용하게 될 데이터 타입에 대한 정의부터 하기로 한다. 데이터 타입이 필요한 것은 ELF format이 다양한 프로세서에서 사용할 수 있으며, 이들 각각이 사용하는 기본 데이터의 길이가 달라질 가능성이 있기 때문이다. 따라서, object 파일은 기계 의존적인 특성들에 대비를 해야하는데, 기계에 의존적이지 않은 형식을 지원하기 위한 제어 데이터(control data)가 이 역할을 해주고 있다. 즉, object 파일을 인식하고, 파일의 내용을 일반적인 방법으로 해석하도록 돕는다.
리눅스에서는 아래와 같은 것을 정의해서 사용하고 있다.
/* 32-bit ELF base types. */
typedef __u32Elf32_Addr;
typedef __u16Elf32_Half;
typedef __u32Elf32_Off;
typedef __s32Elf32_Sword;
typedef __u32Elf32_Word;
/* 64-bit ELF base types. */
typedef __u64Elf64_Addr;
typedef __u16Elf64_Half;
typedef __s16Elf64_SHalf;
typedef __u64Elf64_Off;
typedef __s64Elf64_Sword;
typedef __u64Elf64_Word;
코드1. ELF 파일형식에사용할데이터타입정의
두가지로 나누어 볼 수 있는데, 먼저 32bit 기계에 대한 것과 64bit 기계에 대한 부분이다. 각각이 기본 연산의 단위로 32bit과 64bit을 사용함을 알 수 있다. 나중에 여기서 정의된 것을 이용해서 다른 데이터 타입에 대한 정의를 하게 될 것이다. 만약 이러한 데이터 타입을 제공하게 될 때, 기본 연산의 단위로 정렬(alignment)하고 싶다면, 패딩(padding)을 삽입해서 4bytes나 혹은 8bytes단위로 정렬할 수 있을 것이다.