링커는 무엇을합니까?


127

나는 항상 궁금했다. 컴파일러가 작성한 코드를 바이너리로 변환한다는 것을 알고 있지만 링커는 무엇을합니까? 그들은 항상 나에게 미스터리였습니다.

나는 '연결'이 무엇인지 대략 이해합니다. 라이브러리 및 프레임 워크에 대한 참조가 바이너리에 추가 될 때입니다. 나는 그 이상을 이해하지 못합니다. 나를 위해 그것은 "그냥 작동한다". 나는 또한 동적 연결의 기본을 이해하지만 너무 깊이는 없습니다.

누군가 용어를 설명 할 수 있습니까?

답변:


160

링커를 이해하려면 먼저 소스 파일 (예 : C 또는 C ++ 파일)을 실행 파일 (실행 파일은 컴퓨터에서 실행할 수있는 파일 또는 동일한 시스템 아키텍처를 실행하는 다른 사람의 시스템).

내부적으로 프로그램이 컴파일 될 때 컴파일러는 소스 파일을 객체 바이트 코드로 변환합니다. 이 바이트 코드 (때때로 개체 코드라고 함)는 컴퓨터 아키텍처 만 이해하는 니모닉 명령어입니다. 일반적으로 이러한 파일의 확장자는 .OBJ입니다.

개체 파일이 생성되면 링커가 작동합니다. 종종 유용한 일을하는 실제 프로그램은 다른 파일을 참조해야합니다. 예를 들어 C에서 이름을 화면에 인쇄하는 간단한 프로그램은 다음과 같이 구성됩니다.

printf("Hello Kristina!\n");

컴파일러가 프로그램을 obj 파일로 컴파일 할 때 단순히 printf함수 에 대한 참조를 넣습니다 . 링커는이 참조를 확인합니다. 대부분의 프로그래밍 언어에는 해당 언어에서 예상되는 기본 항목을 다루는 루틴의 표준 라이브러리가 있습니다. 링커는 OBJ 파일을이 표준 라이브러리와 연결합니다. 링커는 OBJ 파일을 다른 OBJ 파일과 연결할 수도 있습니다. 다른 OBJ 파일에서 호출 할 수있는 함수가있는 다른 OBJ 파일을 만들 수 있습니다. 링커는 워드 프로세서의 복사 및 붙여 넣기와 거의 유사합니다. 프로그램이 참조하는 모든 필수 함수를 "복사"하고 단일 실행 파일을 만듭니다. 때때로 복사되는 다른 라이브러리는 다른 OBJ 또는 라이브러리 파일에 종속됩니다. 때로는 링커가 작업을 수행하기 위해 꽤 재귀 적이어야합니다.

모든 운영 체제가 단일 실행 파일을 만드는 것은 아닙니다. 예를 들어 Windows는 이러한 모든 기능을 단일 파일에 함께 보관하는 DLL을 사용합니다. 이렇게하면 실행 파일의 크기가 줄어들지 만 실행 파일이 이러한 특정 DLL에 종속됩니다. DOS는 오버레이 (.OVL 파일)라는 것을 사용했습니다. 이것은 많은 목적을 가지고 있었지만, 하나는 일반적으로 사용되는 기능을 하나의 파일에 함께 보관하는 것이 었습니다. 메모리에서 "언로드"되고 다른 오버레이는 해당 메모리 위에 "로드"될 수 있으므로 "오버레이"라는 이름). 리눅스는 공유 라이브러리를 가지고 있는데, 이것은 기본적으로 DLL과 같은 생각입니다 (내가 아는 하드 코어 리눅스 사람들은 많은 큰 차이점이 있다고 말할 것입니다).

이해하는 데 도움이 되었기를 바랍니다.


9
좋은 대답입니다. 또한 대부분의 최신 링커는 템플릿 인스턴스화와 같은 중복 코드를 제거합니다.
Edward Strange

