Pintos Project 2에 process.c 파일에 load하는 과정이 있다.
이 과정에서 ELF 개념이 나온다.
Deep dive를 안하려고 했지만, 찾다보니 여기까지 와버렸다...
ELF가 무엇인지를 알아보자 !
🔗 ELF 형식의 파일
- 실행 파일
- 프로그램 실행 가능한 바이너리 파일
- 공유 라이브러리 파일
- 오브젝트 파일
🔗 ELF 형식이 아닌 파일
- 텍스트 파일
- 이미지 및 데이터 파일
file /bin/ls
- 위와 같은 Linux 명령에서 파일 형식 확인 가능
✨ELF 파일 특징
- 하나의 파일 안에 여러 섹션(섹션 테이블)으로 구성
- 실행에 필요한 모든 데이터를 하나의 파일 안에 포함
- 모듈화된 설계
- 실행 파일, 오브젝트 파일, 공유 라이브러리 등을 하나의 표준 형식으로 처리
- 프로그램 실행과 동적 링킹 수행 (ex : printf 함수 호출)
- 프로그램 실행 중 외부 라이브러리 로드하여 사용 가능
- 하나의 파일 안에서 프로그램을 구성하는 다양한 데이터를 구분하여 저장
- 파일 구조가 명확하고 효율적
- ELF파일은 명확하게 정의된 헤더와 섹션들로 구성
- 이 정보를 운영 체제와 링커, 로더가 효율적 사용
- 각 섹션은 목적 파일의 특징 데이터 영역과 유사한 역할 수행
✨ ELF 파일의 세그먼트 종류
- 텍스트 세그먼트
- 프로그램 실행 코드 포함
- 주로 읽기 전용
- 데이터 세그먼트
- 초기화된 전역 변수나 정적 변수가 저장
- BSS 세그먼트 (Block Started by Symbol)
- 초기화되지 않은 전역 변수나 정적 변수
- 실행 파일에 실제 데이터가 저장되지 않고 메모리 공간만 할당
- 읽기 전용 세그먼트
- 상수 데이터
- 읽기 전용으로 수정 불가능
✨ Linkable & Executable 요소들
Linkable 요소
- 역할
- 링킹 과정에서 사용되는 데이터
- 다른 오브젝트 파일이나 라이브러리와 결합합하여 하나의 실행 파일을 만듬
- 섹션 헤더 테이블
- 각 섹션에 대한 정보 저장
- 이 정보를 사용해 여러 오브젝트 파일 결합
- .test/.data/.bss 의 섹션들
- 심볼 테이블
- 함수와 변수등의 심볼 정보 저장
- 심볼 테이블을 사용해 오브젝트 파일 간의 함수 호출, 변수 참조 등을 연결
- 심볼 이름/주소/타입/크기 등
[심볼 테이블 동작 원리]
int a = 10;
void func() {
int b = 20;
}
Name Type Size Address Scope
----------------------------------------
a int 4 0x1000 Global
func void() - 0x2000 Global
b int 4 0x3000 Local (func)
- 컴파일러가 코드를 읽어서 위와 같은 심볼 테이블 생성
- 컴파일 단계에서는 컴파일러 내부에서 일시적 유지
- 이후 ELF 파일 같은 바이너리 파일에 심볼 정보 저장
- 재배치 테이블
- 심볼의 주소가 실행 시점에 결정되어야하는 경우
- 링커가 재배치 테이블을 참고하여 심볼의 상대 주소나 임시 주소 기록
- 동적 섹션
- 동적 링킹 시 필요한 정보 저장
Executable 요소
- 역할
- 실행 과정에서 사용되는 데이터
- 운영체제의 Loader(로더)가 프로그램을 메모리에 적재
- ELF 헤더
- 파일에 대한 메타 데이터 제공
- ELF 파일의 전체적인 속성과 구조 정의
- 실행 파일, 오브젝트 파일 , 공유 라이브러리 중 어떤 유형인지 판별
- 파일의 엔트리 포인트, 프로그램 헤더 테이블, 섹션 헤더 테이블 위치 기술
- Loader 가 ELF 파일을 인식하고 처리할 수 있도록 기본 정보 제공
- 프로그램 헤더 테이블
- 프로그램이 메모리에 어떻게 적재될지를 정의
- Loader는 프로그램 헤더 테이블을 읽어 ELF 파일의 각 세그먼트를 메모리에 매핑
- 각종 데이터 세그먼트
- 엔트리 포인트
- 프로그램의 시작 주소 (실행 파일에서 프로그램의 진입점)
- 로더가 이 위치로 제어를 넘겨 프로그램 실행
inode는 디스크에 저장된 데이터가 어디에 있고 어떤 속성을 가지는 알려주는 지도
ELF 헤더는 프로그램이 어떻게 메모리에 로드되고 실행될지 운영체제에게 알려주는 조리법
프로그램 헤더 테이블은 실행 파일의 세그먼트를 관리하는 데이터 구조
→ 프로그램 헤더는 조리 레시피의 준비단계처럼 어떤 재료 (코드/데이터)를 어느 위치 (메모리)에 배치하고 어떻게 활용할지를 정의
이정도만 알아도 코드를 이해하는데에는 지장이 없을 것 같다.
🔗 ELF 헤더
struct ELF64_hdr {
unsigned char e_ident[EI_NIDENT]; // 32bit or 64bit & 엔디안 방식
uint16_t e_type; // 파일 유형 (실행 파일, 오브젝트 파일, 동적 라이브러리)
uint16_t e_machine; // 아키텍처
uint32_t e_version; // ELF 형식의 버전
uint64_t e_entry; // 엔트리 포인트(프로그램 시작 주소)
uint64_t e_phoff; // 프로그램 헤터 테이블 시작 위치 (파일 내 오프셋)
uint64_t e_shoff; // 섹션 헤더 테이블의 시작 위치 (파일 내 오프셋)
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize; // 프로그램 헤더 엔트리 크기
uint16_t e_phnum; // 프로그램 헤더 엔트리 개수
uint16_t e_shentsize; // 섹션 헤더 엔트리 크기
uint16_t e_shnum; //// 섹션 헤더 엔트리 개수
uint16_t e_shstrndx;
};
- 아직 file system 구현 전이여서 필수적으로 알아야 하는 것은 아닌 것 같다.
- 그래도 이왕 보는거 잘 이해해보자
🔗 프로그램 헤더
struct ELF64_PHDR {
uint32_t p_type; // 세그먼트 타입 (Load,DYNAMIC,INTERP)
uint32_t p_flags; // 세그먼트 메모리 접근 권한
uint64_t p_offset; // ELF 파일 내에서 세그먼트가 시작하는 위치
uint64_t p_vaddr; // 세그먼트가 메모리에서 시작하는 가상 주소
uint64_t p_paddr; // 물리 주소
uint64_t p_filesz; // 파일에서 세그먼트의 크기
uint64_t p_memsz; // 메모리에서 세그먼트의 크기
uint64_t p_align; // 메모리와 파일의 정렬 기준
};
'크래프톤 정글' 카테고리의 다른 글
Pintos Project2 - User Program (커널/사용자 모드 이해하기) (0) | 2024.11.26 |
---|---|
Pintos Project2 - User Programs 키워드 (0) | 2024.11.21 |
Pintos Project1 - 스레드 전반적인 흐름 잡기 (0) | 2024.11.11 |
Pintos Project1 - Priority Scheduling (2) (0) | 2024.11.07 |
Pintos Project1 - Priority Scheduling (1) (2) | 2024.11.07 |