들어가기 전에

이 글은 공유 라이브러리를 실행파일로 링크하는 과정을 설명하고 있습니다. mlx 링크와 관련된 문제를 겪기 전이라면, 시행착오를 거친 이후에 읽는 것을 권장합니다. 클러스터 맥을 기준으로 설명합니다.(catalina OS, version 10.15.7) mlx 버전은 2021년 버전을 사용합니다. (슬랙에 있어요!)

이게 왜 되는 거지?

2서클 과제 중 일부는 그래픽과 관련된 과제가 있습니다. 여러분들은 여기서 처음으로 임의의 새 창에 그림을 그리게 됩니다. 서브젝트는 여러분의 시간을 절약해주기 위해 그림을 그리는 도구(mlx)를 제공합니다. (만세!!)

이 도구가 바로 이번 글의 주제인 공유 라이브러리입니다. 과제를 진행하기 위해 여러분의 프로그램과 공유 라이브러리를 링크해야만 하는데, libft와는 다르게 뭔가 이상합니다. 링크를 제대로 걸어줬지만, 자꾸만 에러가 납니다. 몇번의 시행착오를 겪다가, 몇몇 분들은 공유 라이브러리를 실행 파일과 같은 위치로 옮겨 프로그램 실행에 성공하신 분들도 있을 겁니다.

왜 파일을 옮기니까 잘 될까요? 그리고 왜 아까부터 동적 라이브러리를 공유 라이브러리로 부르는 걸까요?

1. Dynamic library? Shared library?

먼저 용어를 정리해야 할 필요가 있습니다.

우리가 흔히 동적 라이브러리(dynamic library)라고 부르던 것을 정확하게 부르자면 공유 라이브러리(shared library)입니다. 물론 일반적으로는 같은 의미로 사용되곤 합니다. 각 용어를 세밀하게 정의할 땐 아래와 같이 구분할 수 있습니다.

<aside> 👨🏻‍💻 What is the difference between shared and dynamic libraries in C? answer: https://stackoverflow.com/a/56899620

</aside>

두 용어의 차이가 어떤지 감이 잡히시나요? 첫 번째 경우는 일반적일 때입니다. 첫번째 상황과 같이 우리는 mlx를 쓰기 위해 컴파일 타임에 해당 라이브러리의 위치를 지정합니다.

2. 날 옮기지 마!

mlx가 공유 라이브러리라는 것을 알았으니, 이제 이 라이브러리를 실행파일에 링크시키는 방법을 알아야 할 차례입니다. libft.a 를 실행파일의 위치로 옮기지 않고 링크할 수 있으니 공유 라이브러리도 가능할 수 있다는 생각이 들면, 더 이상 mlx를 실행파일과 같은 위치로 옮겨선 안될 것 같습니다. 그럼 어떻게 해야 할까요?

1) man otool

클러스터 맥에서 man otool을 치면 objdump와 연계된 설명이 나옵니다. 해당 설명이 복잡하신 분들은 man otool-classic 을 입력하시거나 구글에서 검색해보세요.

otool 명령어를 사용하면 오브젝트 파일의 여러 정보를 볼 수 있습니다. 우선 mlx를 make로 생성해봅시다. libmlx.dylib 파일이 생성될텐데, 이를 otool -L libmlx.dylib 으로 확인해봅시다.

% otool -L libmlx.dylib
libmlx.dylib:
	libmlx.dylib (compatibility version 0.0.0, current version 0.0.0)
   ...

뭔가 엄청 많이 나오는데, 우리가 볼 것은 두 번째 줄입니다. otool -L 옵션의 설명을 보면 해당 줄이 공유 라이브러리의 ID 라는 것을 알 수 있습니다. 그 아래의 나머지 줄은 mlx를 사용하기위해 필요한 라이브러리의 목록입니다. 다음으로 -D 옵션을 사용하면 -L 을 실행한 결과의 두번째 줄과 비슷한 결과를 얻을 수 있습니다.

% otool -D libmlx.dylib
libmlx.dylib:
libmlx.dylib

