대부분 컴파일 시스템은 사용자를 대신해서 언어 전처리기, 컴파일러, 어셈블러, 링커를 필요에 따라 호출하는 컴파일러 드라이버를 제공한다.
앞서 C 프로그램이 컴파일되는 4단계를 배운 적 있을 거다.
(※ 안 봤다면... 다시 보고 오시길)
이 모든 작업을 컴파일러 하나가 혼자 다 하는 건 아니다.
실제로는 각 단계를 처리하는 별도의 도구(툴)들이 필요하다.
cppcc1asld이런 도구들을 한데 모아둔 대표적인 컴파일 시스템이 바로
GNU 컴파일 시스템이다.
그런데, 사용자 입장에서 이걸 직접 하나씩 호출한다고 생각해보자.
cpp → cc1 → as → ld...
매번 다 쓰기 너무 귀찮고 복잡하다. 😵
그래서 등장한 것이 바로 컴파일러 드라이버다.
GNU 컴파일 시스템에서는, 각 단계의 도구들을 대신 호출해주는 드라이버가 바로
gcc다.
덕분에 우리는 복잡한 명령어를 일일이 입력하지 않고도,
gcc 한 줄로 컴파일 과정을 간편하게 처리할 수 있는 것이다.
.o 파일 속 들여다보기.o 파일은 아직 실행할 수 없다.
링커가 나중에 연결해줄 걸 전제로 만들어진 "중간 산출물" 이다.
이를 "재배치 가능 목적파일"이라고 부른다.
링커에게 전달하기 위한 정보를 꽉꽉 채워넣기만한 구조이다.
위에서 부터 살펴보자.
[ ELF header ]
이 파일을 생성한 워드 크기와 시스템 바이트 순서를 나타낸다.
일종의 README 이다.
즉, 이 ELF 파일을 나중에 다른 컴퓨터에서 읽을 일이 생겼을 때,
그 시스템이 오해 없이 이 파일을 제대로 해석할 수 있게 해주는 정보가 담겨 있다.
WHY❓
나중에 다른 시스템(다른 컴퓨터)이 이 목적파일을 읽을 때, 오해없이 정확히 실행할 수 있도록 알려주기 위한 설명서이다.
[ .text ] 컴파일된 프로그램의 코드
[ .rodata ] 읽기 전용 데이터
[ .bss ] 초기화 안 된 전역 변수
.
.
등
[ Section header table ]
ELF 파일 안에 들어있는 각 섹션(.text, .data, .bss 등)들에 대한 "목차표"
정리하지 않고 꽉꽉 눌러담기만한 내부 구조를 파악할 수 있도록 정리해둔 참조표이다.
이전내용 참고 ➡️ CSAPP 7장 보기 전에 | 링커, 실행파일, 그리고 메모리
우리가 ./program이라고 실행하면,
운영체제(OS)와 로더가 힘을 합쳐서 이 실행파일을 메모리에 배치해준다.
그때 메모리 안에서는 아래 그림처럼 공간이 나뉜다.
아래에서부터 살펴보자.
[ Read-only code segment ] 실행파일로부터 복사됨
[ Read/write segment ] 전역 변수들이 저장됨
[ Run-time heap ] malloc, new 같은 동적 할당 메모리
[ Memory-mapped region ] 공유 라이브러리(libc.so, libm.so 등) 올라오는 공간
[ User stack ] 함수 호출할 때 쓰는 공간
[ Kernel memory ] 운영체제 전용 영역
📌 이걸 “런타임 메모리 이미지”라고 부른다.
프로그램이 실행되는 순간, 메모리에 배치되는 이 전체 모습을
우리는 "런타임 메모리 이미지(Runtime Memory Image)"라고 부른다.
프로세스마다 이 구조는 거의 동일하다
운영체제가 이 구조에 맞춰 실행파일을 배치해주기 때문이다.
❓왜 이렇게 반대로 자랄까
→ 서로 충돌이 안 나게 하기 위해서이다.
스택은 함수 호출하면서 계속 새 정보가 쌓이니까 위에서 아래로
힙은 동적 할당할 때마다 새로 확보되니까 아래에서 위로
둘이 충돌하지 않게 중간에 mmap 영역이 완충 역할을 해준다.
짱이에요...!!