NAME

ld.so, ld-linux.so - 동적 링커/로더

SYNOPSIS

어떤 동적 링크 된 프로그램 내지 공유 오브젝트를 실행함으로써 동적 링커를 간접적으로 실행할 수도 있고 (이 경우 동적 링커에 명령행 옵션을 줄 수 없으며, ELF인 경우 프로그램의 .interp 섹션에 저장된 동적 링커를 실행한다.) 다음처럼 직접 실행할 수도 있다.

/lib/ld-linux.so.*  [OPTIONS] [PROGRAM [ARGUMENTS]]

DESCRIPTION

프로그램 ld.sold-linux.so*는 프로그램에 필요한 공유 오브젝트(공유 라이브러리)들을 찾아서 적재하고 프로그램을 실행을 위한 준비를 한 다음 실행한다.

리눅스 바이너리들은 컴파일 때 ld(1)-static 옵션을 준 경우가 아니라면 동적 링크(런타임 링크)가 필요하다.

ld.so 프로그램은 예전에 쓰던 바이너리 형식인 a.out 바이너리를 다룬다. ld-linux.so*(libc5에선 /lib/ld-linux.so.1, glibc2에선 /lib/ld-linux.so.2) 프로그램은 더 최신인 ELF 형식으로 된 바이너리를 다룬다. 두 프로그램은 동작 방식이 동일하며 같은 지원 파일 및 프로그램(ldd(1), ldconfig(8), /etc/ld.so.conf)을 사용한다.

공유 오브젝트 의존성을 해결할 때 동적 링커에서는 먼저 각 의존성 문자열에 슬래시가 있는지 확인한다. (링크 때 슬래시가 포함된 공유 오브젝트 경로명을 지정했으면 그럴 수 있다.) 슬래시가 있으면 그 의존성 문자열을 (상대 또는 절대) 경로명으로 해석하여 그 경로명으로 공유 오브젝트를 적재한다.

공유 오브젝트 의존성에 슬래시가 없으면 다음 순서로 탐색을 한다.

동적 문자열 토큰

동적 링커가 여러 곳에서 동적 문자열 토큰들을 확장시킨다.

대체되는 토큰들은 다음과 같다.

$ORIGIN (또는 ${ORIGIN})

프로그램 내지 공유 오브젝트를 담은 디렉터리로 확장된다. 즉 somedir/app에 위치한 응용을 다음과 같이 컴파일 하면

gcc -Wl,-rpath,'$ORIGIN/../lib'

somedir이 디렉터리 구조에서 어디든 간에 somedir/dir에서 관련 공유 오브젝트를 찾게 된다. 이를 이용하면 특정 디렉터리에 설치할 필요 없이 아무 디렉터리에나 풀어 주면 자체 공유 오브젝트를 찾아내는 "턴키" 응용을 쉽게 만들 수 있다.

$LIB (또는 ${LIB})
아키텍처에 따라서 lib 또는 lib64로 확장된다. (가령 x86-64에서는 lib64로 확장되고 x86-32에서는 lib로 확장된다.)
$PLATFORM (또는 ${PLATFORM})
호스트 시스템의 프로세서 유형에 대응하는 문자열(가령 "x86_64")로 확장된다. 일부 아키텍처에서는 리눅스 커널이 동적 링커에게 플랫폼 문자열을 알려 주지 않는다. 보조 벡터(getauxval(3) 참고)의 AT_PLATFORM에서 이 문자열 값을 가져온다.

셸에서 동적 문자열 토큰을 설정할 때는 셸 변수나 환경 변수로 확장되지 않도록 동적 문자열 토큰을 잘 감싸 주어야 한다.

OPTIONS

--argv0 string (glibc 2.33부터)
프로그램을 실행하기 전에 argv[0]string 값으로 설정한다.
--audit list
list에 지정한 오브젝트들을 감사자로 쓴다. list의 오브젝트들은 콜론으로 구분한다.
--inhibit-cache
/etc/ld.so.cache를 쓰지 않는다.
--library-path path
환경 변수 LD_LIBRARY_PATH 대신 path를 사용한다. (아래 참고.) 이름 ORIGIN, LIB, PLATFORMLD_LIBRARY_PATH 환경 변수에서처럼 해석한다.
--inhibit-rpath list
RPATH 및 RUNPATH 정보가 list의 오브젝트 이름들 중 하나이면 무시한다. 안전 실행 모드(아래 참고)에서 돌고 있을 때는 이 옵션을 무시한다. list의 오브젝트들은 콜론이나 공백으로 구분한다.
--list
모든 의존 관계과 각각이 어떻게 해결되었는지를 나열한다.
--preload list (glibc 2.30부터)

