컴파일과 ELF 파일 구조

프로그램이 만들어지는 과정

- Source 코드를 Compile하여 Object File을 만들고, Linker로 Executable File을 만든 뒤, 우리가 사용하는 하드웨어(Memory)에 적재시켜 Process화 한다.
Compiler
- 사람이 이해할 수 있는 프로그래밍 언어로 작성된 소스 코드를 컴퓨터(CPU)가 이해할 수 있는 기계어로 표현된 Object 파일 로 변환
-
Object(e.g., o file)
- 컴퓨터가 이해할 수 있는 기계어로 구성된 파일
- 자체적으로 수행되지 않음
- 프로세스로 변환되기 위한 정보가 삽입되어야 함
- Relocatable Addresses(Relative Addresses)로 표현
- 주소 할당이 제대로 되지 않은 상태
- 상대주소로 표현된 instruction들의 집합
- 심볼들의 주소가 상대적인 값으로 표현되어 있음(ex, 시작 주소로부터 26바이트 지점)
Linker
- 관련된 여러 Object 파일들과 라이브러리들을 연결하여, 메모리로 로드 될 수 있는 하나의 Executable로 변환
-
Executable(e.g., exe file)
- 특정한 환경(OS)에서 수행될 수 있는 파일
- 프로세스로의 변환을 위한 Header, 작업 내용인 Text, 필요한 데이터인 Data를 포함한다.
- Absolute Addresses로 표현
- 컴파일러와 링커는 결과물이 수행될 OS와 CPU에 따라 다른 형태의 파일을 만든다.
Loader
- Executable을 실제 메모리에 올려주는 역할을 담당하는 운영 체제의 일부
-
동작 과정
- Executable의 Header(Shared Libraray)를 읽어, Text와 Data의 크기를 결정
- 프로그램을 위한 Address Space를 생성
- 실행 명령어와 Data들을 Executable로부터 생성한 Address Space로 복사
- 프로그램의 Argument들을 Stack으로 복사
- CPU 내 Register를 초기화하고, Start-up Routine으로 Jump
Object Files
Relocatable Object File(.o)
- Compile-time에 생성되는 바이너리 코드와 데이터로 이루어진 파일
- 다른 재배치 가능한 오브젝트 파일과 통합 가능
Executable Object File : 실행 가능한 파일
- 메모리에 직접 로딩되어 실행될 수 있는 바이너리 코드와 데이터로 이루어진 파일
Shared Object File (.so)
- Load-time이나 Run-time에 동적으로 메모리에 로드되고 링크될 수 있는 타입의 재배치 가능한 오브젝트 파일
- COFF(Common Object FIle format) : System V 계열 초기 Unix에서 사용
- PE(Portable Executable) : Windows NT계열에서 사용하는 COFF의 변종
- ELF(Executable and Liking Format) : Linux, Solaris와 같은 대부분의 Unix에서 사용

Object FIle 구조 분석
파일 헤더에는 해당 파일이 실행되기 위한 모든 정보가 있다.
- 메모리에 어떻게 적재되는가?
- 어디서부터 실행되어야 하는가?
- 실행에 필요한 라이브러리(.so 또는 .dll)은 어떠한 것이 있는가?
- 필요한 stack heap 메모리의 크기를 얼마로 할것인가?
오브젝트 파일(.o) 구조

실행 파일 구조

- 실행파일을 만들면
.text, .rodata, .data, .strtab, .symtab이 무조건 있다.
.bss영역은 프로세스가 될 때 0으로 초기화 되기 때문에 실행파일 자체에는 실제 데이터로 저장되지 않는다.
strip으로 .strtab, .symtab 섹션 삭제 가능하다.
- ELF 파일 사이즈가 줄어들지만 디버깅 하기 어려워짐
- 동적 링킹을 한다면
.interp, .dynsym, .dynstr, .dynamic영역이 추가로 생긴다.
심볼과 링킹
Symbol과 Symbol Resolution
모든 오브젝트파일에는 심볼 테이블을 가지고 있다.
- Global Symbol (다른 모듈에서 참조 가능) : 일반함수, 외부함수, 전역변수, 외부변수
- strong symbol : 함수, 초기화된 전역변수
- weak symbol : 초기화되지 않은 전역변수
- Local Symbol (해당 모듈에서만 참조) : 정적함수, 정적변수, 파일명
- 파일명 : 컴파일러가 오브젝트 파일을 생성할 때, 해당 오브젝트 파일이 어떤 소스 파일에서 컴파일되었는지 정보를 포함하기 위해 기록하는 심볼
심볼 해석(Symbol Resolution) 규칙
- 여러개의 strong symbol은 허락되지 않는다.
- strong symbol과 weak symbol이 주어질 때, strong symbol을 선택한다.
- 여러 개의 weak symbol이 있을 경우 아무거나 선택한다.
Symbol Resolution - 1

- 지역 변수는 해당 블록에서만 유효하고 다른 파일이나 함수에서 참조할 수 없으니 Symbol이 아니다.
- Strong 심볼인
add가 두 파일에서 존재하므로 링킹을 하면 에러가 난다.
Symbol Resolution - 2

sum에 static이 있어서 add.c에서만 접근 가능하다.(Local Symbol)
main.c'의 extern int sum;는 아무것도 참조하지 못해 undefined reference 오류가 난다.
Linking
- 실행파일에 정적 또는 동적으로 링크된 symbol과 symbol definition(symbol table)을 병합 및 재구성
Static Linking vs Dynamic Linking

- Library
- 이전에 작성한 코드의 집합
- 헤더 파일에서 참조하는 함수를 구현할 수 있게 해주는 이진 코드의 데이터베이스
static Linking
- 실행 가능한 파일을 만들 때 프로그램에서 사용하는 모든 라이브러리 모듈을 복사하는 방식
- Compile Time에 프로그램마다 static libraries를 linking
- file size ↑
- 내 코드의 크기는 작더라도 큰 라이브러리가 들어가 있음(중복 발생)
- 보안 좋음
- Dynamic Linking보다 빠름
- Compatibility issues X
Dynamic Linking
- Compile TIme에 linking 관련 정보만 부여
- run time에 shared libraries를 loading한 후 linking
- 라이브러리를 올릴 때 보안 위협 우려(바꿔치기)
Loading
- 실행 전에 모든 코드와 라이브러리를 메모리에 미리 로드하여 실행하는 방식
- 실행 중에 필요한 코드나 라이브러리를 해당 위치에서 가져와 메모리에 올리는 방식
출처