라이브러리 파일
1. 컴파일 과정
프로그램의 컴파일은 복잡한 절차를 거치며, 보통 소스코드 파일을 한 번에 실행 가능한 최종 파일로 변환할 수 있지만, 실제로는 다음과 같은 네 단계를 거친다:1. 전처리: 소스 프로그램 내 모든 전처리 명령어를 해석하고 확장하여 *.i 파일을 생성한다.
2. 컴파일: 어휘와 문법 분석을 수행하고 대상 하드웨어 플랫폼용 어셈블리 언어 파일을 생성하며 *.s 파일이 만들어진다.
3. 어셈블: 어셈블리 언어 파일을 해당 프로세서의 이진 머신 코드로 변환하여 *.o 파일을 생성한다.
4. 링크: 여러 개의 *.o 파일을 하나의 확장자 없는 실행 파일로 결합한다.
예시로 hello.c 파일을 사용해 각 중간 파일을 순차적으로 생성하는 컴파일 명령어는 다음과 같다:
gcc hello.c -o hello.i -E
gcc hello.i -o hello.s -S
gcc hello.s -o hello.o -c
gcc hello.o -o hello -lc
2. ELF 포맷
개요
위 컴파일 과정에서 특히 주목해야 할 부분은 마지막 단계인 라이브러리 파일 링킹(gcc hello.o -o hello -lc)이다: 링킹은 여러 .o 파일을 결합하는 작업이다. 이들 *.o 파일은 결합 전후 모두 ELF 형식을 유지한다.
ELF는 Executable and Linkable Format의 약어로 실행 가능 및 링크 가능 형식을 의미한다. ELF 파일은 다양한 섹션(section)으로 구성되며, 다음 그림과 같다:
ELF 형식의 결합은 여러 파일의 해당 섹션을 하나로 통합하여 통합된 ELF 파일을 형성하는 것이다. 이 과정에서 각 *.o 파일 내 정적 데이터(상수 포함)와 함수 진입점 주소를 통합적으로 할당하고 관리해야 하며, 이를 재배치(relocation)라고 한다. 따라서 링크되지 않은 개별 *.o 파일은 재배치 가능 파일로 불리고, 동일한 섹션을 결합한 후의 파일은 실행 파일로 간주된다.
라이브러리는 본래 도서관(library)이라는 의미를 가지며, 라이브러리 파일은 여러 *.o 파일이 모여 있는 집합체이다.
관련 명령어
(1) readelf는 ELF 형식 파일의 세부 정보를 확인하는 데 사용된다:
# 파일 포맷 헤더 정보 조회
readelf -h a.out
# 각 섹션 정보 조회
readelf -S a.out
# 심볼 테이블 조회
readelf -s a.out
(2) ldd는 ELF 형식 파일의 동적 라이브러리 종속성을 확인하는 데 사용된다
ldd a.out
3. 라이브러리 기본 개념
라이브러리 파일은 정적 라이브러리와 동적 라이브러리 두 가지로 나뉜다. 예시는 다음과 같다:
정적 라이브러리: libx.a
동적 라이브러리: liby.so
// 라이브러리 파일 이름은 다음과 같은 규칙을 따른다:
lib라이브러리이름.확장자
여기서 lib는 모든 라이브러리 파일에 공통적으로 존재하는 접두사이며, 라이브러리 이름은 라이브러리 파일의 실제 이름이다. 위 예시에서 두 라이브러리 파일은 각각 x와 y라고 부르며, 링크 시 -lx와 -ly로 작성된다. 확장자는 정적 라이브러리와 동적 라이브러리에 따라 .a 또는 .so가 될 수 있다:
- 정적 라이브러리 확장자:
.a(archive, 즉 아카이브) - 동적 라이브러리 확장자:
.so(shared object, 즉 공유 객체)
참고: 정적 라이브러리든 동적 라이브러리든 모두 재배치 가능한 *.o 파일의 집합이다.
4. 동적 라이브러리와 정적 라이브러리 차이점
동적 라이브러리와 정적 라이브러리 모두 *.o 파일의 집합이다. 만약 *.o 파일을 책 한 권에 비유한다면, 라이브러리 파일은 서점이나 도서관이며, 정적 라이브러리와 동적 라이브러리의 관계와 차이는 다음과 같다:
- 정적 라이브러리(서점에 비유, 팔기만 함)
원리: 컴파일 시 라이브러리 내 코드가 각 프로그램에 복제됨
장점: 프로그램 실행 시 라이브러리에 의존하지 않음, 실행 효율이 약간 높음
단점: 저장 공간 희생, 사용자 업그레이드 반영 불가능
- 동적 라이브러리(도서관에 비유, 빌리기만 함)
원리: 컴파일 시 프로그램은 라이브러리 내 기능 모듈의 일치 여부만 확인하고 복제하지 않음
단점: 프로그램 실행 시 라이브러리에 의존, 실행 효율이 약간 낮음
장점: 저장 공간 절약, 사용자 업그레이드 용이
표면적으로 정적 라이브러리와 동적 라이브러리 모두 장단점이 있으며 서로 상대적인 관계이지만, 실제 응용에서는 동적 라이브러리 사용이 정적 라이브러리보다 훨씬 많다. 동적 라이브러리의 런타임 로딩 특성으로 인해 프로그램 성능이 약간 저하되지만, 대량의 저장 공간 절약 외에도 주 프로그램과 라이브러리 간 느슨한 결합을 달성하여 상호 연결되지 않고, 라이브러리 업그레이드 시 애플리케이션 프로그램을 수정할 필요 없이 새로운 버전 라이브러리의 기능을 얻을 수 있어 프로그램의 유연성이 크게 향상된다.
5. 정적 라이브러리 제작
기능 파일 a.c, b.c가 다른 프로그램에서 재사용 가능한 일반적인 프로그램 모듈을 포함한다고 가정하면, 이를 정적 라이브러리로 제작할 수 있으며, 구체적인 단계는 다음과 같다:
첫 번째 단계, *.o 원료 제작
gcc a.c -o a.obj -c
gcc b.c -o b.obj -c
두 번째 단계, *.o를 하나의 정적 라이브러리로 결합
ar crs libcommon.a a.obj b.obj
정적 라이브러리 제작은 매우 간단하며, 제작 완료 후 ar 명령어로 라이브러리에 포함된 *.o 파일을 확인할 수 있다:
ar -t libcommon.a
6. 정적 라이브러리 일반 작업
1. 정적 라이브러리 내 *.o 목록 보기
ar t libcommon.a # (t는 table 의미, *.o 파일을 목록 형태로 표시)
a.obj
b.obj
2. 정적 라이브러리 내 *.o 파일 삭제
ar d libcommon.a b.obj # (d는 delete 의미, 지정된 *.o 파일 삭제)
ar t libcommon.a
a.obj
3. 정적 라이브러리에 *.o 파일 추가
ar r libcommon.a b.obj # (r은 replace 의미, 지정된 *.o 파일 추가 또는 교체)
ar t libcommon.a
a.obj
b.obj
4. 정적 라이브러리에서 *.o 파일 추출
ar x libcommon.a # (x는 extract 의미, 라이브러리 내 모든 *.o 파일 해제)
ar x libcommon.a a.obj # (라이브러리 내 a.obj 파일 지정 해제)
7. 정적 라이브러리 사용
라이브러리 파일의 가장 큰 가치는 코드 재사용에 있다. 위 라이브러리 파일이 포함하는 *.o 파일에 이미 몇 가지 함수 인터페이스가 포함되어 있다면, 해당 라이브러리를 링크하기만 하면 이러한 인터페이스를 다시 작성할 필요 없이 직접 링크하면 된다.
예제
다음은 전체 과정을 설명하는 간단한 구체 예제(정적 라이브러리 예제.zip):
(1) a.c 작성
#include
void utilityFunc(void)
{
printf("특정 기능 모듈 구현...\n");
}
(2) a.c를 정적 라이브러리로 제작
gcc a.c -o a.obj -c
ar crs libutil.a a.obj
(3) 해당 기능 모듈이 필요한 위치에서 라이브러리 링크
#include
#include "a.h"
int main(int argc, char const *argv[])
{
utilityFunc();
return 0;
}
(4) 컴파일 및 실행
참고:
대문자 L은 라이브러리 경로 지정
소문자 l은 라이브러리 이름 지정(접두사 및 확장자 제외)
참고 사항
- 컴파일 문장의
-L/경로/라이브러리/는 라이브러리 파일libutil.a의 구체 위치를 지정하며, 그렇지 않으면 시스템이 해당 라이브러리 파일을 찾을 수 없다. - 컴파일 문장의
-lutil은 링크할 라이브러리 파일의 구체 이름을 지정하며, 접두사와 확장자는 포함하지 않는다. - 정적 라이브러리의 경우, 컴파일 링크 시
main.c에 필요한 라이브러리 코드를 최종 실행 파일에 복사하므로 정적 라이브러리의 다음과 같은 특성이 발생한다:
- 실행 프로그램은 컴파일 후 정적 라이브러리와 관계를 끊으며, 실행도 정적 라이브러리에 의존하지 않는다.
- 실행 프로그램은 정적 라이브러리에 의존하지 않으므로 런타임 동적 로딩도 생략된다.
8. 라이브러리 내 중복 심볼
심볼은 함수 이름, 변수 이름을 의미하며, 라이브러리의 다른 *.o 파일에 중복 심볼이 있을 경우, 호출되는 버전은 라이브러리 내 배치 순서가 앞선 것을 우선한다.
라이브러리 a.obj와 b.obj 모두 다음과 같은 함수를 포함한다고 가정한다:
void process_func()
{
printf("%s 내 함수 %s입니다\n", __FILE__, __FUNCTION__);
}
이때, main 함수가 process_func 함수를 호출하면, 호출되는 최종 버전은 a.obj와 b.obj의 라이브러리 내 배치 순서에 따라 결정된다:
ar t libutil.a
a.obj
b.obj
./main
a.c 내 함수 process_func입니다
라이브러리 내 *.o 파일의 등장 순서를 변경하여 프로그램 실행 결과를 바꿀 수 있다:
라이브러리에서 b.obj 추출
ar d libutil.a b.obj
b.obj를 라이브러리에 추가하고 a.obj 앞으로 배치
ar rb a.obj libutil.a b.obj
ar t libutil.a
b.obj
a.obj
main 프로그램 실행
./main
b.c 내 함수 process_func입니다
정리:
프로그램이 링크하는 라이브러리에 중복 심볼이 여러 개 존재할 경우, 어느 버전을 호출할지는 라이브러리 내 각 *.o 파일의 배치 순서에 따라 결정되며, 앞에 있는 것이 우선 호출된다.
9. 다중 라이브러리 상호 의존성
두 라이브러리 파일이 있다고 가정: liba.a와 libb.a, 각각 a.obj와 b.obj만 포함한다. 두 소스 프로그램이 다음과 같은 의존 관계를 갖는다고 가정(라이브러리 상호 의존성.zip):
// a.c
#include
void func_a()
{
printf("나는 %s입니다\n", __FUNCTION__);
}
// b.c
#include "a.h"
void func_b()
{
func_a();
}
명백하게, b.c의 기능 인터페이스는 a.c에 의존한다. 즉, 라이브러리 libb.a는 liba.a에 의존한다.
이제 func_b()를 호출하는 주 함수를 작성한다:
////////////////////////////////////////////////////////
//
// 파일: main.c
// 설명: 라이브러리 상호 의존성 시연용
//
///////////////////////////////////////////////////////
#include
#include "b.h"
int main(int argc, char const *argv[])
{
func_b();
return 0;
}
컴파일 상황은 다음과 같다:
gcc main.c -o main -L. -lb -la
gcc main.c -o main -L. -la -lb
./libb.a(b.obj): In function `func_b':
b.c:(.text+0xa): undefined reference to `func_a'
collect2: error: ld returned 1 exit status
위 컴파일 정보에서 결론을 도출할 수 있다:
- 여러 라이브러리 컴파일 링크 시, 이들 사이에 의존 관계가 존재하면, 의존 대상이 되는 기반 라이브러리는 컴파일 문장의 뒷쪽에 배치해야 한다.
- 위 예제에서, 라이브러리
libb.a는liba.a에 의존하므로liba.a가 의존 대상인 기반 라이브러리이므로-la는-lb뒤에 있어야 컴파일이 성공한다.
참고: 위 결론은 정적 라이브러리와 동적 라이브러리 모두 적용된다.
10. 동적 라이브러리 명명
동적 라이브러리, 정적 라이브러리의 이름은 모두 다음과 같은 규칙을 따른다:
lib라이브러리이름.확장자
동적 라이브러리의 경우, 확장자 뒤에 종종 버전 번호가 붙는다:
lib라이브러리이름.확장자.버전번호
예를 들어 시스템 표준 라이브러리 경로 하:
여기서 심볼릭 링크의 역할은 "바로가기"가 아니라, 동적 라이브러리 버전 업그레이드 시 전방 호환성을 더 쉽게 유지하기 위한 것이다. 일반적으로, 완전한 동적 라이브러리 파일 이름은 다음과 같다:
lib라이브러리이름.so.주버전.부버전.수정버전
예: libutil.so.1.3.2
동적 라이브러리 반복 업그레이드 시, 버전 번호가 상응하는 변경을 겪는다. 아래는 버전 변경 예시:
- 2021년 3월 8일 출시:
libutil.so.1.0.0 - 2021년 4월 2일 출시:
libutil.so.1.0.1 - 2021년 4월 23일 출시:
libutil.so.1.0.2 - 2021년 5월 18일 출시:
libutil.so.1.0.3 - 2021년 8월 9일 출시:
libutil.so.1.1.0 - 2021년 9월 12일 출시:
libutil.so.1.1.1
수정 버전 변경이 가장 자주 발생하고, 부버전은 그 다음, 주버전은 그 다음이다. 매번 버전 번호 수정으로 인한 재컴파일을 피하기 위해, 동적 라이브러리는 일반적으로 주버전만 포함한 심볼릭 링크를 사용하여 프로그램을 링크한다. 예:
ls -l
lrwxrwxrwx 1 root root 15 Jan 16 2020 libbsd.so.0 -> libbsd.so.0.8.7
-rw-r--r-- 1 root root 80104 Jan 16 2020 libbsd.so.0.8.7
이렇게 하면, 미래에 버전 번호가 어떻게 변화하든 주버전이 같다면, 사용자가 링크하는 라이브러리 이름은 항상 libbsd.so.0이 되어 특정 버전을 신경 쓸 필요가 없다. 주버전까지 변경되었다면, 이는 일반적으로 라이브러리가 전방 호환되지 않음을 의미하며, 일부 기존 인터페이스가 삭제되었을 수 있으므로, 사용자는 프로그램을 재컴파일해야 한다.
11. 동적 라이브러리 제작
정적 라이브러리든 동적 라이브러리든, 모두 다른 프로그램에서 링크하는 기능 모듈이다. 정적 라이브러리와 일치하게, 동적 라이브러리 제작 단계는 다음과 같다:
*.c를 컴파일하여*.o생성*.o를 동적 라이브러리로 컴파일
예:
ls
a.c b.c
# 첫 번째 단계: 소스 코드를 *.o로 컴파일
gcc a.c -o a.obj -c -fPIC
gcc b.c -o b.obj -c -fPIC
ls
a.c b.c a.obj b.obj
# 두 번째 단계: *.o를 동적 라이브러리로 컴파일
gcc -shared -fPIC -o libcommon.so a.obj b.obj
ls
a.c b.c a.obj b.obj libcommon.so
12. 동적 라이브러리 사용
동적 라이브러리의 컴파일은 정적 라이브러리와 다르지 않다. 예:
pwd
/home/user
ls lib/
libcommon.so
gcc main.c -o main -L./lib -lcommon
- 설명:
-L 옵션 뒤에는 동적 라이브러리가 있는 경로가 따른다.
-l 옵션 뒤에는 동적 라이브러리 이름이 따른다.
런타임 링킹
동적 라이브러리의 최대 특징은 컴파일 링킹 후 프로그램이 동적 라이브러리의 코드를 포함하지 않는다는 것으로, 이 프로그램은 매번 실행 시 동적으로 자신이 의존하는 라이브러리 파일 내 모듈을 탐색하고 위치를 파악한다. 이것이 동적 라이브러리라 불리는 이유이다.
즉, 프로그램 실행 시 동적 라이브러리를 찾을 수 없으면 실행에 실패한다. 예:
./main
./a.out: error while loading shared libraries:
libmyFunc.so:
cannot open shared object file:
No such file or directory
위 오류가 발생하는 이유는 프로그램 main 실행 시, 자신이 의존하는 동적 라이브러리 libcommon.so를 찾을 수 없기 때문이며, 이 문제 해결 방향은 대략 두 가지이다:
라이브러리 파일을 시스템 기본 검색 경로에 저장:
운영체제는 기본적으로 두 디렉토리에서 검색한다
- /lib
- /usr/lib
따라서 라이브러리 파일을 위 경로 중 하나에 저장하면 된다
운영체제에게 라이브러리 위치 알리기:
1. 컴파일 시 미리 알림:
gcc main.c -o main -L. -lcommon -Wl,-rpath=/home/user/lib
2. 환경 변수 설정:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/lib
참고:
이 방법은 임시 설정이며, 영구 설정이 필요하면 위 명령어를 구성 파일에 기록하여 새 터미널 시작 시 자동 실행되도록 해야 한다(~/.bashrc)
3. 시스템 기본 라이브러리 경로 수정(구성 파일 수정):(권장하지 않음)
sudo vi /etc/ld.so.conf.d/libc.conf # 구성 파일 열기
############다음은 구성 파일 내용###############
# libc 기본 구성
/usr/local/lib
/usr/lib/CustomLib/ # 끝에 사용자 정의 라이브러리 경로 추가
~
~
~
~
# 저장 후 ldconfig으로 구성 파일 내용 새로 고침(재적용)
sudo ldconfig
위 파일에 동적 라이브러리 위치 경로를 추가하면 된다.
참고: 여기서 편집 시 주의해야 하며, 오류가 발생하면 시스템 부팅 불가능 상태가 될 수 있으므로, 시스템 오염을 초래하는 작업으로 권장하지 않는다.
13. 동적 로딩 공학 문제
다음과 같은 문제를 제기한다: A회사가 B회사의 자동화 생산 라인용 검사 장치를 개발하며, B회사는 최종 제품의 합격 여부를 검사할 수 있기를 요구한다. 구체적으로는:
- 1차 버전은 제품 코팅 색상 균일성, 외형 손상 여부 두 항목을 검사해야 한다.
- 시스템 상용화 운영 후, B회사가 새로운 검사 항목을 자율적으로 추가할 수 있도록 지원해야 한다.
이 공학 요구사항은 간단히 말해 A회사가 개발한 검사 시스템이 아직 나타나지 않은 미래의 인터페이스를 자동으로 링크할 수 있어야 함을 의미한다. 따라서 A회사는 외형, 코팅 색상 등의 구체 기능을 검사하는 소프트웨어가 아니라, B회사에 확장 가능한 소프트웨어 "프레임워크"를 제공해야 하며, B회사는 이후 자신의 실제 요구에 따라 검사 장치 기능을 확장할 수 있어야 한다.
14. 동적 로딩 라이브러리
동적 라이브러리의 최대 장점은 링킹을 런타임으로 연기하는 것으로, 런타임에 동적 라이브러리를 링크하므로 링킹 대상 선택의 여지를 남긴다. 위 공학 요구사항과 결합하여, 프로그램 실행 시 지정된 동적 라이브러리를 링크하게 하여 필요에 따라 동적 라이브러리를 링크하는 목적을 달성할 수 있으며, 이를 동적 라이브러리의 동적 로딩이라 한다.
구체적인 방법은 다음과 같다:
- 함수 인터페이스를 미리 약속, 예:
void inspection() - 각기 다른 요구사항의 구현 코드를 서로 다른 라이브러리에 캡슐화, 예:
libcolor.so,libshape.so - 상응하는 구성 파일 작성, 프로그램 시작 후 링크할 구체 라이브러리 지정
관련 API 인터페이스:
#include
함수 원형:
void *dlopen(const char *filename, int flags);
int dlclose(void *handle);
매개변수 분석:
filename --> 대상 라이브러리 파일 이름
flags --> 동적 라이브러리 로딩 타이밍
RTLD_LAZY 지연 로딩(심볼 호출 시 로딩)
RTLD_NOW 즉시 로딩(열린 후 즉시 심볼 주소 로딩)
반환값:
성공 시 핸들 반환
실패 시 NULL 반환
라이브러리에서 지정된 심볼(함수) 주소 탐색:
#include
함수 원형:
void *dlsym(void *restrict handle, const char *restrict symbol);
매개변수 분석:
handle --> 어느 라이브러리에서 탐색할지 지정(dlopen 반환 핸들)
symbol --> 탐색할 대상 심볼 이름(함수 이름)
반환값:
성공 시 해당 함수 주소 반환
실패 시 NULL 반환
예제 코드:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv )
{
// 구성 파일에 따라 지정된 라이브러리 열기
void *handle = dlopen( argv[1] , RTLD_NOW);
if(handle == NULL)
{
printf("동적 라이브러리 [%s] 로딩 실패:%s\n", argv[1] , strerror(errno));
exit(0);
}
// 알려지지 않은 인터페이스 포인터 정의
void * (*check_func)(void*);
// 라이브러리에서 미리 약속된 인터페이스 탐색
check_func = dlsym(handle, "check_func");
if(check_func == NULL)
{
printf("심볼 [%s] 탐색 실패:%s\n", "check_func", strerror(errno));
exit(0);
}
// 자유롭게 해당 인터페이스 호출
check_func(NULL);
}
컴파일 및 사용 관련:
컴파일 시 일부 시스템에서는 수동으로 지정된 동적 링크 라이브러리를 연결해야 할 수 있다:
gcc main.c -ldl
실행 시:
현재 예제 기준, 실행 시 사용하는 라이브러리 파일을 명령줄 인자로 전달해야 하며, 해당 라이브러리 파일을 지정된 경로(/lib 또는 /usr/lib)에 저장해야 한다
./a.out libcolor.so
색상 균일도 검사 중인 척..
./a.out libdamage.so
외형 손상 정도 검사 중인 척..
15. 동적 라이브러리 버전 관리 기본 배경
동적 라이브러리와 정적 라이브러리의 최대 차이점은 동적 라이브러리가 동적 업그레이드(프로그램 재컴파일 없이 새 버전 기능 획득 가능)를 지원한다는 것으로, 즉 애플리케이션 프로그램이 처음 컴파일 링킹 후, 라이브러리 파일이 전방 호환되지 않는 개정(주버전 변경)을 하지 않았다면, 애플리케이션은 동적 라이브러리 업그레이드에 따라 새 버전 특성을 얻을 수 있으며 재컴파일이 필요 없다.
동적 라이브러리 버전
일반적으로 동적 라이브러리의 전체 이름은 다음과 같다:
libcommon.so.1.2.3
여기서, 버전 번호 순서는 주버전.부버전.수정버전이며, 위 예제의 경우:
- 주버전은 1
- 부버전은 2
- 수정버전은 3
주버전 수정은 일반적으로 중대 변경 시 발생하며, 주버전 변경은 이 동적 라이브러리에 의존하는 모든 애플리케이션 프로그램의 재컴파일을 유발하므로, 라이브러리 인터페이스의 중대 변경으로 인한 전방 호환 불가 또는 라이브러리 인터페이스 보안 취약점으로 사용자 업그레이드 요구 시와 같은 경우를 말한다.
부버전 수정은 일반적으로 큰 업그레이드가 발생하고 전방 호환 시 적용되며, 예를 들어 새로운 기능 추가, 기존 인터페이스 버그 수정 등이 있다.
SONAME
동적 라이브러리 업그레이드 과정에서 버전 번호가 변경되므로, 링커가 최신 동적 라이브러리 파일을 동적으로 링크할 수 있도록 하기 위해 컴파일 및 실행 단계에서 구체적인 전체 버전 번호가 포함된 라이브러리 파일 이름에 직접 의존하지 않고 비교적 안정적인 이름에 의존해야 하며, 예를 들어 주버전만 포함하는所谓SONAME이며, SONAME은 실제 라이브러리 파일 외부에 감싸져 있는 외부에 노출되는 링크 이름으로 이해할 수 있다:
외부 SONAME과 내부 실제 라이브러리 이름
수정 버전은 일반적으로 작고 필수적인 수정 시 발생하며, 수정 버전 변경은 상대적으로 자주 발생한다.
SONAME은 변경되지 않으며 실제 라이브러리 이름과 연관되어 있으므로, 링크 실행 단계에서 애플리케이션은 고정된 SONAME을 통해 올바른 라이브러리 파일을 링크할 수 있다. 그렇다면 라이브러리 파일의 SONAME은 어떻게 생성하는가? 기능 파일 a.c가 있다고 가정하고, 이를 동적 라이브러리 liba.so.1.0.0으로 만들고 주버전이 포함된 SONAME을 포함하고자 할 때, 컴파일 명령은 다음과 같다:
gcc -shared -fPIC a.c -o liba.so.1.0.0 -Wl,-soname,liba.so.1
ls -l
총 사용량 20
-rw-rw-r-- 1 user user 84 Nov 16 19:17 a.c
-rwxrwxr-x 1 user user 15664 Nov 17 17:49 liba.so.1.0.0
명령어 설명:
gcc: 컴파일러 이름-shared-fPIC: 위치 독립적인 동적 라이브러리 생성-oliba.so.1.0.0:liba.so.1.0.0동적 라이브러리 생성 지정-Wl,-soname,liba.so.1: 링커에 전달되는 매개변수, 라이브러리 파일의 SONAME이liba.so.1임을 지정(참고, 쉼표 뒤에 공백이 없어야 함)
여기서, 동적 라이브러리 파일 liba.so.1.0.0은 실제 라이브러리 이름이며, 링커의 관점에서 보면, 사용하는 것은 동적 라이브러리의 외부에 노출되는 SONAME이며, 여기서는 liba.so.1로 설정되며, 이 속성은 readelf 명령어로 확인할 수 있다:
ldconfig
프로그램 실행 단계에서 SONAME을 기준으로 동적 라이브러리를 동적으로 탐색하므로, 반드시 SONAME 소프트 링크 파일을 생성하고 실제 라이브러리 파일을 가리키도록 설정해야 하며, 두 가지 방식으로 이를 달성할 수 있다:
방식 1, 직접 소프트 링크 생성:
ln -s liba.so.1.0.0 liba.so.1
ls -l
총 사용량 36
-rw-rw-r-- 1 user user 85 Nov 17 20:11 a.c
lrwxrwxrwx 1 user user 13 Nov 17 18:49 liba.so.1 -> liba.so.1.0.0
-rwxrwxr-x 1 user user 15664 Nov 17 17:49 liba.so.1.0.0
방식 2, ldconfig으로 자동 소프트 링크 생성(권장):
ldconfig -n .
ls -l
총 사용량 36
-rw-rw-r-- 1 user user 85 Nov 17 20:11 a.c
lrwxrwxrwx 1 user user 13 Nov 17 18:49 liba.so.1 -> liba.so.1.0.0
-rwxrwxr-x 1 user user 15664 Nov 17 17:49 liba.so.1.0.0
명령어 설명:
ldconfig -n .: 현재 디렉토리에서 모든 동적 라이브러리의 SONAME을 검색하고 소프트 링크 파일 생성.
두 방식의 효과가 완전히 동일하지만, 방식 1은 각 라이브러리 이름, SONAME 등의 세부 사항을 수동으로 입력해야 하므로 오류 발생 가능성이 높으며, 지정된 디렉토리에 여러 라이브러리 버전이 존재할 경우 방식 1은 SONAME을 자동으로 매칭하여 올바른 소프트 링크 파일을 자동 생성할 수 없으므로, ldconfig를 사용하여 동적 라이브러리 및 SONAME을 자동 관리하는 것을 권장한다.
컴파일 단계
liba.so.1.0.0과 liba.so.1만으로는 충분하지 않으며, 컴파일 시 버전 정보가 없는 라이브러리 파일을 지정해야 하므로, 라이브러리 파일의 SONAME을 가리키는 버전 없는 소프트 링크 파일을 생성해야 하며, 예를 들면:
ln -s liba.so.1 liba.so
이렇게 하면 다음과 같은 관계의 세 파일이 형성된다:
동적 라이브러리, SONAME 및 소프트 링크
이것이 동적 라이브러리의 완전한 표준 명명 규칙이며, 다음 그림과 같다: