미완)크래프톤정글5주차 - CSAPP 7장

김태성·2024년 2월 11일
0
post-thumbnail

CSAPP 7장 링커

이번장은 여러개의 코드와 데이터를연결하여 로드/실행이 가능한 파일로 만드는 작업인 '링킹'에 관한 내용이다.

왜 링킹에 대해서 배워야 하는가?

  • 링커를 이해하면 큰 프로그램을 작성하는데 도움이 된다. 링커의 에러가 발생할때 왜 에러가 뜨는지, 링커가 어떻게 참조를 하는지에 대해 모른다면 시간이 많이 소모될 것이다.

  • 위험한 프로그래밍 에러를 피할 수 있다. 예를 들어, 전역변수를 중복해서 쓰면 경고 없이 링커를 통과할 수 있지만, 런타임 동작에 큰 영향을 줘 디버깅 하는 것 조차도 어려워진다.

  • 링킹을 이해하면 언어의 변수 영역 규칙이 어떻게 구현되었는지 이해하는데 도움을 준다. 예를들어서, 전역변수와 지역변수의 차이를 무엇인가? 등이 있다.

  • 링킹에 대해서 이해하면 다른 중요한 시스템 개념을 이해할 수 있다.링커가 만든 실행 가능 객체 파일은 로딩/프로그램 실행 같은 중요한 시스템 함수, 가상메모리, 페이징, 메모리 매핑에서 중요한 역할을 한다.

  • 공유 라이브러리에 대해 이해 할 수 있다.

이번장은 링커에 대한 설명을 정말 세세하게 다루기 때문에 챕터는 15까지 있지만, 페이지 수는 40페이지정도 된다.

7-1 컴파일 드라이버

.c 파일을 translator(cpp,cc1,as)를 통해 .o 파일로 번역한 후, 링커를 통해 실행가능한 prog파일을 형성하게 된다.

7-2 정적연결

7-2 부터 7-9까지의 내용은 정적 링킹에 관한 설명이다.

