링킹이란?

  • 링킹은 컴파일과 링킹에서 마지막 과정으로 조각난 오브젝트 파일들을 하나의 바이너리 이미지로 합치는 과정이다.
  • ELF 포맷으로 되어있는 각 오브젝트를 섹션 종류별로 하나의 오브젝트로 합치는 과정이다.
  • $ ld -o main main.o sub.o
  • ld는 링킹 과정에서 각 오브젝트를 같은 종류의 섹션(text, data, bss 등)별로 묶어 하나의 ELF 바이너리로 만든다 : 결합
  • 결합과정 후에 합쳐진 각 섹션을 실제 코드에 맞게 조정한다. 이 때, 바이너리에 각 심볼이 가질 실제 주소를 구하고 이를 참조하는 부분에 주소를 설정한다. : 재배치

ELF 바이너리 포맷

NOTE

링킹과정에서 주요하게 알아두어야 할 부분은 ELF 바이너리 포맷과 링커 스크립트이다. 따라서 링킹 과정에 대해 자세히 알아보기 전 간략하게 ELF 바이너리 포맷에 대해 알아두고 넘어가자.

ELF 바이너리 포맷의 구조

: ELF 바이너리 포맷의 구조는 크게 ELF header, Program header table, Section & Segment area, Section header table 로 구성되어 있다. 여기서 Program header table에는 재배치 불가능한 ELF 오브젝트(실행 바이너리), Section header table에는 재배치 가능한 ELF 오브젝트(*.o 바이너리)가 온다.

  • readelf -h {executable binary}: ELF header를 조회한다.

  • readelf -l {executable binary}: 실행 바이너리 파일의 프로그램 헤더 테이블, Segment Section을 조회한다.

    • 프로그램 헤더의 내용에는 다음을 포함한다.

      LOAD 0x000000 0x... 0x... 0x... 0x... RWE 0x...

      여기서 맨 첫번째 값(타입)에 올 수 있는 값들의 종류는 다음과 같다.

      타입명 의미
      LOAD 메모리에 로드해야 되는 세그먼트
      NULL 사용되지 않는 프로그램 헤더
      DYNAMIC 동적 링크를 해야 되는 자료를 포함하는 세그먼트
      INTERP 인터프리터 이름을 포함하는 세그먼트
      PHDR 프로그램 헤더를 포함하는 세그먼트
      NOTE 노트 정보를 포함하는 세그먼트
      SHLIB 예약된 프로그램 헤더
    • Segment Section에는 위의 프로그램 헤더의 각 세그먼트가 어떤 섹션들로 이루어져 있는지를 나타낸다.

ld를 이용한 링킹

  1. Application Linking

    • 재배치 가능한 오브젝트( file.o )의 결합과정

    • 재배치 과정을 통해 재배치 불가능한 오브젝트( 실행 바이너리 main )를 만들어 내는 과정

      /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2
      -o test  
      /usr/lib/gcc/crt1.o  
      ...  
      main.o func.o
      -lgcc —as-needed -lgcc\_s —no-as-needed -lc -lgcc —as-needed -lgcc\_s —no-as-needed  
      /usr/lib/gcc/crtend.o

      위와 같은 명령을 위해선 main.o , func.o 가 존재해야하는데, 이는 $ gcc -c *.c 명령을 통해 얻을 수 있다. 그리고 위 명령어는 아래와 동일한 값을 가지기도 한다.

      ld -dynamic-linker /lib/ld-linux.so.2 
      -o main /usr/lib/crt1.o /usr/lib/crtn.o main.o func.o -lc

      -dynamic-linker lib/ld-linux.so.2 : 동적 링커로 ld-linux.so.2 를 사용한다. 이는 .interp 섹션에 해당 동적 링커의 경로가 들어가 main 프로그램 실행 시 해당 링커를 메모리 주소 공간이 같이 로드해 먼저 수행한다. 이후, 동적 링커는 프로그램 실행에 필요한 공유 라이브러리들을 확인하여 메모리에 로드 및 주소공간을 맵핑한다.

      NOTE
      -dynamic-linker lib/ld-linux.so.2 옵션을 주지 않으면 링커는 -lc 옵션을 동적링커로 인식하여 오류를 발생시킴에 주의하자.

      아키텍처에서 지원하지 않는 연산이 사용되어 gcc의 내부 함수로 바뀐 경우, 해당 연산에 대한 라이브러리(libgcc.a, libgcc_s.so)가 필요한 경우가 있다. 이 때, 이러한 라이브러리가 추가되어야 한다.

      ld -L/usr/lib/gcc/i386-redhat-linux/4.1.2
      -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o  
      main.o func.o -lc -lgcc -lgcc_s
  2. -l 옵션으로 라이브러리 지정

    : 링킹시 특정 라이브러리를 포함하려면 -l{lib_name} 을 사용한다. 만약 포함할 라이브러리가 libJay.so 라면 -lJay 만을 기입하면 충분하다.

    : -l 옵션은 반드시 소스보다 뒤에 와야한다. 이는, ld가 C 소스 파일에서 호출한 함수들을 라이브러리에서 찾을 때 명령라인 입력을 기준으로 한 번만 스캔해서 찾아 링킹하기 때문이다. 만약 이를 위반할 경우 ld가 해당 라이브러리의 심볼들을 resolve 하지 못하는 문제가 발생한다.

  3. -L 옵션으로 라이브러리 검색 디렉토리 지정

    : ld는 기본적으로 /usr/i386-redhat-linux/lib, /usr/local/lib, /lib, /usr/lib 디렉토리 순서로 라이브러리를 찾는다. 이 경로에 없는 라이브러리의 경우 위 옵션을 이용해 경로를 지정해 주어야 한다.

  4. -static 옵션으로 정적 링킹

    : 공유 라이브러리와 동적 라이브러리가 동시에 있는 경우 위 옵션을 사용하면 정적 라이브러리와 링킹한다. 정적 링킹 시 포함되는 모든 라이브러리의 정적 라이브러리가 있어야 한다.

     ld -static -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -o test main.o func.o 
     -\( -lgcc -lgcc_eh -lc -\)

    : -\( -lgcc -lgcc_eh -lc -\) 옵션은 앞의 라이브러리에 대해 참조하는 모든 심볼을 완전히 찾을 때까지 라이브러리들을 계속 스캔하면서 찾는 옵션이다. 이는 ld가 기본적으로 정적 라이브러리에 있는 오브젝트를 링킹하고자 할 때 명령 라인에서 주어지는 순서대로 한 번만 스캔하면서 심볼의 참조를 찾고, 해당 심볼을 정의하는 정적 라이브러리 내의 해당 오브젝트만 링킹하기 때문이다.

  5. -shared 옵션으로 공유 라이브러리와 링킹

    : 정적 라이브러리와 공유 라이브러리가 동시에 존재할 경우 공유 라이브러리와 링킹한다. 이 옵션은 default 값으로 -static 을 지정하지 않으면 자동으로 지정되는 옵션이라 보면 된다.

  6. -r 옵션으로 부분 링킹

    : 재배치 가능한 오브젝트들을 링킹해 하나의 큰 재배치 가능한 오브젝트로 만드는 옵션이다. 이는 나중에 다시 링킹될 수 있다.

     ld -r -o Operator.o plus.o minus.o mult.o div.o
    
     Prev           |  Next
     plus.o minus.o |  Operator.o
     mult.o div.o   |

+ Recent posts