list에 지정한 오브젝트들을 사전 적재한다. list의 오브젝트들은 콜론이나 공백으로 구분한다. 아래의 LD_PRELOAD 환경 변수 설명처럼 오브젝트들이 사전 적재된다.

LD_PRELOAD와 달리 --preload 옵션에서는 새 프로그램을 실행하는 자식 프로세스에서 수행되는 사전 적재에 영향을 끼치지 않고 실행 파일 하나에만 사전 적재를 수행할 수 있다.

--verify
프로그램이 동적으로 링크 되어 있고 이 동적 링커에서 다룰 수 있는지 확인한다.

ENVIRONMENT

다양한 환경 변수들이 동적 링커의 동작에 영향을 준다.

안전 실행 모드

보안상 이유 때문에 동적 링커에서 바이너리를 안전 실행(secure-execution) 모드로 돌려야 한다고 판단한 경우에는 일부 환경 변수의 효과가 무효화되거나 바뀌고 환경에서 그 환경 변수들이 제거돼서 프로그램에서는 그 정의조차 볼 수 없게 된다. 동적 링커 자체의 동작에 영향을 끼치는 몇 가지 환경 변수를 아래에서 설명한다. 이런 식으로 처리되는 다른 환경 변수로 GCONV_PATH, GETCONF_DIR, HOSTALIASES, LOCALDOMAIN, LOCPATH, MALLOC_TRACE, NIS_PATH, NLSPATH, RESOLV_HOST_CONF, RES_OPTIONS, TMPDIR, TZDIR 등이 있다.

보조 벡터(getauxval(3) 참고)의 AT_SECURE 항목이 0 아닌 값을 가지고 있으면 바이너리를 안전 실행 모드로 실행한다. 여러 이유로 그 항목이 0 아닌 값을 가질 수 있다.

환경 변수

가장 중요한 환경 변수들로 다음이 있다.

LD_ASSUME_KERNEL (glibc 2.2.3부터)

각 공유 오브젝트에서는 필요한 최소 커널 ABI 버전을 동적 링커에게 알려 줄 수 있다. (ELF note 섹션에 이 요건이 들어가는데 readelf -n을 통해 NT_GNU_ABI_TAG라는 레이블의 섹션으로 볼 수 있다.) 런타임에 동적 링커가 돌고 있는 커널의 ABI 버전을 알아내서 그 ABI 버전을 넘는 최소 ABI 버전을 지정한 공유 오브젝트를 적재하지 않게 된다.

LD_ASSUME_KERNEL을 쓰면 동적 링커에서 다른 커널 ABI 버전의 시스템에서 돌고 있다고 여기게 만들 수 있다. 예를 들어 다음 명령행은 myprog가 필요로 하는 공유 오브젝트들을 동적 링커가 적재할 때 리눅스 2.2.5 상에서 돌고 있다고 상정하도록 한다.

$ LD_ASSUME_KERNEL=2.2.5 ./myprog

각기 최소 커널 ABI 버전 요건이 다른 여러 버전의 공유 오브젝트를 (탐색 경로의 다른 디렉터리들에서) 제공하는 시스템에서는 LD_ASSUME_KERNEL을 이용해 (디렉터리 탐색 순서에 따라서) 사용할 오브젝트 버전을 선택할 수 있다.

역사적으로 LD_ASSUME_KERNEL 기능의 가장 큰 용도는 LinuxThreads와 NPTL를 모두 제공하는 시스템에서 (그럴 때는 보통 후자가 기본인데) 더 구식인 LinuxThreads POSIX 스레드 구현을 수동으로 선택하는 것이었다. pthreads(7) 참고.

LD_BIND_NOW (glibc 2.1.1부터)
비어 있지 않은 문자열로 설정돼 있으면 함수 호출 결정을 첫 참조 시점까지 미루지 않고 프로그램 시작 때 동적 링커가 모든 심볼들을 결정하게 한다. 디버거 사용 시 유용하다.
LD_LIBRARY_PATH

