동적 링커

컴퓨팅에서 **동적 링커**(dynamic linker), **동적 연결자**(動的連結子)는 실행 파일이 실행되기 전이나 실행되는 동안, 런타임에 로더가 동적 라이브러리를 링크하는 운영체제의 기능이다. 세부 사항은 운영체제마다 다르지만, 일반적으로 동적 링커는 보조 기억 장치에서 RAM으로 각 라이브러리의 내용을 복사하고, 점프 테이블을 채우며 포인터를 재배치한다.

링킹은 흔히 실행 파일이 컴파일 타임에 컴파일될 때 수행되는 과정을 일컫지만, 동적 링커는 외부 공유 라이브러리를 실행 중인 프로세스에 로드한 다음, 해당 공유 라이브러리를 실행 중인 프로세스에 동적으로 바인딩하는 운영체제의 특수한 부분이다. 이 방식은 **동적 링킹**(dynamic linking) 또는 **지연 링킹**(late linking)이라고도 불린다.

## 구현

### 마이크로소프트 윈도우

동적 링크 라이브러리(DLL)는 마이크로소프트 윈도우 및 OS/2 운영체제에서 공유 라이브러리 개념을 마이크로소프트가 구현한 것이다. 이러한 라이브러리는 보통 `DLL`, `OCX`(액티브X 컨트롤을 포함하는 라이브러리), 또는 `DRV`(레거시 시스템 드라이버) 파일 확장자를 가진다. DLL의 파일 형식은 윈도우 EXE 파일과 동일하다즉, 32비트 및 64비트 윈도우의 경우 PE 포맷(PE)이고, 16비트 윈도우의 경우 New Executable(NE)이다. EXE와 마찬가지로 DLL은 기계어, 데이터 및 리소스를 임의의 조합으로 포함할 수 있다.

DLL과 동일한 파일 형식을 가지지만 파일 확장자가 다르고 리소스 섹션만 포함할 수도 있는 데이터 파일을 리소스 DLL이라고 부를 수 있다. 이러한 DLL의 예로는 확장자가 `MUI`인 다국어 사용자 인터페이스 라이브러리, 확장자가 `ICL`인 아이콘 라이브러리, 확장자가 `FON` 및 `FOT`인 글꼴 파일 등이 있다.

### ELF를 사용하는 유닉스 계열 시스템 및 다윈 기반 시스템
대부분의 유닉스 계열 시스템에서 동적 링커를 구성하는 대부분의 기계어는 실제로는 외부 실행 파일이다. 운영체제 커널은 `[[exec (운영 체제)|exec]]` 또는 `[[Spawn (computing)#POSIX spawn functions|posix_spawn]]` 함수 호출의 결과로 새로 구성된 프로세스 주소 공간에 이 파일을 가장 먼저 로드하고 실행한다. 링크 시점에 사용해야 할 동적 링커의 경로가 실행 파일 이미지 내에 포함된다.

실행 파일이 로드되면 운영체제 커널은 해당 파일에서 동적 링커의 경로를 읽은 다음, 이 다른 실행 이진 파일을 로드하고 실행하려고 시도한다. 예를 들어 해당 경로에 파일이 없어 시도가 실패하면 원래 실행 파일을 실행하려는 시도도 실패한다. 그런 다음 동적 링커는 초기 실행 파일 이미지와 그것이 의존하는 모든 동적으로 링크된 라이브러리를 로드하고 실행 파일을 시작한다. 결과적으로 동적 링커의 경로명은 운영체제의 응용 프로그램 이진 인터페이스의 일부가 된다.

#### ELF를 사용하는 시스템
솔라리스, 64비트 버전 HP-UX, 리눅스, FreeBSD, NetBSD, OpenBSD, DragonFly BSD와 같이 실행 파일 이미지와 동적 라이브러리에 ELF 파일 형식(ELF)을 사용하는 유닉스 계열 시스템에서, 사용해야 할 동적 링커의 경로는 링크 시점에 실행 파일의 `PT_INTERP` 세그먼트 내 `.interp` 섹션에 포함된다. 이러한 시스템에서 동적으로 로드된 공유 라이브러리는 파일 이름 접미사 `.so`(shared object)로 식별할 수 있다.

