Search
Duplicate

공유 라이브러리를 링크하는 방법

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
개발지식
라이브러리
Scrap
태그
mlx
9 more properties

들어가기 전에

install_name_toolotool 에 대해 모르신다면 아래 글을 먼저 읽어주세요.
클러스터 맥을 기준으로 설명합니다.(catalina OS, version 10.15.7) 파일 구조는 아래와 같습니다. 대부분의 작업은 test의 위치에서 실행됩니다.
top/ - test/ - main.c - animal/ - cat.c
Bash
복사

1. 한번에 경로 입력하기

이전 글에선 animal 디렉토리에서 공유 라이브러리를 만들고, install_name_tool 로 수정하는 과정을 거쳤습니다. 하지만 이것도 반복하다보면 번거로운 작업이 됩니다. 대부분의 경우 이를 쉽게 해결할 수 있습니다.
test % gcc -dynamiclib -o animal/libcat.dylib animal/cat.c animal % otool -L libcat.dylib libcat.dylib: animal/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Bash
복사
-o 옵션 뒤에 경로를 포함해서 파일 이름을 작성하면 install_name_tool을 사용하지 않고도 바로 수정된 것을 확인할 수 있습니다. 다른 방법도 시도해보겠습니다.
test % gcc -dynamiclib -o ~/top/test/animal/libcat.dylib animal/cat.c animal % otool -L libcat.dylib libcat.dylib: /Users/jhwang/top/test/animal/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Bash
복사
이번에는 ~ 를 사용하여 라이브러리를 만드니 절대 경로로 지정된 것을 확인할 수 있습니다. 이 방법을 사용할 땐 실제로 접근이 가능해야 합니다. 만약 없는 경로로 잘못 지정하면 아래와 같은 에러를 반환합니다.
test % gcc -dynamiclib -o ~/test/animal/libcat.dylib animal/cat.c ld: can't open output file for writing: /Users/jhwang/test/animal/libcat.dylib, errno=2 for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
Plain Text
복사

2. 특수한 변수를 써보자

이전의 글과 위 내용만으로도 어느정도의 문제는 해결할 수 있습니다. 하지만 다양한 상황을 커버할 수 있는 것은 아닙니다. 라이브러리를 링크할 때 마다 일일이 경로를 직접 써 넣어가는건 생산성이 너무 떨어집니다. 이번 글의 내용은 다양한 상황에서 도움이 되는 특수한 변수들입니다.

1) @executable_path

먼저, @executable_path 입니다. dyld가 이 변수를 만나면 실행파일의 위치로 대체됩니다. 쉽게는, 실행시킬 파일이 있는 경로에서 pwd를 한 결과와 같다고 생각하면 됩니다.
test % pwd /Users/jhwang/top/test
Bash
복사
libcat.dylibinstall name 을 변수를 사용해 바꿔봅시다.
animal % gcc -dynamiclib -o libcat.dylib cat.c test % gcc -Lanimal -lcat -o meow main.c test % install_name_tool -change libcat.dylib @executable_path/animal/libcat.dylib meow test % otool -L meow meow: @executable_path/animal/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Bash
복사
현재 디렉토리를 표현하는 대신 @executable_path 를 입력했습니다. 글로만 보면 두개가 별 차이가 없는 것처럼 보입니다. 하지만 차이는 실행파일을 실행시키는 위치에서 발생합니다. 먼저, 기존의 경로대로 설정되어 있는 실행 파일을 상위 폴더 top 에서 실행시켜보겠습니다.
test % otool -L meow meow: animal/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) top % ./test/meow dyld: Library not loaded: ./animal/libcat.dylib Referenced from: /Users/jhwang/top/./test/meow
Bash
복사
에러가 난 이유를 찾으셨나요? 정답은 현재 실행 위치 top 에선 animal 폴더가 없기 때문입니다. animal/libcat.dylib 라는 경로는 상대 경로이고, 실행 파일을 실행시키는 위치에서 animal 디렉토리를 찾으려고 하기 때문에 에러가 발생하게 됩니다. 이 때문에 테스트 환경에선 잘 실행되던 프로그램이 실제 배포시엔 에러를 마구 뿌릴 수 있습니다…
@executable_pathdyld 가 링크 시에(프로그램 시작 전) 해당 변수를 만나면 실행파일(meow)의 위치로 변환해줍니다.
경로를 변경하고 다시 실행해봅시다.
test % install_name_tool -change animal/libcat.dylib @executable_path/animal/libcat.dylib meow test % otool -L meow meow: @executable_path/animal/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) top % ./test/meow MEOW!
Bash
복사
출력에 성공했습니다!

2) @loader_path