실행 시점에 ELF 라이브러리들을 탐색할 디렉터리들의 목록. 콜론이나 세미콜론으로 목록 내 항목들을 구분하는데 어느 쪽 구분자에도 이스케이핑 지원은 없다. 길이가 0인 디렉터리 이름은 현재 작업 디렉터리를 나타낸다.

안전 실행 모드에서는 이 변수를 무시한다.

동적 링커는 LD_LIBRARY_PATH에 지정된 경로명에서 토큰 $ORIGIN, $LIB, $PLATFORM을 (또는 이름에 중괄호를 두른 버전을) 위의 동적 문자열 토큰에서 설명한 것처럼 확장한다. 그래서 예를 들어 다음과 같이 하면 실행할 프로그램을 담은 디렉터리 아래의 lib 또는 lib64 서브디렉터리에서 라이브러리를 찾게 된다.

$ LD_LIBRARY_PATH='$ORIGIN/$LIB' prog

(작은따옴표를 쓴 것에 유의하라. $ORIGIN$LIB가 셸 변수로 확장되는 걸 막기 위해서이다.)

LD_PRELOAD

다른 어떤 오브젝트보다 먼저 추가로 적재할 사용자 지정 ELF 공유 오브젝트들의 목록. 이 기능을 이용해 다른 공유 오브젝트의 함수들을 선택적으로 오버라이드 할 수 있다.

공백이나 콜론으로 목록 내 항목들을 구분하는데 어느 쪽 구분자에도 이스케이핑 지원은 없다. 오브젝트 탐색은 DESCRIPTION에 기술된 규칙에 따라 이뤄진다. 목록에 지정한 좌에서 우로의 순서에 따라 오브젝트를 탐색해서 링크 맵에 추가한다.

안전 실행 모드에서는 슬래시가 포함된 사전 적재 경로명을 무시한다. 또한 표준 탐색 디렉터리에 있는 것만, 그리고 set-user-ID 모드 비트가 켜져 있는 (흔치 않은) 경우에만 공유 오브젝트를 미리 적재한다.

동적 링커는 LD_PRELOAD 목록에 지정된 이름에서 토큰 $ORIGIN, $LIB, $PLATFORM을 (또는 이름에 중괄호를 두른 버전을) 위의 동적 문자열 토큰에서 설명한 것처럼 처리한다. (LD_LIBRARY_PATH 설명의 따옴표 처리 내용도 참고.)

사전 적재할 라이브러리를 지정하는 방법이 여러 가지 있으며, 다음 순서로 처리된다.

(1) LD_PRELOAD 환경 변수.

(2) 동적 링커 직접 호출 시의 명령행 옵션 --preload.

(3) /etc/ld.so.preload 파일. (아래에서 설명함.)

LD_TRACE_LOADED_OBJECTS
(어떤 값으로든) 설정돼 있으면 프로그램이 정상 실행하는 대신 ldd(1)로 실행한 것처럼 자기의 동적 의존성을 나열하게 된다.

그리고 좀 덜 알려진 변수들이 여럿 있는데, 다수는 구식이거나 내부용이다.

LD_AUDIT (glibc 2.4부터)

다른 어떤 오브젝트보다 먼저 별도의 링커 네임스페이스에 적재할 (즉 프로세스에서 이뤄질 정상적 심볼 결속을 침해하지 않는다) 사용자 지정 ELF 공유 오브젝트들의 목록이다. 이 오브젝트들을 이용해 동적 링커의 동작을 감사할 수 있다. 리스트의 항목들을 콜론으로 구분하며 구분자 이스케이핑 지원이 없다.

안전 실행 모드에서는 LD_AUDIT을 무시한다.

이른바 감사 지점들에서 (예를 들어 새 공유 오브젝트 적재 시, 심볼 결정 시, 다른 공유 오브젝트로부터 심볼 호출 시) 동적 링커가 감사 공유 오브젝트 내의 적절한 함수를 호출하여 알려 주게 된다. 자세한 내용은 rtld-audit(7)을 보라. 감사 인터페이스가 솔라리스에서 제공하는 Linker and Libraries GuideRuntime Linker Auditing Interface 장에서 설명하는 내용과 전반적으로 호환된다.