-D 옵션의 설명을 읽어보면 공유 라이브러리의 install name 을 출력한다고 나와 있습니다. 이를 통해 -L 의 결과로 나온 ID-D 의 결과인 install name 이 같다는 것과, 공유 라이브러리는 자신의 상대경로를 install name(ID)으로 가진다는 것을 알 수 있습니다.

이번엔 실행 파일을 확인해볼까요? libft와 같은 방식으로 mlx를 링크한 뒤 명령어를 실행시켜봅시다.

% otool -L FdF
FdF:
	libmlx.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)

오브젝트 파일이 사용하는 공유 라이브러리들의 이름과 버전을 나타내준다는 -L 옵션의 설명대로 실행파일의 의존성에 mlx가 포함되어 있는 것을 확인할 수 있습니다. FdF를 실행하면, 프로그램을 실행시키기 전에 우선 실행시키는 위치를 기준으로 의존성 목록에 있는 공유 라이브러리들을 불러오려고 합니다. 이때 목록에 적힌libmlx.dylib 의 위치가 FdF를 실행시킨 위치를 기준으로 현재 위치로 적혀 있으니 mlx를 찾지 못하고 에러를 반환하는 것입니다.

% ./FdF
dyld: Library not loaded: libmlx.dylib
  Referenced from: /Users/jhwang/current/42_FdF/./FdF
  Reason: image not found
zsh: abort      ./FdF test_maps/10-2.fdf

2) man install_name_tool

해당 목차부터는 단순 복사 붙여넣기를 방지하기 위해 예제 코드로 진행됩니다.

원인을 찾았으니 문제를 해결해봅시다. 매뉴얼(링크)의 설명을 읽어보면 install_name_tool 에 더 많은 정보가 있다고 합니다.

install_name_tool - change dynamic shared library install names

제목에 익숙한 단어가 보입니다. 바로 install name 입니다. 이 명령어를 사용해 실제로 경로를 바꿔봅시다.

명령어를 연습하기 위해 아래의 두 파일을 작성해주세요.

// cat.c

#include <stdio.h>

void catSound() {
  printf("MEOW!\\n");
}
// main.c

void catSound();

int main(int argc, char** argv) {
  catSound();
  return 0;
}

해당 프로그램은 cat.c에 있는 catSound() 를 호출하여 출력하는 프로그램입니다. 이때 cat.c의 내용을 동적 라이브러리로 만들어 봅시다.

# 파일 구조

test/
- cat.c
- main.c
test % gcc -dynamiclib -o libcat.dylib cat.c

-dynamiclib 옵션을 사용해 cat.c를 공유 라이브러리로 만들었습니다. -o 옵션을 사용해 libft.a와 비슷하게 접두사 lib과 접미사 dylib 를 붙여주세요.

test % gcc -L. -lcat -o meow main.c

공유 라이브러리와 함께 링크하여 meow 라는 실행 파일을 만들었습니다. otool 명령어를 사용해보면 위의 내용과 같이 나오는 것을 확인할 수 있습니다. 실행도 해볼까요?

test % ./meow
MEOW!

제대로 작동합니다!

이번엔 여러 동물들을 하나의 디렉토리에 저장하기 위해 animal 디렉토리를 생성해 그곳에 cat.c를 옮겨봅시다. 만들어진 공유 라이브러리와 실행파일은 제거해주세요.

# 파일 구조
test/
- main.c
- animal/
  - cat.c
animal % gcc -dynamiclib -o libcat.dylib cat.c
test % gcc -L./animal -lcat -o meow main.c

animal디렉토리에서 다시 공유 라이브러리를 만들고 링크를 해보면 실행파일이 정상적으로 만들어지는 것을 확인할 수 있습니다. 실행 해볼까요?

test % ./meow
dyld: Library not loaded: libcat.dylib
  Referenced from: /Users/jhwang/current/test/./meow
  Reason: image not found
zsh: abort      ./meow