이 변수는 실행파일의 입장에선 똑같습니다. 그럼 왜 있는 걸까요? 해답은 mlx 에 있습니다. otool -L libmlx.dylib 의 결과가 기억나시나요? 라이브러리 안에 수많은 공유 라이브러리들이 연결되어 있습니다. 이렇게 라이브러리 사이에 서로 종속성을 가지는 경우 일일이 경로를 작성하는 것은 너무 불편할게 명백합니다. 이번 변수를 이해하기 위해 main.c를 수정하고, animal.c를 추가해야 합니다.
# 파일 구조 top/ - test/ - main.c - animal/ - animal.c - cat.c
Bash
복사
// main.c 수정 void animalSound(); int main(int argc, char** argv) { animalSound(); return 0; }
C
복사
// animal.c 추가 void catSound(); void animalSound() { catSound(); }
C
복사
main.canimal.c의 내용을 호출하고, animal.ccat.c의 내용을 호출합니다.
이제 라이브러리를 만들고 링크해보겠습니다.
animal % gcc -dynamiclib -o libcat.dylib cat.c animal % gcc -dynamiclib -L. -lcat -o libanimal.dylib animal.c test % gcc -Lanimal -lanimal -o meow main.c test % install_name_tool -change libanimal.dylib @executable_path/animal/libanimal.dylib meow
Bash
복사
test % otool -L meow meow: @executable_path/animal/libanimal.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) animal % otool -L libanimal.dylib libanimal.dylib: libanimal.dylib (compatibility version 0.0.0, current version 0.0.0) libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Bash
복사
실행 결과가 예상되시나요? executable 을 사용했으니 어디에서 실행하든 animal 라이브러리를 불러올 것이고, cat 라이브러리는 animal과 같은 위치에 있으니 불러올 수 있지 않을까요? 실행해봅시다.
% ./meow dyld: Library not loaded: libcat.dylib Referenced from: /Users/jhwang/top/test/animal/libanimal.dylib Reason: image not found zsh: abort ./meow
Bash
복사
이런, cat 라이브러리를 불러오지 못했습니다. 이유는 excutable_path 의 동작에 있습니다. 현재 executable_path의 경로는 ~/top/test/ 입니다. meow에서 animal을 불러올 때는 ~/top/test/animal/libanimal.dylib 으로 지정됩니다. animal에서 cat을 불러올 때는 어떨까요?
~/top/test/libcat.dylib 이 됩니다. 그 이유는 executable_path는 언제나 meow의 위치만 가르키기 때문입니다.
loader_path로드를 수행하는 클라이언트의 경로로 해석되는 변수입니다. 이때 클라이언트는 실행파일 일수도 있고 공유 라이브러리일 수도 있습니다.
이제 libanimal.dylibloader_path를 사용해 수정해봅시다.
animal % install_name_tool -change libcat.dylib @loader_path/libcat.dylib libanimal.dylib animal % otool -L libanimal.dylib libanimal.dylib: libanimal.dylib (compatibility version 0.0.0, current version 0.0.0) @loader_path/libcat.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) test % ./meow MEOW!
Bash
복사
잘 실행되는 것을 확인할 수 있습니다. 추가로, 실행파일의 입장에선 executable이나 loader나 같은 경로로 인식됩니다. 따라서 meow 에서 animal 을 불러올 때, executable_path 대신 loader_path를 사용해도 정상적으로 동작합니다.
따라서, loader_path는 main → animal 에선 ~/top/test/ 가 될 것이고, animal → cat 에서는 ~/top/test/animal/ 이 되겠죠?

3) @rpath

