JJ's blog

ELF - Excutable and Linkable Format 본문

Technology/LINUX

ELF - Excutable and Linkable Format

Jayden_Ji 2019. 4. 25. 14:13

 

출처: https://blog.naver.com/xogml_blog/130143099472

 

ELF 

우선 ELF가 뭘까 ? 여기저기서 많이 들어본 이름인데.... 아마 시스템프로그래밍때 들어보았을 것이다.

ELF란 Excutable and Linkable Format 의 약어로써...

실행 가능하고, 또한 링킹 가능한 포맷의 파일이다.

 

우선 들어가기 전에 ELF를 눈으로 한번 보자.

/usr/bin으로 가자. 많은 파일들이 보이는데 이것들이 다 ELF들이다.

오호 여기보니 우리가 자주 쓰는 gcc 도 있다.

 

# readelf -h gcc  을 해보자. 뭔지는 모르지만 elf를 읽어라는 명령인거 같다.

오 이렇게 해보니 뭐가 주르륵 뜬다. 어쨋든 gcc도 ELF인가 보다

 

자 이제 본격적으로 ELF가 뭔지 알아보자. 우선 단순히 말하면 ELF 는 실행파일이다

그렇다면 실행이란 무엇인가?(아놔 ㅋㅋㅋ)

 

실행이라 하면 디스크에 저장되어있던 프로그램이 메모리영역에 올라가서 컴퓨팅자원을 사용하여 서비스를 제공해주는것일 것이다.? ㅋ

이렇게 실행중인 프로그램, 즉 프로세스에게는 언제나 주소공간이라는 메모리 영역이 주어지는데(물론 100% 물리 메모리는 아님.

MMU를 이용한 페이징을 통해서 가상 주소를 제공... 여튼) 이것이 갑자기 뚝딱 생기는것이 아니라, ELF 포맷에서 기원하는 것이다.

 

바로 이것이 세그먼트 이다.

ELF에서는 프로그램이 실행될때 메모리에 올라가야될 각각의 부분들을 미리 정리하여 관리하다가, 실행해라! 똭 하면

정리된 부분들(코드, 전역 데이터, 읽기전용 데이터 등등등) 을 샤샤샥 올리는것이다(몇가지 영역을 덧붙여서)

그래서 이렇게 올라온 주소공간의 .text영역, 즉 코드영역의 Instruction을 한라인 한라인 실행하며 프로세스가 진행되는것이다.

물론 같이 올린 + 덧분인 영역들의 데이터를 사용하면서......

 

 

그렇다면 Linkable는 뭘까 ?

 //main.c

#include "my_header.h"

 

int main(void) {

my_func();

return 0;

}

//my_header.h

 

void my_func(); 

 

 

 

 

//my_header.c

#include "my_header.h"

 

void my_func() {} 

 

 

 

이런 코드가 있다면 보통 어떻게 컴파일 하는가 ?

 

# gcc main.c my_header.c -o main

 

아마 이런식으로 컴파일 해서 main 실행파일을 생성할 것이다.

아니면 리눅스 라이브러리 를 사용할 경우에는 ? 

 

여기서 하고싶은 말은 main.c 만자을 컴파일 해서는 완벽한 프로그램이 될수 없다는것이다. 즉 my_func를 알려줘야지 

제대로된 완벽한 프로그램이 될것이다

 

즉 이 알려주는 시점은 다양하지만(컴파일시, 실행시-즉 메모리에 로드시, 런타임시) 여튼 ELF는 

"나 아직 my_func 에 대해서 몰라 언젠지 몰라도 나 완벽해져야 되니까 알려죠"

이라믄서 .rel 섹션에다가 my_func를 적어두고 알려줘라고 즉 relocatable symbol들을 resolving reference 해달라고 적어둔다. 

 

후에 말하겠지만 링커스크립터가 돌면서 모르는 심볼(relocatable symbol)들을 알아낸다(resolving reference). 어디서 ?

다른 ELF 든지, 라이브러리든지....... 이래서 Linkable이라고 하는것이다.

 

자 그럼 여기서 궁금증이 생긴다. 대체 뭔 개소리야 ?

ELF란 실행 가능하고, 그리고 링킹 가능한 포맷이다. 이를 구체화 해보자

위에서 했던 명령을 다시 해보자 -save-temps 옵션을 덧붙여서

 

# gcc main.c my_header.c -o main -save-temps     이러면

 

my_header.h                       <- 헤더

main.c      my_header.c     <- 소스파일

main.i       my_header.i      <- 전처리된 파일

main.s      my_header.s      <- 어셈블된 파일

main.o      my_header.o     <- 링킹 가능한 바이너리 파일(ELF)

main                                       <- 실행 가능한 바이너리 파일(ELF)

이런 파일들이 생길것이다.

여기서 main.o, my_header.o, main 이 ELF 포맷 파일이고

main.o, my_header.o 가 Linkable 

main 이 Excutable 이다

 

 

정리하면 엘프는 세가지 경우의 수로 나뉜다

1. 재배치 가능한 파일(relocatable file) : 링킹 가능한 포맷

- relocatable symbol들을 symbol resolving reference 해야하는 바이너리

- 즉 링크를 쉽게 할 수 있는 코드 및 데이터와 함께실행 파일을 만들거나 공유 object파일을 생성하기위한 

  다른 object파일을 가지고 있는 형태의 파일

 