1
이러한 차이점 중 일부를 살펴볼 수있는 적절한 장소입니까?
John P

2
안녕하세요, 내 파일이 다른 파일을 참조하지 않는다고 가정합니다. 두 개의 변수를 간단히 선언하고 초기화한다고 가정합니다. 이 소스 파일도 링커로 이동합니까?
Mangesh Kherdekar

3
@MangeshKherdekar-예, 항상 링커를 통과합니다. 링커는 외부 라이브러리를 연결하지 않을 수 있지만 실행 파일을 생성하려면 연결 단계가 여전히 발생해야합니다.
Icemanind

78

주소 재배치 최소 예

주소 재배치는 연결의 중요한 기능 중 하나입니다.

이제 최소한의 예제로 어떻게 작동하는지 살펴 보겠습니다.

0) 소개

요약 : 재배치 .text는 번역 할 개체 파일 의 섹션을 편집합니다 .

  • 개체 파일 주소
  • 실행 파일의 최종 주소로

컴파일러는 한 번에 하나의 입력 파일 만 볼 수 있기 때문에 링커에서 수행해야하지만 다음 방법을 결정하려면 모든 개체 파일을 한 번에 알아야합니다.

  • 선언 된 정의되지 않은 함수와 같은 정의되지 않은 기호 해결
  • 여러 객체 파일의 여러 섹션 .text과 충돌하지 않음.data

전제 조건 : 최소한의 이해 :

링크는 C 또는 C ++와 특별히 관련이 없습니다. 컴파일러는 객체 파일 만 생성합니다. 그런 다음 링커는 컴파일 된 언어를 알지 못한 채 입력으로 가져옵니다. Fortran 일 수도 있습니다.

따라서 크러스트를 줄이기 위해 NASM x86-64 ELF Linux hello world를 연구 해 보겠습니다.

section .data
    hello_world db "Hello world!", 10
section .text
    global _start
    _start:

        ; sys_write
        mov rax, 1
        mov rdi, 1
        mov rsi, hello_world
        mov rdx, 13
        syscall

        ; sys_exit
        mov rax, 60
        mov rdi, 0
        syscall

컴파일 및 조립 :

nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o

NASM 2.10.09 포함.

1) .o의 .text

먼저 .text개체 파일 의 섹션을 디 컴파일 합니다.

objdump -d hello_world.o

다음을 제공합니다.

0000000000000000 <_start>:
   0:   b8 01 00 00 00          mov    $0x1,%eax
   5:   bf 01 00 00 00          mov    $0x1,%edi
   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00
  14:   ba 0d 00 00 00          mov    $0xd,%edx
  19:   0f 05                   syscall
  1b:   b8 3c 00 00 00          mov    $0x3c,%eax
  20:   bf 00 00 00 00          mov    $0x0,%edi
  25:   0f 05                   syscall

중요한 라인은 다음과 같습니다.

   a:   48 be 00 00 00 00 00    movabs $0x0,%rsi
  11:   00 00 00

hello world 문자열의 주소를 rsi레지스터 로 이동해야하며 , 이는 쓰기 시스템 호출로 전달됩니다.

하지만 기다려! "Hello world!"프로그램이로드 될 때 컴파일러 가 메모리에서 끝날 위치를 어떻게 알 수 있습니까?

글쎄요, 특히 우리 .o가 여러 .data섹션 과 함께 여러 파일을 연결 한 후에는 할 수 없습니다 .

링커 만이 모든 객체 파일을 가지므로이를 수행 할 수 있습니다.

따라서 컴파일러는 다음과 같습니다.

  • 0x0컴파일 된 출력에 자리 표시 자 값 을 넣습니다.
  • 링커에 좋은 주소로 컴파일 된 코드를 수정하는 방법에 대한 추가 정보를 제공합니다.

이 "추가 정보"는 .rela.text개체 파일 의 섹션에 포함되어 있습니다.

2) .rela.text