동적 링커는 LD_AUDIT 목록에 지정된 이름에서 토큰 $ORIGIN, $LIB, $PLATFORM을 (또는 이름에 중괄호를 두른 버전을) 위의 동적 문자열 토큰에서 설명한 것처럼 처리한다. (LD_LIBRARY_PATH 설명의 따옴표 처리 내용도 참고.)

glibc 2.13부터 안전 실행 모드에서는 감사 목록에서 슬래시가 포함된 이름을 무시하며 표준 탐색 디렉터리에 있고 set-user-ID 비트가 켜진 공유 오브젝트만 적재한다.

LD_BIND_NOT (glibc 2.1.95부터)
이 환경 변수가 비어 있지 않은 문자열로 설정돼 있으면 함수 심볼 결정 후에 GOT(global offset table)과 PLT(procedure linkage table)을 갱신하지 않는다. 이 변수를 LD_DEBUG(bindingssymbols 분류)와 함께 사용하면 모든 런타임 함수 결속을 관찰할 수 있다.
LD_DEBUG (glibc 2.1부터)

동적 링커의 동작에 대한 자세한 디버깅 정보를 출력한다. 이 변수의 내용은 다음 분류들을 하나 이상 콜론이나 쉽표, (값을 따옴표로 감싼 경우) 공백으로 구분한 것이다.

help
이 변수 값에 help를 지정하면 해당 프로그램을 실행하지 않으며 이 환경 변수에 어떤 분류들을 지정할 수 있는지에 대한 도움말 메시지를 표시한다.
all
모든 디버깅 정보를 찍는다. (statisticsunused 제외. 아래 참고.)
bindings
각 심볼이 어느 정의로 결속되는지에 대한 정보를 표시한다.
files
입력 파일 진행 상태를 표시한다.
libs
라이브러리 탐색 경로를 표시한다.
reloc
재배치 처리를 표시한다.
scopes
스코프 정보를 표시한다.
statistics
재배치 통계를 표시한다.
symbols
각 심볼 검색에 대해 탐색 경로를 표시한다.
unused
안 쓰이는 DSO를 알아낸다.
versions
버전 의존성을 표시한다.

glibc 2.3.4부터 안전 실행 모드에서는 /etc/suid-debug 파일이 존재하지 않는 한 (파일 내용은 상관없음) LD_DEBUG를 무시한다.

LD_DEBUG_OUTPUT (glibc 2.1부터)

기본적으로 LD_DEBUG 출력을 표준 오류에 기록한다. LD_DEBUG_OUTPUT이 정의돼 있으면 그 값에 "."(마침표)와 프로세스 ID를 덧붙인 경로명에 출력을 기록한다.

안전 실행 모드에서는 LD_DEBUG_OUTPUT을 무시한다.

LD_DYNAMIC_WEAK (glibc 2.1.91부터)

기본적으로 공유 라이브러리들을 찾아서 심볼 참조를 결정할 때 동적 링커에서는 처음 찾은 정의로 결정하게 된다.

예전 glibc 버전들(2.2 전)에서 제공하는 동작은 달랐다. 약한 심볼을 찾으면 링커가 그 심볼을 기억해 두고 나머지 공유 라이브러리들에서 계속 탐색을 했다. 이후 동일 심볼에 대한 강한 정의를 찾게 되면 그 정의를 대신 사용했다. (더는 심볼을 찾지 못하면 동적 링커가 처음 찾았던 약한 심볼을 사용했다.)

예전 glibc 동작 방식은 비표준이었다. (표준 방식에선 약한 심볼과 강한 심볼 구별이 정적 링크 때만 효력이 있어야 한다.) glibc 2.2에서 (당시 다른 대다수 구현체에서 제공하던 방식이었던) 현재의 동작 방식을 제공하도록 동적 링커가 변경됐다.

LD_DYNAMIC_WEAK 변수를 (어떤 값으로든) 설정하면 어떤 공유 라이브러리의 약한 심볼을 이후 다른 공유 라이브러리에서 발견된 강한 심볼이 오버라이드 할 수 있는 예전의 (비표준) glibc 동작 방식을 제공한다. (참고로 이 변수가 설정돼 있는 경우에도 공유 라이브러리의 강한 심볼이 메인 프로그램에 있는 동일 심볼의 약한 정의를 오버라이드 하지는 않는다.)