2. 실행 가능 파일(executable file) : 실행 가능한 포맷

- relocatable symbol들을 모두 symbol resolving reference해서 실행 가능한 바이너리

- 즉 실행을 위해서 적합한 프로그램을 가지는 파일로서, exec시스템 콜이 어떻게 프로세스의 이미지를 

  프로그램을 이용해서 만들지를 기술하는 파일이다.

 

3. 공유 object 파일(shared object file) : 실행 가능하면서 링킹가능한 포맷

- relocatable symbol들을 symbol resolving reference해서 실행 가능하지만,

  공유 혹은 동적 라이브러리를 실행시에 혹은 런타임에 링킹시켜야 하는 바이너리

- 즉 두개의 환경(context)상에서 링크에 적합한 형태의 코드와 데이터를 가지는 파일로서먼저 LD와 같은 

  링크 에디터가 이 파일과 함께다른 재배치 가능 파일과 공유 object파일을 처리해서 또하나의 object 

  파일을 생성해주게 되고이를 다시 동적 링커(dynamic linker)가 생성된 object 파일을 실행 가능 파일과 

  다른 공유 object파일을 묶어서 프로세스의 이미지를 생성해 주는 두 단계를 거치는 파일이다.

즉 결론부터 말하면 ELF는 두가지 VIEW를 가진다. 

이 뷰에따라 각 ELF는 여러가지 부분(section, segment, header, table) 들로 나뉜다. 

 

우선 이그림을 보자

 

 

A. ELF HEADER

- 이 부분은 Linkable이던 Excutable이던 무조건 가지고 있다.

- 엘프 헤더는 현재의 엘프에대한 정의를 한다.

- Program header table과 section header table의 위치를 결정한다

 

C. Section Header Table

링크 가능 파일에만 있다. 물론 공유 Object 파일에도 있다.

- 이 테이블의 엔트리는 섹션에 대한 구조체 즉 struct Elf32_shdr 이다.

- 이 테이블은 섹션들의 이름, 메모리, 시작주소(if loadable), 파일오프셋, 크기, 정렬방식, 표현방식 등을 정의한다

- 상세 사항은 http://minirighi.sourceforge.net/html/structelf32__shdr.html

- 나타날수 있는 모든 섹션

.bss                             : 초기화 되지않은 전역변수, ( == zero initialized)

.comment                    : 버전 컨트롤 정보

.data and .data1          : 초기화된 데이터

.debug                         : symbolic 디버깅 정보

.dynamic                      : 동적 링킹 정보

.dynstr                         : 동정 링킹에 필요한 string

.dynsym                       : 동적 링킹 심볼 테이블

.fini                              : 프로세스 종료시 사용될 코드

.got                             : Global Offset Table

.hash                           : 심볼 해쉬 테이블

.init                              : 프로세스 초기화시 사용될 코드

.interp                          : 프로그램 인터프리터의 path name

.line                             : 심볼릭 디버깅의 라인정보

.note                           : file note

.plt                              : Procedure linkage table

.rel.dyn                        : 'Relocation' 에 기술된 재배치 정보를 갖음. 이 재배치는 .dyn 섹션에 적용된다

.relname and .relaname          : Relocation Information

.rodata and .rodata1              : 읽기전용 데이터

.shstrtab                                : 섹션 이름

.strtab                                   : 심볼 테이블 엔트리와 관련된 이름

.symtab                                 : 심볼 테이블

.text                                      : 실행 코드

 

 

 

  즉 이러한 섹션들에 대한 정보가 링크 가능 파일에는 Section Header Table에 저장되고, 또한 실제 섹션들이 바이너리 상에 

  차례대로 위치하고 있다.

 

B. Program Header Table

- 실행가능 파일에만 있다. 물론 공유 Object 파일에도 있다.

- 이 테이블의 엔트리는 세그먼트에 대한 구조체 즉 struct Elf32_phdr 이다.

- 이 테이블은 세그먼트의 타입, 파일오스셋, 물리주소, 가상주소, 파일크기, 메모리 이미지 크기, 정렬방식등을 정의한다

- 상세 사항은 http://www.sco.com/developers/gabi/latest/ch5.pheader.html

 

- 즉 위에서 본 여러 Linkable 파일들의 섹션들을 링커가 돌면서 통합해주고, 이것들에 대한 정보를 이 테이블에 갖고 있는다.

 

 

 

 

즉 섹션과 세그먼트는 다른것이 아니라 유사한 섹션들을 모아 놓은것이 세그먼트이다.

그렇다면 왜 구분할까? 목적이 다르니까....

 

섹션 즉 링킹가능 파일은 링킹이 편리하게 되기 위해

(즉 실행가능 파일로 만들때 여러 링킹가능 파일의 섹션을 모아 하나의 큰 섹션으로 만듬)

 

세그먼트 즉 실행가능 파일은 exec 시스템콜에 의해, 다시말해 ELF-Loader에 의해 로드되기 혹은 링킹(라이브러리)

되기 쉽게 하기 위해.

 

 

결론적으로 말하면 ELF는 ABI(application Binary Interface)로써 실행과 링킹을 유용하게 하려고, 각 용도에 따라 섹션이나 세그먼트를 나누어 두고 프로그램의 실행을 원활하기 위해 사용하는 것이다.

 

 

 

Comments