공유 라이브러리를 찾지 못해 에러가 발생합니다. 찾지 못하는 이유는 위의 1)man otool 에 나와 있듯이 실행 파일을 실행시키는 위치를 기준으로 의존성 목록에 있는 라이브러리를 찾지 못했기 때문입니다. 이를 install_name_tool로 해결해 봅시다. 여러 해결 방법이 있습니다!

i) install_name_tool -id <name> <file>

-id 명령어는 공유 라이브러리의 install name 을 직접 변경합니다. 만약 Mach-O 바이너리가 아니라면 무시됩니다.

<aside> 💡 Mach-O는 Mach Object file format의 줄임말으로, 파일 포맷의 한 종류 입니다.

</aside>

animal 디렉토리에서 공유 라이브러리의 경로를 다음과 같이 수정해줍시다.

animal % install_name_tool -id ./animal/libcat.dylib libcat.dylib

<name>위치에는 실행파일 meow 를 기준으로 작성해주셔야 합니다. 아래는 install name을 변경한 뒤의 공유 라이브러리와, 변경된 라이브러리를 링크한 뒤의 실행파일을 각각 otool -L 을 실행한 결과 입니다.

# install_name_tool로 변경 후 재시도

animal % install_name_tool -id ./animal/libcat.dylib libcat.dylib
animal % otool -D libcat.dylib
libcat.dylib:
./animal/libcat.dylib
test % gcc -L./animal -lcat -o meow main.c
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)

libcat.dylibinstall name이 변경되었고, 실행파일은 변경된 라이브러리의 install name을 그대로 가져오는 것을 확인할 수 있습니다. 이제 다시 실행해봅시다.

test % ./meow
MEOW!

잘 실행되네요! 하지만 이 방법은 약간 불편한 점이 있습니다. install name을 변경하는 명령과 링크를 하는 명령의 순서가 반대로 되면 변경된 install name을 가져올 수 없습니다!

aniaml % gcc -dynamiclib -o libcat.dylib cat.c
test % gcc -Lanimal -lcat -o meow main.c
animal % install_name_tool -id animal/libcat.dylib libcat.dylib

animal % otool -D libcat.dylib
libcat.dylib:
animal/libcat.dylib

test % otool -L meow
meow:
	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
dyld: Library not loaded: libcat.dylib
  Referenced from: /Users/jhwang/current/test/./meow
  Reason: image not found
zsh: abort      ./meow

ii) install_name_tool -change <old> <new> <file>

다시 소스파일만 남기고 지워주세요. 이번에는 공유 라이브러리는 내버려두고 실행파일을 변경해봅시다.

test % gcc -dynamiclib -o libcat.dylib cat.c
test % gcc -L. -lcat -o meow main.c

실행파일은 공유 라이브러리와 링크될 때 공유 라이브러리의 install name을 그대로 복사해옵니다.

공유 라이브러리의 install name을 수정하지 않고 가져왔다고 install_name_tool -id 를 다시 사용하고 링크하는 것은 너무 시간낭비겠죠? 이럴때 사용할 수 있는 명령어가 -change 입니다. 이 명령어는 종속한 공유 라이브러리의 install name을 변경합니다. 쉽게 생각하면 실행파일이 실행될 때 불러올 공유 라이브러리의 경로를 변경한다고 생각할 수 있습니다.

test % install_name_tool -change libcat.dylib ./animal/libcat.dylib meow
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)

실행파일이 실행할 때 필요한 공유 라이브러리의 경로가 변경되었습니다! libcat.dylib은 아무런 변경 사항이 없습니다. 실행파일을 실행해도 출력이 나오는 것을 확인할 수 있습니다.

예제를 통해 공유 라이브러리를 링크하는 두 가지 방법을 알아보았습니다. 이제 여러분들은 mlx를 옮기지 않아도 그림을 그릴 수 있습니다! 하지만… 아직 뭔가 부족합니다.. 기능이 이게 전부일까요?

출처

What is the significance of a macOS Mach-O dylib LC_ID_DYLIB name, or install_name?

Dynamic Libraries(DYLIB) on MAC OS X & iPhoneOS

dyld(1) [osx man page]