.rela.text ".text 섹션 재배치"를 의미합니다.

링커가 개체의 주소를 실행 파일로 재배치해야하기 때문에 재배치라는 단어가 사용됩니다.

다음을 사용하여 .rela.text섹션을 분해 할 수 있습니다 .

readelf -r hello_world.o

포함하는;

Relocation section '.rela.text' at offset 0x340 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000c  000200000001 R_X86_64_64       0000000000000000 .data + 0

이 섹션의 형식은 http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html에 문서화되어 있습니다.

각 항목은 재배치해야하는 하나의 주소에 대해 링커에게 알려줍니다. 여기서는 문자열에 대해 하나만 있습니다.

이 특정 라인에 대해 약간 단순화하면 다음 정보가 있습니다.

  • Offset = C: .text이 항목이 변경 되는 첫 번째 바이트는 무엇입니까 ?

    디 컴파일 된 텍스트를 되돌아 보면 정확히 critical 내부에 있으며 movabs $0x0,%rsix86-64 명령어 인코딩을 알고있는 사용자는 이것이 명령어의 64 비트 주소 부분을 인코딩한다는 것을 알 수 있습니다.

  • Name = .data: 주소가 .data섹션을 가리킴

  • Type = R_X86_64_64, 주소를 변환하기 위해 수행해야하는 계산을 정확히 지정합니다.

    이 필드는 실제로 프로세서에 따라 다르 므로 AMD64 System V ABI 확장 섹션 4.4 "재배치"에 문서화되어 있습니다.

    그 문서는 다음과 같이 말합니다 R_X86_64_64.

    • Field = word64: 8 바이트, 따라서 00 00 00 00 00 00 00 00at 주소0xC

    • Calculation = S + A

      • S되는 의 어드레스에 따라서, 재배치되고00 00 00 00 00 00 00 00
      • A0여기에 있는 부록입니다 . 재배치 항목의 필드입니다.

      그래서 S + A == 0우리는 .data섹션의 첫 번째 주소로 재배치됩니다 .

3) .out의 .text

이제 ld생성 된 실행 파일의 텍스트 영역을 살펴 보겠습니다 .

objdump -d hello_world.out

제공합니다 :

00000000004000b0 <_start>:
  4000b0:   b8 01 00 00 00          mov    $0x1,%eax
  4000b5:   bf 01 00 00 00          mov    $0x1,%edi
  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00
  4000c4:   ba 0d 00 00 00          mov    $0xd,%edx
  4000c9:   0f 05                   syscall
  4000cb:   b8 3c 00 00 00          mov    $0x3c,%eax
  4000d0:   bf 00 00 00 00          mov    $0x0,%edi
  4000d5:   0f 05                   syscall

따라서 개체 파일에서 변경된 유일한 것은 중요한 줄입니다.

  4000ba:   48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:   00 00 00

이제 주소 0x6000d8( d8 00 60 00 00 00 00 00리틀 엔디안)를 가리 킵니다 0x0.

이것이 hello_world문자열 의 올바른 위치 입니까?

결정하려면 프로그램 헤더를 확인해야합니다. 이는 Linux에 각 섹션을로드 할 위치를 알려줍니다.

다음과 같이 분해합니다.

readelf -l hello_world.out

다음을 제공합니다.

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000d7 0x00000000000000d7  R E    200000
  LOAD           0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
                 0x000000000000000d 0x000000000000000d  RW     200000

 Section to Segment mapping:
  Segment Sections...
   00     .text
   01     .data

이것은 .data두 번째 섹션 인 섹션이 VirtAddr= 에서 시작 한다는 것을 알려줍니다 0x06000d8.

데이터 섹션의 유일한 것은 hello world 문자열입니다.

보너스 레벨


1
넌 대단해. 'ELF 파일의 전역 구조'자습서에 대한 링크가 끊어졌습니다.
Adam Zahran