glibc 2.3.4부터 안전 실행 모드에서는 LD_DYNAMIC_WEAK를 무시한다.

LD_HWCAP_MASK (glibc 2.1부터)
하드웨어 능력들에 대한 마스크.
LD_ORIGIN_PATH (glibc 2.1부터>

바이너리를 찾은 경로.

glibc 2.4부터 안전 실행 모드에서는 LD_ORIGIN_PATH를 무시한다.

LD_POINTER_GUARD (glibc 2.4에서 2.22까지)
0으로 설정하면 포인터 보호 기능을 끈다. 그 외 다른 값은 포인터 보호 기능을 켜며, 이게 기본이다. 포인터 보호 기능이란 쓰기 가능한 프로그램 메모리에 저장되는 몇몇 코드 포인터들(setjmp(3)에서 저장하는 반환 주소나 여러 glibc 내부 함수에서 쓰는 함수 포인터들)을 난수에 가깝게 바꿔 놔서 공격자가 버퍼 오버런 내지 스택 넘침 공격 시 그 포인터들을 악용하는 걸 어렵게 만드는 보안 메커니즘이다. glibc 2.23부터는 포인터 보호 기능이 항상 켜져 있으므로 더는 LD_POINTER_GUARD를 써서 기능을 끌 수 없다.
LD_PROFILE (glibc 2.1부터)

프로파일 할 (단일) 공유 오브젝트의 이름을 경로명이나 soname으로 지정한 것. "$LD_PROFILE_OUTPUT/$LD_PROFILE.profile"이라는 이름의 파일에 프로파일링 출력이 덧붙는다.

glibc 2.2.5부터 안전 실행 모드에서는 LD_PROFILE를 무시한다.

LD_PROFILE_OUTPUT (glibc 2.1부터)

LD_PROFILE 출력이 기록되는 디렉터리. 이 변수가 정의돼 있지 않거나 빈 문자열로 정의돼 있는 경우 기본값은 /var/tmp이다.

안전 실행 모드에서는 LD_PROFILE_OUTPUT을 무시한다. 대신 항상 /var/profile을 쓴다. (이 사항은 glibc 2.2.5 전에서만 유효하다. 이후의 glibc 버전들에서는 안전 실행 모드에서 LD_PROFILE까지 무시한다.)

LD_SHOW_AUXV (glibc 2.1부터)

이 환경 변수가 (어떤 값으로든) 정의돼 있으면 커널로부터 전달받은 보조 배열(getauxval(3) 참고)을 보여 준다.

glibc 2.3.4부터 안전 실행 모드에서는 LD_SHOW_AUXV를 무시한다.

LD_TRACE_PRELINKING (glibc 2.4부터)
이 환경 변수가 설정돼 있으면 이 변수에 이름이 할당돼 있는 오브젝트의 사전 링크 처리를 추적한다. (ldd(1)를 써서 추적 가능한 오브젝트들의 목록을 얻을 수 있다.) 알 수 없는 오브젝트 이름이면 모든 사전 링크 처리 활동을 추적한다.
LD_USE_LOAD_BIAS (glibc 2.3.3부터)

기본적으로 (즉 이 변수가 정의돼 있지 않으면) 실행 파일과 사전 링크 된 공유 오브젝트들은 의존하는 공유 오브젝트의 기준 주소(base address)를 존중하는 반면 (사전 링크 돼 있지 않은) 위치 독립 실행 파일(PIE)과 다른 공유 오브젝트들은 존중하지 않는다. LD_USE_LOAD_BIAS를 값을 1로 해서 사용하면 실행 파일과 PIE 모두 기준 주소를 존중하게 된다. LD_USE_LOAD_BIAS가 0으로 정의돼 있으면 실행 파일과 PIE 어느 쪽도 기준 주소를 존중하지 않게 된다.

glibc 2.3.3부터 안전 실행 모드에서는 이 변수를 무시한다.

LD_VERBOSE (glibc 2.1부터)
비어 있지 않은 문자열로 설정돼 있으면 LD_TRACE_LOADED_OBJECTS 환경 변수가 설정돼 있을 때 프로그램에 대한 심볼 버전 정보를 출력한다.
LD_WARN (glibc 2.1.3부터)
비어 있지 않은 문자열로 설정돼 있으면 결정 안 된 심볼들에 대한 경고를 표시한다.
LD_PREFER_MAP_32BIT_EXEC (x86-64 전용, glibc 2.23부터)

인텔 실버몬트 소프트웨어 최적화 안내서에 따르면 64비트 응용에서 브랜치의 대상이 브랜치에서 4GB 넘게 떨어져 있으면 브랜치 예측 성능에 부정적 영향이 있을 수 있다. 이 환경 변수가 (어떤 값으로든) 설정돼 있으면 동적 링커에서 먼저 mmap(2) MAP_32BIT 플래그를 써서 실행 파일 페이지 매핑을 시도하고, 실패하면 그 플래그 없이 매핑하게 된다. 주의: MAP_32BIT는 주소 공간 하위 (4GB가 아닌) 2GB로 매핑 한다.

MAP_32BIT를 쓰면 주소 공간 배치 무작위화(ASLR)에 쓸 수 있는 주소 범위가 줄어들기 때문에 안전 실행 모드에서는 항상 LD_PREFER_MAP_32BIT_EXEC를 끈다.

FILES

/lib/ld.so
a.out 동적 링커/로더
/lib/ld-linux.so.{1,2}
ELF 동적 링커/로더
/etc/ld.so.cache
공유 오브젝트를 탐색할 디렉터리들의 목록과 후보 공유 오브젝트들의 순서 목록을 담은 파일. ldconfig(8) 참고.
/etc/ld.so.preload
프로그램보다 먼저 적재할 ELF 공유 오브젝트들의 공백 구분 목록을 담은 파일. 위의 LD_PRELOAD 설명 참고. LD_PRELOAD/etc/ld.so.preload를 모두 사용하면 LD_PRELOAD에 지정한 라이브러리들을 먼저 적재한다. /etc/ld.so.preload는 효과가 시스템 전역이어서 시스템에서 실행되는 모든 프로그램에 대해 지정한 라이브러리들이 사전 적재된다. (일반적으로 이는 바람직하지 않으며 보통은 긴급 요법으로만 쓰인다. 예를 들어 라이브러리가 잘못 구성됐을 때 임시 처치용으로 쓸 수 있다.)
lib*.so*
공유 오브젝트

NOTES

하드웨어 호환성

어떤 공유 오브젝트들은 모든 CPU에 존재하지는 않는 하드웨어별 인스트럭션을 이용해 컴파일 된다. 그런 오브젝트들은 /usr/lib/sse2/처럼 필요한 하드웨어 능력을 나타내는 이름의 디렉터리에 설치돼야 한다. 동적 링커에서 그런 디렉터리들을 머신 하드웨어와 맞는지 확인해서 해당 공유 오브젝트의 가장 적절한 버전을 선택한다. 하드웨어 능력 디렉터리들을 계층적으로 만들어서 CPU 기능들을 합칠 수도 있다. 지원하는 하드웨어 능력 이름의 목록은 CPU에 따라 다르다. 현재 다음 이름들을 인식한다.

Alpha
ev4, ev5, ev56, ev6, ev67
MIPS
loongson2e, loongson2f, octeon, octeon2
PowerPC
4xxmac, altivec, arch_2_05, arch_2_06, booke, cellbe, dfp, efpdouble, efpsingle, fpu, ic_snoop, mmu, notb, pa6t, power4, power5, power5+, power6x, ppc32, ppc601, ppc64, smt, spe, ucache, vsx
SPARC
flush, muldiv, stbar, swap, ultra3, v9, v9v, v9v2
s390
dfp, eimm, esan3, etf3enh, g5, highgprs, hpage, ldisp, msa, stfle, z900, z990, z9-109, z10, zarch
x86 (32비트)
acpi, apic, clflush, cmov, cx8, dts, fxsr, ht, i386, i486, i586, i686, mca, mmx, mtrr, pat, pbe, pge, pn, pse36, sep, ss, sse, sse2, tm

SEE ALSO

ld(1), ldd(1), pldd(1), sprof(1), dlopen(3), getauxval(3), elf(5), capabilities(7), rtld-audit(7), ldconfig(8), sln(8)


2021-03-22