운영체제 기본/Linux/ELF 로딩/ELF 세그먼트와 주소 공간: 두 판 사이의 차이
BIOS GRUB 부팅 경로 보강과 각론 형식 통일 |
문장형 절 제목을 직관적인 명사형으로 통일하고 기존 절 링크 보존 |
||
| 5번째 줄: | 5번째 줄: | ||
여기서는 먼저 정적 링크된 <code>ET_EXEC</code> 실행 파일을 기준으로 설명한다. <code>ET_EXEC</code>는 ELF 파일의 종류이고, 아래에서 다루는 <code>PT_LOAD</code>는 그 파일 안의 프로그램 헤더 종류이다. 기준 구현은 Linux v6.12 x86-64이다. | 여기서는 먼저 정적 링크된 <code>ET_EXEC</code> 실행 파일을 기준으로 설명한다. <code>ET_EXEC</code>는 ELF 파일의 종류이고, 아래에서 다루는 <code>PT_LOAD</code>는 그 파일 안의 프로그램 헤더 종류이다. 기준 구현은 Linux v6.12 x86-64이다. | ||
== ELF | <span id="ELF_헤더에서_배치_정보를_찾는다"></span> | ||
== ELF 헤더와 배치 정보 == | |||
ELF 파일의 맨 앞에는 ELF 헤더가 있다. 커널의 <code>load_elf_binary()</code>는 이 파일이 지원하는 ELF 형식과 아키텍처인지 확인하고, 헤더가 가리키는 프로그램 헤더 테이블을 읽는다. ELF 헤더의 <code>e_entry</code>는 실행을 시작할 주소이며, <code>e_phoff</code>와 <code>e_phnum</code>은 프로그램 헤더 테이블의 파일 위치와 항목 수를 알려 준다.<ref name="elf">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_elf.c?h=v6.12 Linux v6.12, fs/binfmt_elf.c], <code>load_elf_binary()</code>, <code>load_elf_phdrs()</code>, <code>elf_map()</code>, <code>elf_load()</code>.</ref> | ELF 파일의 맨 앞에는 ELF 헤더가 있다. 커널의 <code>load_elf_binary()</code>는 이 파일이 지원하는 ELF 형식과 아키텍처인지 확인하고, 헤더가 가리키는 프로그램 헤더 테이블을 읽는다. ELF 헤더의 <code>e_entry</code>는 실행을 시작할 주소이며, <code>e_phoff</code>와 <code>e_phnum</code>은 프로그램 헤더 테이블의 파일 위치와 항목 수를 알려 준다.<ref name="elf">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_elf.c?h=v6.12 Linux v6.12, fs/binfmt_elf.c], <code>load_elf_binary()</code>, <code>load_elf_phdrs()</code>, <code>elf_map()</code>, <code>elf_load()</code>.</ref> | ||
| 36번째 줄: | 36번째 줄: | ||
이때 섹션과 세그먼트를 구분해야 한다. <code>.text</code>, <code>.data</code>, <code>.bss</code> 같은 섹션은 컴파일·링크 과정에서 사용하는 구분이다. 실행 시 커널이 배치의 기준으로 사용하는 것은 프로그램 헤더의 세그먼트이다. 하나의 세그먼트에 여러 섹션이 포함될 수도 있으며, 커널이 <code>.text</code>라는 이름을 찾아 복사하는 것은 아니다. | 이때 섹션과 세그먼트를 구분해야 한다. <code>.text</code>, <code>.data</code>, <code>.bss</code> 같은 섹션은 컴파일·링크 과정에서 사용하는 구분이다. 실행 시 커널이 배치의 기준으로 사용하는 것은 프로그램 헤더의 세그먼트이다. 하나의 세그먼트에 여러 섹션이 포함될 수도 있으며, 커널이 <code>.text</code>라는 이름을 찾아 복사하는 것은 아니다. | ||
== | <span id="PT_LOAD는_파일과_가상_주소를_연결한다"></span> | ||
== PT_LOAD와 세그먼트 매핑 == | |||
<code>PT_LOAD</code>의 핵심 필드는 다음과 같다. | <code>PT_LOAD</code>의 핵심 필드는 다음과 같다. | ||
| 77번째 줄: | 77번째 줄: | ||
실제 메모리 매핑은 페이지 단위이므로 시작과 끝을 페이지 경계에 맞춰 처리한다. 파일 오프셋과 가상 주소의 페이지 내 위치가 맞아야 같은 바이트가 올바르게 대응된다. Linux의 <code>elf_map()</code>은 이 정렬을 반영해 <code>vm_mmap()</code>으로 파일 매핑을 만든다.<ref name="elf" /> | 실제 메모리 매핑은 페이지 단위이므로 시작과 끝을 페이지 경계에 맞춰 처리한다. 파일 오프셋과 가상 주소의 페이지 내 위치가 맞아야 같은 바이트가 올바르게 대응된다. Linux의 <code>elf_map()</code>은 이 정렬을 반영해 <code>vm_mmap()</code>으로 파일 매핑을 만든다.<ref name="elf" /> | ||
== | <span id="파일에_없는_BSS는_0으로_준비한다"></span> | ||
== BSS의 0 초기화 == | |||
초기값이 0인 전역·정적 변수에 필요한 공간을 파일에 모두 0으로 저장할 필요는 없다. <code>p_memsz</code>를 <code>p_filesz</code>보다 크게 두면 커널은 추가 구간을 0으로 준비한다. 흔히 BSS라고 부르는 부분이며, 커널은 섹션 이름이 아니라 이 두 크기의 차이를 사용한다. | 초기값이 0인 전역·정적 변수에 필요한 공간을 파일에 모두 0으로 저장할 필요는 없다. <code>p_memsz</code>를 <code>p_filesz</code>보다 크게 두면 커널은 추가 구간을 0으로 준비한다. 흔히 BSS라고 부르는 부분이며, 커널은 섹션 이름이 아니라 이 두 크기의 차이를 사용한다. | ||
| 100번째 줄: | 100번째 줄: | ||
이제 실행 파일의 코드와 데이터를 사용할 주소 공간이 준비되었다. 다음은 [[운영체제 기본/Linux의 ELF 로딩/초기 스택과 사용자 모드 진입|초기 스택과 사용자 모드 진입]]에서 인자와 스택을 만들고 CPU가 실행을 시작할 상태를 준비하는 과정이다. | 이제 실행 파일의 코드와 데이터를 사용할 주소 공간이 준비되었다. 다음은 [[운영체제 기본/Linux의 ELF 로딩/초기 스택과 사용자 모드 진입|초기 스택과 사용자 모드 진입]]에서 인자와 스택을 만들고 CPU가 실행을 시작할 상태를 준비하는 과정이다. | ||
== | <span id="다른_주소에_배치하는_경우"></span> | ||
== load_bias와 적재 주소 == | |||
지금까지는 <code>ET_EXEC</code>의 지정된 가상 주소를 그대로 사용하는 경우를 설명했다. <code>ET_DYN</code> 형식은 실행 시 다른 기준 주소에 배치할 수 있으며, 이때 세그먼트 주소와 진입 주소에 공통으로 더하는 값이 <code>load_bias</code>이다. 실행할 때마다 배치 주소를 달리하는 ASLR에도 이 방식이 사용된다. 다만 다른 위치에 배치할 수 있다는 성질이 ASLR에만 쓰이는 것은 아니다. | 지금까지는 <code>ET_EXEC</code>의 지정된 가상 주소를 그대로 사용하는 경우를 설명했다. <code>ET_DYN</code> 형식은 실행 시 다른 기준 주소에 배치할 수 있으며, 이때 세그먼트 주소와 진입 주소에 공통으로 더하는 값이 <code>load_bias</code>이다. 실행할 때마다 배치 주소를 달리하는 ASLR에도 이 방식이 사용된다. 다만 다른 위치에 배치할 수 있다는 성질이 ASLR에만 쓰이는 것은 아니다. | ||