실행파일을 만들기 위해서 링커는 두 가지 주요 작업을 수행해야 한다.

  • 심볼 해석 : 목적파일들은 심볼들을 정의하고 참조하며 여기서 각 심볼은 함수, 전역변수, 또는 정적변수에 대응된다.
    심볼이 뭔지 몰라서 따로 뒤져보니까 다음과 같이 나왔다.
    symbol is the corresponding entity of most user-defined variables, function names, mangled with namespace, class/struct/name, and so on
    (출처 : https://developer.ibm.com/articles/au-aix-symbol-visibility/)
    유저가 정의한 변수, 함수이름, namespace(c++), 클래스/스트럭쳐/이름 등과 같은 것임.

  • 재배치 : 컴파일러와 어셈블러들은 코드와 데이터 섹션들을 생성한다. 링커는 이 섹션들을 각 심볼의 정의와 연결시켜 재배치 하며, 이 심볼에 향하는 모든 참조들을 갱신시킨다.

7-3 목적파일

! 목적파일 = 컴파일러나 어셈블러가 소스 코드 파일을 컴파일 또는 어셈블해서 생성하는 파일

목적파일은 세 가지 형태가 있다.

  • 재배치 가능 목적파일 : 실행가능 목적파일을 생성하기 위해 다른 재구성가능 목적파일들과 결합 될 수 있는 2진코드와 데이터를 가짐.

  • 실행가능 목적파일 : 메모리에 직접 복사/실행이 가능한 형태로 2진코드와 데이터를 가짐

  • 공유 목적파일 : 로드/런 타임 시 동적으로 링크되고, 메모리에 로드 될 수 있는 특수한 유형의 재배치가능 목적파일

7-4 재배치 가능 목적파일


ELF 헤더는 이 파일을 생성한 워드 크기/시스템 바이트 순서를 나타내는 16바이트 배열로 시작한다.
또한 링커가 목적파일을 분석/해석 하도록 하는 정보를 포함하고 있다.
ELF 재배치 가능 목적파일은 다음과 같은 섹션들을 포함한다.

  • .text : 컴파일된 프로그램의 기계어

  • .rodata printf : switch문의 점프 테이블과 같은 읽기 허용 데이터

  • .data : ㅊ초기화된 C 전역변수 및 정적변수. C 지역변수들은 런타임 스택에 저장되며, data나 .bss 섹션에는 나타나지 않는다.

  • .bss : 초기화되지 않은 C 전역변수와 정적변수 그리고 0으로 초기화된 전역변수 및 정적변수. 실제 공간을 차지하지는 않고 단순히 위치만 표시한다.

  • .symtab : 프로그램에서 정의되고 참조되는 전역변수들과 함수에 대한 정보를 가지고 있는 심볼테이블.

  • .real.text : 링커가 이 목적파일과 다른 파일을 연결할 때 수정되어야 하는 .text 섹션 내 위치 리스트.

  • .real.data : 이 모듈에 의해 정의되거나 참조되는 전역변수들에 대한 재배치 정보.

  • .debug : 디버깅 심볼 테이블.

  • .line : 최초 C소스 프로그램과 .text 섹션 내 기계어 인스트럭션 라인 번호들간의 매핑.

  • .strtab : .strtab과 .debug 섹션들 내에 있는 심볼 테이블/섹션 헤더들에 있는 섹션 이름들을 위한 스트링 테이블.

7-5 심볼과 심볼 테이블

모든 재배치 가능 목적파일들은 자신이 정의하거나 참조하는 심볼들을 심볼 테이블에 기록한다.
링커의 문맥에 따라 3가지의 심볼이 존재한다.

  • m에 의해 정의되고 다른 모둘에 의해서 참조될 수 있는 전역 심볼.

  • 모듈에 의해 참조되지만 다른 모듈에 의해 정의된 전역 심볼.

  • 모듈에 의해서 배타적으로 참조되고 정의된 지역 심볼.

심볼 테이블은 다음과 같은 구성을 가지고 있다.

  • name은 심볼의 이름을 가리키는 bite offset이다.

  • value는 심볼의 주소 이다.

    • 재배치 가능 모듈에서 value는 섹션 시작 부분의 offset값이다.
    • 실행가능 목적 파일에서 value는 절대 런타임 주소이다.
  • size는 객체의 크기다.

7-6 심볼 해석

1. 링커가 중복으로 정의된 전역 심볼을 해결하는 방법

  • 강한 심볼의 이름이 겹치는 것을 허용하지 않는다.

  • 강한 심볼과 여러개의 약한 심볼이 있다면 강한심볼을 선택한다.

  • 약한심볼만 여러개 있다면 뭘 선택해도 상관이 없다.

강한 심볼 : 함수 , 초기화된 지역 변수
약한 심볼 : 초기화 되지 않은 지역 변수

2. 정적 라이브러리와 링크하기

컴파일러는 '정적 라이브러리'라는 것을 만들어서 필요한 객체 모듈들을 집어넣는다.
즉, 필요한 모듈을 라이브러리에 집어넣고, 그것을 링킹하는 것이다.
이러한 방식은 프로그래머에게 상당히 편리하다는 장점을 가진다.

단점으로는 모든 실행파일이 표준 함수 전체의 복사본을 가지게 되며, 메모리 낭비가 심하다.
또한 표준함수를 건들이기만 해도 전체 소스 파일을 재컴파일 하고 표준 함수의 개발과 유지 작업이 힘들어진다.

3. 링커가 참조를 해석하기 위해 정적 라이브러리를 사용하는 방법
링커는 실행파일을 구성하기 위해

  • 합쳐질 목적파일의 집합 E
  • 미해석 심볼 집합 U
  • 이전 파일에서 정의된 심볼 집합 D

를 정의한다. 처음에는 E , U , D 모두 비어있다. 입력파일 f에 대해 실행순서는 다음과 같다.

  • f가 목적파일이면 f를 E에 추가하고, f에서 참조를 반영하도록 U와 D를 갱신한다.

  • f가 정적 라이브러리라면, 라이브러리를 구성하는 오브잭트 파일 m을 하나씩 확인하면서, m이 U에 포함된 심볼이 있는지 찾는다. 만약 있다면 필요한 파일이니까 E에 m을 추가한다. 이후 U,D를 업데이트 한다.
    즉, 라이브러리의 요소 하나하나를 살피면서 필요한놈이면 E에 라이브러리 요소를 추가하고, U,D를 업데이트 한다는 소리다.

  • 이후 스캔이 끝났는데도 U가 비어있지 않다면 에러를 출력하고, 비어있다면 목적파일들을 합치고 재배치하여 출력 실행파일을 작성하다.

7-7 재배치

어셈블러가 목적 모듈을 생성할 때, 저장을 어디에 해야 할지 알 수 없다. 또한 참조할 함수/전역변수의 위치도 모른다. 그래서 어떻게 수정해야되는지를 알려주는 ELF 재배치 엔트리를 작성한다.

  • offset : 수정해야할 참조의 섹션

  • type : 어떻게 수정할지

  • addend : 부호를 갖는 상수, 변경된 참조값을 조정하기 위해서 사용됨

7-8 실행 가능한 목적파일

지금까지의 내용은 링커가 다수의 목적파일을 하나의 실행 가능 목적파일로 합치는 과정이다.
실행가능 목적파일의 포맷은 재배치 가능한 목적파일의 포맷과 유사하다.
실행 가능 목적파일의 구조는 재배치가능 목적파일의 ELF 헤더 밑에 세그먼트 해더 테이블이 추가된 것이라고 봐도 된다.

7-9 실행 가능 목적파일의 로딩

모든 리눅스 프로그램은 execve 함수를 호출해서 로더를 호출 할 수 있으며, 로더는 디스크로부터 실행 가능한 목적파일의 코드/데이터/메모리를 복사하고, 프로그램의 엔트리 포인트로 점프해 프로그램을 실행시킨다.
이와 같이 프로그램을 메모리로 복사하고 실행하는 과정을 로딩이라고 한다.

리눅스 프로그램은 실행 중에 위 그림과 유사한 런타임 메모리 이미지를 가진다.

7-10 공유 라이브러리로 동적 링크하기

공유 라이브러리는 정적 라이브러리의 단점을 극복하기 위해 만들어졌다.
공유 라이브러리는 런타임이나 로드타임에 임의의 메모리 주소에서 로드되고, 메모리에서 프로그램으로 연결될 수 있는 목적 모듈이다. 이 과정을 동적 링킹이라고 하며 동적 링커라는 프로그램에 의해서 수행된다.

공유 라이브러리는 두 가지 방법으로 '공유'된다.

  • 첫째는 주어진 파일 시스템에서, 특정 라이브러리에 대해 .so 파일은 하나만 존재하는데, 이 파일의 내용을 실행 가능한 목적파일들과 공유한다.

  • 둘째는 메모리에 있는 공유 라이브러리의 .text 섹션으로 다른 실행중인 프로세스에 의해 공유되 ㄹ 수 있다.

7-11 응용으로부터 공유 라이브러리를 로드하고 링크하기

동적 라이브러리는 프로그램이 실행되는 도중에도 링킹이 가능한데,
이러한 예시를 보여주는 파트..





































내용의 설명과 예시가 상당수 짤렸다.
그리고 12,13장의 내용도 다 하지 못했다.(14,15장은 요약같은거)
다 하고 싶지만 그럴 시간도 없고 능력도 없다...
다음을 기약하자.

profile
닭이 되고싶은 병아리

0개의 댓글

관련 채용 정보