1
@AdamZahran 감사합니다! 슬래시를 처리 할 수없는 멍청한 GitHub 페이지 URL!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

15

'C'와 같은 언어에서 개별 코드 모듈은 전통적으로 개체 코드의 덩어리로 개별적으로 컴파일되며, 모듈이 자체 외부에서 만드는 모든 참조 (즉, 라이브러리 또는 다른 모듈)가 갖는 것 이외의 모든 측면에서 실행할 준비가되어 있습니다. 아직 해결되지 않았습니다 (즉, 누군가가 와서 모든 연결을 만들기를 기다리고있는 공백입니다).

링커가하는 일은 모든 모듈을 함께 살펴보고, 각 모듈이 외부에 연결하는 데 필요한 사항을 살펴보고, 내보내는 모든 항목을 살펴 보는 것입니다. 그런 다음 모든 문제를 수정하고 실행 가능한 최종 실행 파일을 생성합니다.

동적 연결도 진행되는 경우 링커의 출력은 여전히 실행될 수 없습니다. 아직 확인되지 않은 외부 라이브러리에 대한 참조가 여전히 있고 앱을로드 할 때 OS에서 확인합니다 (또는 가능하면 나중에 실행 중).


일부 어셈블러 나 컴파일러는 컴파일러가 필요한 모든 것을 "보면"실행 파일을 직접 출력 할 수 있습니다 (일반적으로 단일 소스 파일과 #includes에 포함 된 모든 파일). 일반적으로 소형 마이크로를위한 일부 컴파일러는이를 유일한 작동 모드로 사용합니다.
supercat

네, 중간에 대답을하려고했습니다. 물론, 당신의 경우와 마찬가지로, 어떤 종류의 객체 파일이 완전한 코드 생성을하지 않는다는 점에서 그 반대도 사실입니다. 링커에 의해 수행됩니다 (MSVC 전체 프로그램 최적화가 작동하는 방식입니다).
Will Dean

@WillDean과 GCC의 Link-Time Optimisation은 내가 말할 수있는 한 모든 '코드'를 필수 메타 데이터와 함께 GIMPLE 중간 언어로 스트리밍하고 링커에서 사용할 수 있도록하며 마지막에 한 번에 최적화합니다. (오래된 문서가 의미하는 바에도 불구하고 개체 코드의 두 가지 표현이 모두 포함 된 이전 'fat'모드가 아닌 김플 만 기본적으로 스트리밍됩니다.)
underscore_d

10

컴파일러가 개체 파일을 생성하면 해당 개체 파일에 정의 된 기호에 대한 항목과 해당 개체 파일에 정의되지 않은 기호에 대한 참조가 포함됩니다. 링커는 그것들을 취해 합쳐서 (모든 것이 제대로 작동 할 때) 각 파일의 모든 외부 참조가 다른 개체 파일에 정의 된 기호로 충족됩니다.

그런 다음 모든 개체 파일을 함께 결합하고 각 심볼에 주소를 할당하고, 한 개체 파일에 다른 개체 파일에 대한 외부 참조가있는 경우 다른 개체가 사용하는 모든 위치에 각 심볼의 주소를 채 웁니다. 일반적인 경우에는 사용 된 절대 주소 테이블도 작성하므로 로더는 파일이로드 될 때 주소를 "수정"할 수 있습니다 (즉, 각 주소에 기본로드 주소를 추가합니다). 주소가 모두 올바른 메모리 주소를 참조하도록합니다).

꽤 많은 현대 링커도 수행 할 수 있습니다 (몇 가지 경우 A의 일부 많은 등 (모든 모듈을 볼 수 있습니다 번만 가능 방법으로 코드를 최적화하는 등의 다른 "물건"의) 예를 들어, 포함되었다 제거 기능 이 때문에 가능한 한 다른 모듈을 호출 할 수도 있지만, 모든 모듈이 조립되면 그것은 아무것도 이제까지)를 호출 없다는 것을 알 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.