[TIL] WEEK05 - CS:APP 7장

woo__j·2024년 4월 23일
0

TIL - Today I Learned

목록 보기
18/23

링킹(Linking)
: 여러개의 코드+데이터를 모아 연결해, 메모리에 로드/실행될 수 있는 하나의 파일로 만드는 작업
컴파일 시 수행되며 ‘소스코드->머신코드’ 번역을 수행
링킹의 역할
독립적인 컴파일을 가능하게 함
큰 규모의 응용 프로그램을 한 개의 소스파일로 구성하지만, 별도로 수정/컴파일 할 수 있는 작은 모듈로 나눔

7.1 컴파일러 드라이버

[실행파일 생성 과정]

#전처리
cpp main.c main.i 
cpp sum.c sum.i
#컴파일
cc1 main.i -o main.s
cc1 sum.i -o sum.s
#어셈블
as main.s -o main.o
as sum.s -o sum.o
#링킹
ld -o pname main.o sum.o

-> 본래 실행파일 생성 과정들을 위처럼 각각 수동으로 진행할 수 있으나, 컴파일 시스템은 사용자를 대신해 전처리기/컴파일러/어셈블러/링커를 필요에 따라 호출하는 ‘컴파일러 드라이버’를 제공한다.
즉, 컴파일러 드라이버로 이 전체 과정을 실행할 수 있음

gcc -o pname main.c sum.c

[컴파일러 드라이버]
1. 전처리기 (소스파일 .c -> ASCII 중간 파일 .i)
2. 컴파일러 (.i -> ASCII 어셈블리 언어 파일 .s)
3. 어셈블러 (.s -> 재배치 가능한 바이너리 목적파일 .o)
4. 링커 : 필요한 시스템 목적파일들로 ‘실행가능 목적파일’ 생성 (.o 파일들 연결)

7.4 재배치 가능 목적파일

[목적파일의 형태]
1. 재배치 가능 목적파일
2. 실행 가능 목적파일
3. 공유 목적파일

목적파일들은 특정 목적파일 형식에 따라 구성되며, 이는 시스템마다 다름
x86-64 리눅스와 유닉스 시스템들은 ELF 포맷을 사용한다

[ELF 파일 구조]
1. ELF Header: 파일의 시작 부분에 위치, 파일이 ELF 형식임을 나타내는 시그니처/기계 아키텍처/엔트리 포인트(프로그램이 실행될 때 첫 번째 인스트럭션의 주소) 등 파일에 대한 메타데이터를 포함
2. Program Header Table: 메모리에 로드될 때 각 세그먼트를 어떻게 처리할 지에 대한 정보를 포함
3. Section Header Table: 파일 내 섹션들의 배열과 각 섹션에 대한 정보를 포함, 디버깅이나 추가적인 정보 처리에 사용

[ELF 재배치 가능 목적파일 포맷]

  • ELF header: 파일을 생성한 워드 크기 & 시스템의 바이트 순서
  • .text: 컴파일된 프로그램의 머신코드
  • .rodata: read only data, 읽기 전용 데이터를 위한 섹션 ex) printf의 문자열 상수나 switch문의 점프 테이블 같은 ‘변경되지 않는 데이터’
  • .data: 0이 아닌 값으로 초기화된 전역 변수나 정적(static) 변수들을 저장하는 섹션
  • .bss: 초기화되지 않았거나, 0으로 초기화된 전역변수 & 정적 변수
  • .symtab: 프로그램에서 정의되고 참조되는 전역변수와 함수에 대한 정보를 가지고 있는 심볼 테이블
  • .rel.text: 링커가 이 목적파일을 다른 파일과 연결할 때 수정(재배치)해야 하는 .text 섹션 내 위치 리스트
  • .rel.data: 이 모듈에 의해 정의/참조되는 전역변수들에 대한 재배치 정보
  • .debug: 프로그램의 디버깅 정보를 포함하는 섹션, 디버거가 프로그램의 소스코드와 실행코드를 매핑할 수 있게 하는 정보를 담음
  • .line: 소스코드의 각 줄이 실행파일의 어떤 부분과 대응되는 지 설명하는 정보를 포함
  • .strtab: .strtab과 .debug 섹션들 내 심볼 테이블과 섹션 헤더들에 있는 섹션 이름들을 위한 문자열 테이블
  • Section header table: 여러가지 섹션들의 위치와 크기를 나타냄

[섹션의 역할과 중요성]
각 섹션은 프로그램을 구성하는 데 필수적인 다양한 정보를 담고 있다.
.text의 명령어들은 CPU가 실행할 프로그램의 실제 코드를 포함
.data & .rodata는 프로그램이 사용하는 데이터를 저장
.bss는 변수가 프로그램 실행 시 메모리에 할당될 공간을 예약해두는 데 사용된다.
심볼 테이블과 재배치 정보가 들어있는 섹션들: 링커가 다양한 오브젝트 파일들을 하나의 실행 파일로 결합하는데 필요한 정보를 제공 -> 실행파일이 시스템의 메모리에 적절히 매핑되고, 함수와 변수들이 올바른 주소를 참조할 수 있도록 함

7.8 실행 가능한 목적파일

실행 가능 목적파일은 컴파일러와 링커에 의해 생성된 파일로, 운영체제가 직접 메모리에 로드해 실행할 수 있는 파일 형식
포맷은 재배치 가능한 목적파일의 포맷과 유사

[차이]
.init 추가
.text & .rodata & .data 섹션들이 각자의 최종 런타임 메모리 주소로 재배치되었다는 점
.rel 제외

[메모리 로딩 & 프로그램 실행]
프로그램 실행 시, 운영체제의 로더는 ELF 파일의 프로그램 헤더 테이블을 참조해 각 세그먼트를 적절한 메모리 주소로 로드한다. 각 세그먼트는 특정 권한(읽기/쓰기/실행)과 함께 메모리에 매핑된다.
ELF의 ‘엔트리 포인트’는 프로그램의 시작 지점을 가리키는 메모리 주소로, 로더는 이 주소로 제어를 넘겨 프로그램의 실행을 시작한다.

7.9 실행 가능 목적파일의 로딩

실행가능 목적파일을 실행하기 위해 리눅스 쉘에 ./pname을 입력할 시, 운영체제는 명령어를 해석하고 실행파일을 찾아 ‘로드’하는 과정을 거치게 된다.그럼 pname이라는 프로그램이 실행되는데, 이 때 운영체제의 로더가 실행파일의 헤더를 확인하고 필요한 메모리를 설정한다.

로더는 디스크로부터 실행가능 목적파일 내 코드와 데이터를 메모리로 복사하고, 프로그램의 첫 번째 인스트럭션, 즉 ‘엔트리 포인트’로 점프해서 프로그램을 실행 -> 이처럼 프로그램을 메모리로 복사/실행하는 과정 = 로딩

! 모든 실행 중인 리눅스 프로그램은 런타임 메모리를 가진다.

[x86-64 런타임 메모리]
x86-64 리눅스 시스템에서 코드 세그먼트는 주소 0x400000에서 시작하고, 뒤이어 데이터 세그먼트가 온다.
런타임 힙(heap)은 데이터 세그먼트 다음에 따라오고, malloc 라이브러리를 호출해서 위로 성장한다.
이 다음엔 공유 모듈들을 위한 예약된 영역이 존재한다.
사용자 스택은 가장 큰 합법적 사용자 주소(2^48 -1) 아래에서 시작해, 더 작은 메모리 주소 방향인 아래로 성장한다.
스택 위 영역은 운영체제의 메모리 상주 부분인 커널의 코드와 데이터를 위해 예약되어 있다.

[구성요소]

  • Text Segment: 읽기 전용 실행 코드 포함
  • Data Segment: 전역 변수 등 초기화된 데이터 포함
  • bss Segment: 초기화되지 않은 데이터 포함
  • Heap: 동적 메모리 할당을 위한 공간, 프로그램 실행 중 malloc 등 함수로 확장 가능
  • Stack: 함수의 지역변수/리턴 주소 등을 저장하고, 함수 호출 시 동적으로 사용되는 공간

링커는 런타임 주소를 스택, 공유 라이브러리, 힙 세그먼트에 할당할 때, ASLR(주소 공간 배치 랜덤화)를 사용한다. 매번 다른 주소에 배치해 보안을 강화하는 것. 이들의 영역 위치가 프로그램이 실행될 때마다 매번 변경될지라도 이들의 상대적인 위치는 동일하다.

로더가 돌아갈 때 위와 유사한 메모리를 생성한다. 실행파일 내부의 프로그램 헤더 테이블에 따라 실행파일의 덩어리를 코드와 데이터 세그먼트로 복사한다.
다음으로, 로더는 프로그램의 엔트리 포인트로 점프해 항상 _start 함수의 주소가 된다.
이는 오브젝트 파일에서 시작되고, 이후 libc_start_main 함수를 호출해 실행 환경을 초기화 하고, 사용자 정의 main 함수를 호출한다. (+리턴 값 처리/필요한 경우 커널로 제어권 넘겨줌)

0개의 댓글

관련 채용 정보