동적 링커는 프로그램 실행 중이나 프로그램 링크 중에 그 동작을 수정하도록 영향을 받을 수 있으며, 이에 대한 예시는 다양한 유닉스 계열 시스템의 런타임 링커 매뉴얼 페이지에서 볼 수 있다. 이러한 동작의 전형적인 수정 사례는 `LD_LIBRARY_PATH` 및 `LD_PRELOAD` 환경 변수의 사용이다. 전자는 대체 위치에서 공유 라이브러리를 검색하여 런타임 링크 프로세스를 조정하고, 후자는 원래라면 링크되지 않았을 라이브러리를 강제로 로드하고 링크하는 데 사용된다. 한 예로 zlibc가 있는데, `uncompress.so`라고도 알려져 있으며, `LD_PRELOAD` 기법을 통해 투명한 압축 해제를 용이하게 한다. 결과적으로 BSD 및 리눅스 시스템에서 파일이 압축되지 않은 것처럼 사전 압축된(gzipped) 파일 데이터를 읽을 수 있으며, 몇 가지 주의 사항은 있지만 본질적으로 사용자가 하부 파일 시스템에 투명한 압축 기능을 추가할 수 있게 해준다. 이 메커니즘은 유연하여, 데이터를 요청한 사용자 프로세스에 제공하기 전, 파일 읽기 중에 데이터의 추가 또는 대체 처리를 수행하도록 동일한 코드를 사소하게 적응시키는 것이 가능하다.

#### macOS 및 iOS
애플의 다윈 운영체제와 그 위에 구축된 MacOS 및 iOS 운영체제에서, 사용해야 할 동적 링커의 경로는 링크 시점에 실행 파일 이미지의 Mach-O 로드 명령 중 하나에 포함된다. 이러한 시스템에서 동적으로 로드된 공유 라이브러리는 파일 이름 접미사 `.dylib` 또는 프레임워크를 위한 번들 내부의 배치로 식별할 수 있다.

동적 링커는 대상 실행 파일을 공유 라이브러리에 링크할 뿐만 아니라, 대상 실행 파일이 링크 시점에 알고 있는 메모리의 특정 주소 지점에 기계어 함수를 배치한다. 실행 파일이 동적 링커와 상호작용하고자 할 때, 해당 실행 파일은 잘 알려진 주소 지점 중 하나로 머신 특정 호출(call) 또는 점프(jump) 명령을 실행하기만 하면 된다. macOS 및 iOS 플랫폼의 실행 파일은 프로세스 실행 중에 동적 링커와 자주 상호작용한다. 심지어 실행 파일이 처음 실행된 지 몇 시간 후에 동적 링커와 상호작용하여 더 많은 라이브러리를 로드하고 더 많은 심볼을 해석하게 하는 경우도 있는 것으로 알려져 있다. macOS 또는 iOS 프로그램이 동적 링커와 이토록 자주 상호작용하는 이유는 애플의 코코아 및 코코아 터치 API와 이를 구현한 언어인 오브젝티브-C 때문이다(자세한 내용은 해당 주요 문서 참조).

동적 링커의 일부 동작을 수정하도록 강제할 수 있지만, 다른 유닉스 계열 운영체제와 달리 이러한 수정 사항은 동적 링커에 의해 무시될 수 있는(때로는 실제로 무시되는) 힌트이다. 이에 대한 예시는 `dyld`의 매뉴얼 페이지에서 볼 수 있다. 이러한 동작의 전형적인 수정 사례는 `DYLD_FRAMEWORK_PATH` 및 `DYLD_PRINT_LIBRARIES` 환경 변수의 사용이다. 전자는 공유 라이브러리에 대한 실행 파일의 검색 경로를 조정하며, 후자는 라이브러리가 로드되고 링크될 때 그 이름을 표시한다.

