7.8 실행 객체 파일 (Executable Object Files)
링커는 여러 개의 목적 파일을 하나의 실행 객체 파일(executable object file)로 병합한다. 이 파일은 이제 디스크에서 메모리로 로드되어 바로 실행할 수 있는 상태이다. 즉, 소스 코드로 시작된 프로그램이 최종 실행 가능한 이진 파일로 완성되는 지점이다.
ELF 실행 파일 구조 요약
ELF 실행 파일은 여러 부분으로 나뉘며, 각 부분은 특정한 기능을 담당한다. 대표적인 구성은 아래와 같다 (그림 7.13 기준):
주요 구성 요소:
- ELF 헤더: 전체 포맷에 대한 정보를 제공
- 섹션 헤더 테이블: .text, .data 등 객체 파일 섹션에 대한 설명
- 세그먼트 헤더 테이블(Program Header Table): 섹션을 런타임 메모리 세그먼트로 매핑
주요 섹션:
- .text: 명령어 (기계어) 코드
- .rodata: 읽기 전용 데이터 (예: 문자열 상수)
- .data: 초기화된 전역 변수
- .bss: 초기화되지 않은 전역 변수 (파일에는 없음, 런타임에 메모리 할당)
- .symtab, .debug, .line: 디버깅과 심볼 정보를 위한 메타데이터 (실행 시에는 메모리에 로드되지 않음)
ELF 세그먼트 구성
실행 파일은 메모리에 연속적인 세그먼트로 매핑된다. 예시를 보면:
- 코드 세그먼트: 0x400000부터 시작, .init, .text, .rodata 포함, 읽기/실행 허용
- 데이터 세그먼트: 0x600df8부터 시작, .data 포함, 읽기/쓰기 허용
이 정보는 ELF 파일의 프로그램 헤더 테이블에 기록되어 있으며, 로더가 이를 사용하여 메모리에 올릴 때 참조한다.
7.9 실행 객체 파일 로딩 (Loading Executable Object Files)
프로그램 실행 과정
리눅스 쉘에서 실행파일 prog를 실행하고자 할 때 다음과 같이 명령을 입력한다.
linux> ./prog
- prog가 셸 내부 명령이 아니라면, 셸은 이것이 실행 객체 파일이라고 가정한다.
- 셸은 로더(loader)라는 운영체제의 메모리 상주 코드에 제어를 넘긴다.
- 로더는 prog의 코드와 데이터를 디스크에서 메모리로 복사하고, 프로그램의 시작 지점(entry point)으로 점프하여 실행을 시작한다.
- 이 전체 과정을 로딩(loading)이라고 부른다.
execve 함수
- execve 함수는 프로그램을 로드하고 실행하는 시스템 호출이다.
- 내부적으로는:
- 기존 프로세스 메모리를 초기화하고
- 실행 파일의 내용을 새로운 코드, 데이터, 힙, 스택으로 채우며
- 프로그램의 main 함수 진입 지점으로 점프한다.
메모리 구조
실행된 프로그램은 다음과 같은 런타임 메모리 이미지(runtime memory image)를 가진다:
- 코드 세그먼트 (.text): 보통 주소 0x400000에서 시작, 읽기/실행 권한
- 데이터 세그먼트 (.data, .bss): 코드 바로 뒤, 읽기/쓰기 권한
- 힙(Heap): 동적 메모리 할당용, malloc에 의해 위쪽으로 확장
- 공유 라이브러리 영역
- 스택(Stack): 주소 공간의 가장 위에서 시작, 아래쪽으로 성장
이 레이아웃은 가독성을 위해 단순화된 것이고, 실제로는 정렬 제약(alignment) 및 주소 공간 배치 무작위화(ASLR)로 인해 여유 공간이 삽입되기도 한다.
정렬 조건
ELF 파일을 메모리에 효율적으로 매핑하기 위해, 로더는 각 세그먼트를 아래 조건을 만족하게 배치한다.
vaddr mod align = offset mod align
예: .data 세그먼트가 0x600df8에 위치한다면, 파일 내 오프셋도 0xdf8이어야 정렬 조건을 만족한다. 이는 페이지 기반 가상 메모리 시스템의 효율적인 전송을 위한 최적화다.
7.10 동적 라이브러리를 이용한 동적 링킹
정적 라이브러리의 한계
정적 라이브러리(static library)는 유용하지만 몇가지 단점이 있다.
- 라이브러리를 수정하면 프로그램을 다시 링크해야 함
- 자주 쓰이는 함수 (예: printf, scanf)가 각 실행 파일에 중복 저장되어 메모리 낭비
- 시스템에서 수백 개의 프로세스가 동일한 코드 복사본을 사용 → 메모리 낭비 심화
동적 라이브러리란?
공유 라이브러리(shared library)는 프로그램 실행 시 동적으로 링크되는 객체 모듈이다. 이 과정을 동적 링킹(dynamic linking)이라고 하며, 이를 수행하는 프로그램은 동적 링커(dynamic linker)라고 불린다.
- 리눅스에서는 보통 .so (shared object) 확장자를 가짐
- 윈도우에서는 .dll (dynamic link library)
공유의 두가지 의미
- 파일 시스템 수준
- 특정 라이브러리는 시스템에 하나의 .so 파일만 존재
- 이 파일을 참조하는 모든 실행 파일은 코드와 데이터를 공유
- 실행 시간 메모리 수준
- 실행 중인 여러 프로세스는 .text 섹션을 공유 메모리로 사용
- 코드가 메모리에 한 번만 로드되고 여러 프로세스가 함께 사용
이로 인해 메모리 절약, 업데이트 용이성, 빠른 실행이 가능해진다.
예시: 동적 링킹을 사용한 실행 파일 만들기
linux> gcc -shared -fpic -o libvector.so addvec.c multvec.c
- -shared: 공유 라이브러리 생성
- -fpic: 위치 독립 코드(PIC) 생성
linux> gcc -o prog2l main2.c ./libvector.so
- 실행 파일 prog2l 생성, libvector.so와 동적으로 연결됨
- 이 시점에서는 실제 코드나 데이터는 prog2l에 복사되지 않음
실행 시:
- ELF 파일 내 .interp 섹션이 동적 링커 경로 (ld-linux.so)를 명시
- 로더가 동적 링커를 실행해 .so 파일의 텍스트와 데이터를 메모리에 로드
- 미해결 심볼들을 .so 파일에서 찾아 재배치
'크래프톤 정글 (컴퓨터 시스템: CSAPP) > 7장 링커' 카테고리의 다른 글
컴퓨터 시스템 : CSAPP 7장 정리 - 7.13 ~ 7.15 (0) | 2025.04.18 |
---|---|
컴퓨터 시스템 : CSAPP 7장 정리 - 7.11 ~ 7.12 (0) | 2025.04.18 |
컴퓨터 시스템 : CSAPP 7장 정리 - 7.7 재배치 (0) | 2025.04.18 |
컴퓨터 시스템 : CSAPP 7장 정리 - 7.6 심볼 결합 (0) | 2025.04.18 |
컴퓨터 시스템 : CSAPP 7장 정리 - 7.1 ~ 7.5 (0) | 2025.04.17 |