프로젝트의 규모가 너무 커지면 loader_path 를 추적해야 하는 작업이 빠르게 복잡해집니다. 이때 사용할 수 있는 변수가 rpath 입니다. rpath는 간단히 말하자면 라이브러리가 있는 경로를 하드 코딩하는 것입니다. 해당 변수는 위 두 변수와는 다르게 다른 명령어와 함께 사용해야 합니다. 바로 install_name_tool 의 사용하지 않았던 나머지 옵션들이 등장할 차례입니다!
install_name_tool -add_rpath <new> <file> : rpath를 추가합니다.
install_name_tool -rpath <old> <new> <file> : rpath를 old에서 new로 변경합니다.
install_name_tool -delete_rpath <old> <file> : rpath를 제거합니다.
이 변수를 사용하기 위해선, 먼저 rpath를 추가해주어야 합니다. 지금까지 연습한 경험을 생각해본다면 이것도 @rpath로 바로 변경해주면 될것 같지만, 그렇지 않습니다. 이전에 실습한 파일을 그대로 가지고 테스트 해봅시다.
test % install_name_tool -rpath @loader_path/animal/libanimal.dylib @rpath/libanimal.dylib meow error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: no LC_RPATH load command with path: @loader_path/animal/libanimal.dylib found in: meow (for architecture x86_64), required for specified option "-rpath @loader_path/animal/libanimal.dylib @rpath/libanimal.dylib"
Bash
복사
에러를 살펴보면, no LC_RPATH load command with path: 라는 문구를 찾을 수 있습니다. LC_RPATH 는 왜 갑자기 튀어나오는 걸까요? 이를 확인하기 위해서 otool -l 을 사용해야 합니다. (소문자 L)
test % otool -l meow meow: Load command 0 cmd LC_SEGMENT_64 ... ... ... Load command 13 cmd LC_LOAD_DYLIB cmdsize 64 name @loader_path/animal/libanimal.dylib (offset 24) time stamp 2 Thu Jan 1 09:00:02 1970 current version 0.0.0 compatibility version 0.0.0 Load command 14 cmd LC_LOAD_DYLIB cmdsize 56 name /usr/lib/libSystem.B.dylib (offset 24) time stamp 2 Thu Jan 1 09:00:02 1970 current version 1281.100.1 compatibility version 1.0.0 ... ...
Bash
복사
해당 명령을 실행하면 이상한 영어와 숫자덩어리가 우리를 압도하지만, 우리가 주목해야할 부분은 LC_LOAD_DYLIB 변수 입니다. 우리의 프로그램은 동적 라이브러리를 찾기 위해 위 변수를 사용한다는 것을 알 수 있습니다. 그리고 LC_RPATH가 없는 것도 확인할 수 있습니다. 우리가 사용해야 할 LC_RPATH-add_rpath 를 통해 추가할 수 있습니다.
아래와 같이 rpath를 추가하고, 이를 확인해봅시다.
test % install_name_tool -add_rpath ~/top/test/animal meow test % otool -l meow ... ... Load command 17 cmd LC_RPATH cmdsize 48 path /Users/jhwang/top/test/animal (offset 12)
Bash
복사
LC_RPATH를 찾았습니다! LC_RPATH 에 우리가 추가한 경로가 저장되어 있는것을 확인할 수 있습니다. 하지만 아직은 사용할 수 없습니다. otool -L 를 해봐도 해당 경로가 전혀 보이지 않습니다.
LC_RPATHdyld가 공유 라이브러리를 불러오기 위해 탐색해야 할 경로를 저장하는 변수이기 때문입니다.
이제 추가한 것을 실제로 사용하기 위해 약간의 작업을 해봅시다. meow 실행파일을 top 디렉토리로 옮기고, install_name_tool 을 사용해 rpath 를 사용하도록 변경해봅시다.
test % mv meow .. top % install_name_tool -change <old> @rpath/libanimal.dylib meow
Bash
복사
<old> 자리에는 meow 에 적혀있는 libanimal.dylib 의 경로를 적으면 됩니다.
이제 실행을 해봅시다.
top % ./meow MEOW!
Bash
복사
잘 작동하는 것을 확인할 수 있습니다. 아예 외딴섬으로 meow 를 날려보내도 작동하는지 확인해봅시다.
test % mv meow ~/goinfre goinfre % ls meow goinfre % ./meow MEOW!
Bash
복사
역시 잘 작동합니다. dyld@rpath 를 만나면 LC_RPATH 의 경로들을 모두 탐색하여 필요한 공유 라이브러리를 불러옵니다.
이번엔 cat 라이브러리도 멀리 날려보냅시다.
animal % mv libcat.dylib ~/Documents # 날려보낸 위치 추가 animal % install_name_tool -add_rpath ~/Documents/ libanimal.dylib animal % install_name_tool -change <old> @rpath/libcat.dylib libanimal.dylib goinfre % ./meow MEOW!
Bash
복사
animal 라이브러리에서 cat 라이브러리를 사용하니 해당 라이브러리를 수정하고 실행시키니 잘 작동하는 것을 확인할 수 있습니다.
rpath를 변경하려면 -rpath <old> <new> <file> 을 사용하고, 필요 없어진 rpath-delete_rpath <old> <file> 로 제거합니다. install_name_tool 을 사용해 추가한 rpathotool -l 을 사용해 LC_RPATH 변수에서 확인할 수 있습니다.

3. 요약

1.
프로그램을 실행시키는 위치에 따라 공유 라이브러리 로드가 실패할 수 있다.
2.
이를 해결하기위해 세가지 변수 executable_path, loader_path, rpath가 있다.
3.
executable_path 는 실행시키는 파일의 위치로 변환된다.
4.
loader_path 는 로드를 하려는 파일의 위치로 변환된다.
5.
rpath 는 사용자가 직접 경로를 하드 코딩할 수 있다.
6.
install_name_tool -add_rpathLC_RPATH를 추가할 수 있다.

4. 아직도 끝이 아니야?

제가 글을 쓴 처음 목적은 모두 달성했지만, 자료를 찾아보면서 새로운 의문이 들었고 새로운 기능들을 더 찾을 수 있었습니다. 이것들을 질문의 형태로 여러분과 함께 공유해보려 합니다.
꼭 컴파일(또는 링크)를 한 뒤, install_name_tool을 써야만 LC_RPATH 추가가 가능한가? 컴파일 단계에서는 이 조작이 불가능한가?
변수 조작이 된다면 install_name 도 가능하지 않을까?

출처