애플의 macOS 동적 링커는 다윈의 일부로 공개된 오픈 소스 프로젝트이며, 애플의 오픈 소스 `dyld` 프로젝트에서 찾을 수 있다.

### XCOFF 기반 유닉스 계열 시스템
AIX와 같이 XCOFF를 사용하는 유닉스 계열 운영체제에서 동적으로 로드되는 공유 라이브러리는 파일 이름 접미사 `.a`를 사용한다.

동적 링커는 프로그램의 실행 또는 링크 중에 그 동작을 수정하도록 영향을 받을 수 있다. 이러한 동작의 전형적인 수정 사례는 `[[LIBPATH]]` 환경 변수의 사용이다. 이 변수는 대체 위치에서 공유 라이브러리를 검색하여 런타임 링크 프로세스를 조정하거나, 그렇지 않았으면 링크되지 않았을 라이브러리를 강제로 로드하고 링크하는 데 사용된다.

### OS/360 및 그 후계자들
IBM OS/360 및 그 후계자들의 어셈블리어 프로그램에서의 동적 링킹은 일반적으로 슈퍼바이저 호출 명령을 포함하는 LINK 매크로 명령을 사용하여 수행되며, 이는 링크될 라이브러리 모듈을 프로그램에서 사용할 수 있도록 하는 운영체제 루틴을 활성화한다. 라이브러리 모듈은 제어 카드에 지정되어 프로그램의 특정 실행에만 사용할 수 있는 "STEPLIB" 또는 "JOBLIB"에 있거나, PARMLIB의 LINKLIST(시스템 시작 시 지정됨)에 포함된 라이브러리에 있거나, 시스템 시작 시 특정 재진입 가능 모듈이 로드되는 "링크 팩 영역"(link pack area)에 있을 수 있다.

### 멀틱스
멀틱스(Multics) 운영체제에서 실행 파일을 포함한 모든 파일은 메모리 세그먼트이다. 현재 세그먼트의 일부가 아닌 루틴을 호출하면 시스템이 메모리나 디스크에서 참조된 세그먼트를 찾아 실행 중인 프로세스의 주소 공간에 추가한다. 동적 링킹은 일반적인 작동 방식이며, 바인더(binder)를 사용한 정적 링킹은 예외적인 경우이다.

## 효율성
동적 링킹은 런타임에 실행되는 대부분의 프로세스와 마찬가지로 일반적으로 컴파일 타임에 링킹하는 것보다 느리다(더 많은 CPU 사이클이 필요하다). 그러나 "지연 링킹"(lazy linking)을 사용하면 로딩 시간을 줄일 수 있다. 이는 라이브러리 함수 호출이 첫 번째 호출이 발생할 때만 라이브러리 내부의 구현에 링크되는 방식이다. 부작용으로는 프로그램이 로드된 후 첫 번째 함수 호출 시 링킹 시간이 시작 시점에서 실행 시점으로 옮겨지기 때문에 더 느려질 수 있다는 점이다.

하지만 동적 링킹은 흔히 (디스크 및 런타임 메모리에서) 더 공간 효율적이다. 라이브러리가 정적으로 링크될 때, 실행되는 모든 프로세스는 호출되는 라이브러리 함수의 자체 복사본과 링크된다. 따라서 서로 다른 프로그램에서 라이브러리를 여러 번 호출하면 해당 라이브러리의 동일한 함수가 시스템 메모리의 여러 곳에 중복되어 존재하게 된다. 공유 동적 라이브러리를 사용하면 컴파일 타임에 각 파일을 라이브러리 복사본에 링크하여 메모리 공간을 낭비할 가능성이 있는 대신, 메모리에 단 하나의 라이브러리 복사본만 저장되므로 다른 곳에 사용할 메모리 공간을 확보할 수 있다. 또한 동적 링킹에서는 라이브러리가 실제로 사용되는 경우에만 로드된다.

## 같이 보기
* 링커 (컴퓨팅)
* 목적 파일
* 정적 라이브러리
* 런타임 라이브러리
* C 표준 라이브러리

## 각주

; 내용주


분류:라이브러리
분류:취약점 공격
분류:컴파일러