실행가능한 목적파일

mydailylogs
|2022. 12. 3. 07:58

앞선 글에서 어떻게 링커가 다수의 목적 모듈을 하나의 실행 간으 목적파일로 합치는지 살펴보았다. 우리의 C 프로그램은 아스키 텍스트 파일로 일생을 시작해서 프로그램을 메모리에 로드하고 실행하는데 필요한 모든 정보를 포함하는 하나의 바이너리 파일로 변환되었다. 

그림 1 전형적인 실행가능한 ELF 목적파일

위 그림은 전형적인 ELF 파일에 있는 정보들의 종류를 요약한 것이다. 실행 가능 목적파일의 포맷은 재배치가능한 목적파일의 포맷과 유사하다. ELF 헤더는 우선 이 파일의 전체적인 포맷을 설명한다. 또한 프로그램이 실행될 경우 첫 번째 인스트럭션의 주소인 프로그램 엔트리 포인트를 포함한다. .text, .rodata, .data 섹션들은 이들 섹션들이 각자의 최종 런타임 주소로 재배치되었다는 점을 제외하고는 재배치 가능 목적파일에 있는 섹션들과 유사하다. .init 섹션은 _init이라는 작은 함수를 정의하는데, 이것은 프로그램의 초기화 코드에서 호출한다. 실행파일이 완전히 링크되었기 때문에 여기에서는 .rel 섹션이 필요하지 않다.

실행가능 목적파일의 로딩

그림 2 리눅스 런타임 메모리 이미지

실행가능 목적파일 p를 실행하기 위해서, Unix 쉘의 명령줄에는 다음과 같은 명령어를 입력한다.

> ./p

p가 내장 쉘 명령어에 대응되지 않기 때문에 쉘에서는 해당파일을 실행 가능한 목적파일이라고 가정할 것이다. 쉘은 로더라고 알려진 메모리 상주 운영체제 코드를 호출하여 이 프로그램을 실행한다. 모든 Unix 프로그램은 execve 함수를 호출하여 로더를 호출할 수 있으며, 로더의 동작은 이후 다시 자세히 설명하도록 하겠다. 간단히 설명하자면 로더는 디스크로부터 실행가능한 목적파일 내의 코드와 데이터를 메모리로 복사하고 이 프로그램의 첫 번째 인스트럭션, 즉 엔트리 포인트로 점프해서 프로그램을 실행한다. 이와 같이 프로그램을 메모리로 복사하고 실행하는 과정을 로딩이라고 부른다.

모든 Unix 프로그램은위와 같은 런타임 메모리 이미지를 가진다. 32 비트 리눅스 시스템에서 코드 세그먼트는 0x08048000 에서 시작한다. 데이터 세그먼트가 다음 4 KB 정렬된 주소에 배치되며 이후 런타임 힙이 위치한다. 또한 공유 라이브러리를 위해 예약된 세그먼트가 존재한다. 사용자 스택은 항상 가장 큰 사용자 주소에서 시작해서 아래로 성장한다. 스택의 위에서 시작하는 세그먼트는 커널이라고 알려진 운영체제의 메모리 상주 부분에 있는 코드와 데이터를 위해 예약되어 있다.

로더는 실제로 어떻게 동작하는가?

Unix 시스템에서 각 프로그램은 자신의 가상 주소공간을 갖는 프로세스의 컨텍스트에서 실행된다. 쉘이 프로그램을 실행할 때 부모인 쉘 프로세스는 부모와 자신의 복제인 자식 프로세스를 fork 한다. 자식 프로세스는 execve 시스템 콜을 통해서 로더를 호출한다. 로더는 자식의 기존 가상메모리 세그먼트를 삭제하고 새로운 코드, 데이터, 힙, 스택 세그먼트를 생성한다. 새로운 스택과 힙 세그먼트들은 0으로 초기화된다. 새로운 코드와 데이터 세그먼트들은 가상 주소공간 페이지들을 실행파일의 패이지 크기 덩어리들로 매핑시켜서 실행파일의 내용으로 초기화된다. 마지막으로 로더는 _start 주소로 점프하여 이는 궁극적으로 main 루틴을 호출한다. 헤더 정보와는 별도로, 로딩하는 동안에 디스크에서 메모리로의 데이터 복사는 일어나지 않는다. 

 

'CS' 카테고리의 다른 글

IPv4에서 IPv6로의 전환 매커니즘  (0) 2023.08.06
정적 라이브러리  (0) 2022.12.05
Linking(링킹) 소개  (0) 2022.12.02
시간 지역성을 위한 캐시 재배치  (0) 2022.12.01
공간 지역성을 높이기 위한 루프 재배치  (2) 2022.12.01