Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 3개 있습니다.)
(시리즈 글이 4개 있습니다.)
Linux: 5. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency)
; https://www.sysnet.pe.kr/2/0/11845

개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
; https://www.sysnet.pe.kr/2/0/13490

Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/13494

Linux: 76. Linux - C++ (getaddrinfo 등을 담고 있는) libnss 정적 링크
; https://www.sysnet.pe.kr/2/0/13679




Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기

지난번에 쓴 글에서,

Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency)
; https://www.sysnet.pe.kr/2/0/11845

glibc에 대한 의존성까지는 제거할 수 없었다고 했는데요, Go 언어에서 된다는 것을 봤으니,

Golang - GLIBC 의존을 없애는 정적 빌드 방법
; https://www.sysnet.pe.kr/2/0/13490

당연히 C/C++에도 있을 거라는 확신이 들었습니다. ^^




간단하게 Visual Studio에서 리눅스 C/C++ 프로젝트를 만들고,

#include <cstdio>

int main()
{
    printf("hello from %s!\n", "testapp");
    return 0;
}

빌드한 다음 ldd로 확인하면,

$ ldd testapp.out
        linux-vdso.so.1 (0x00007ffddd154000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f63c8959000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f63c8f4c000)

glibc 의존성이 나왔습니다. "Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency)" 글에서, -static-libgcc -static-libstdc++ 옵션을 주면 된다고 했는데, 위의 기본 예제에서는 변함이 없습니다.

대신 "-static"을 주면 glibc 의존성이 없어집니다.

$ ldd testapp.out
        not a dynamic executable

$ file testapp.out
testapp.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=ee...[생략]...3f9, with debug_info, not stripped

여기까지는 일단 우리가 원하는 대로 됩니다. ^^




반면에, 프로젝트 설정에서 "Configuration Type"만 "Application (.out)"에서 "Dynamic Library (.so)"로 바꾼 다음 소스코드를 main이 없는 것으로 변경하고,

#include <stdio.h>

void foo(void)
{
    puts("foo");
}

빌드하면 이번엔 링크 단계에서 (이전 상황과 같은) 오류가 발생합니다.

1>Linking objects
1>/usr/bin/ld : error : /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginT.o: relocation R_X86_64_32 against hidden symbol `__TMC_END__' can not be used when making a shared object
1>/usr/bin/ld : error : final link failed: Nonrepresentable section on output
1>collect2 : error : ld returned 1 exit status

처음엔, Visual Studio 측에서 리눅스 빌드와의 연동 과정에 뭔가 복잡한 의존성이 있어 충돌이 나는 거라 생각했는데, 검색을 해보니,

Building a shared library created a static library instead
; https://stackoverflow.com/questions/44429253/building-a-shared-library-created-a-static-library-instead

단순히 gcc로도 재현이 되는 문제였습니다.

$ cat foo.c
#include <stdio.h>

void foo(void)
{
    puts("foo");
}

$ gcc -c foo.c

$ gcc -shared -static -o libfoo.so foo.o
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/crtbeginT.o: relocation R_X86_64_32 against hidden symbol `__TMC_END__' can not be used when making a shared object
collect2: error: ld returned 1 exit status

즉 shared library로 빌드하는 경우, 저런 오류가 발생하는 것입니다. 좀 더 검색해 보면,

Compile a shared object (.so) with static glibc
; https://stackoverflow.com/questions/42764747/compile-a-shared-object-so-with-static-glibc

so인 경우 정적 링킹은 불가능하다고 합니다. 이유를 보면 공유 라이브러리 측에서 정적 링크를 해버리면 서로 다른 버전의 함수들이 사용되므로 예기치 못한 동작을 할 수 있기 때문이라는데요, 어쩌면 윈도우용 Visual C++의 msvcrt dll 충돌 문제와 유사한 것입니다.

그 글의 또 다른 덧글에 보면,

Statically linking glibc also risks running a version of glibc that doesn't match the underlying run-time kernel's system call interface.


라는 의견이 보이는데, 저건 좀 이해가 안 됩니다. 저런 식이라면, shared object가 아닌 executable로 빌드한 경우에도 정적 링크가 된 glibc의 system call 역시 마찬가지로 문제가 되어야 합니다.

또 다른 글을 보면,

Building a shared library created a static library instead
; https://stackoverflow.com/questions/44429253/building-a-shared-library-created-a-static-library-instead

shared object인 경우 반드시 "PIC(위치 독립 코드)"로 이뤄져야 하는데 static 옵션을 적용하는 경우 non-PIC 오브젝트 파일들과 링킹을 시도하기 때문에 가능하지 않다고 합니다.

이래저래 정리해 보면, 결국 달성할 수 없는 목표인 듯합니다. ^^




그래도 그나마 다행인 것은 C++의 경우 (닷넷 AOT와는 달리) 빌드된 결과물이 그다지 높은 glibc를 요구하지는 않습니다. 위에서 빌드한 hello world 예제의 경우 2.2.5 버전을 요구하고,

$ objdump -p libtestapp.so
...[생략]...
Version References:
  required from libc.so.6:
    0x09691a75 0x00 02 GLIBC_2.2.5

제가 실제로 만든 C++ 업무 프로그램도 (ldd 2.27 버전의) Ubuntu 18.04에서 빌드하는데 버전 의존성이 2.14가 최고인 것으로 나옵니다.

Version References:
  required from libdl.so.2:
    0x09691a75 0x00 07 GLIBC_2.2.5
  required from ld-linux-x86-64.so.2:
    0x0d696913 0x00 05 GLIBC_2.3
  required from libpthread.so.0:
    0x09691a75 0x00 03 GLIBC_2.2.5
  required from libc.so.6:
    0x06969194 0x00 09 GLIBC_2.14
    0x09691974 0x00 08 GLIBC_2.3.4
    0x0d696914 0x00 06 GLIBC_2.4
    0x0d696913 0x00 04 GLIBC_2.3
    0x09691a75 0x00 02 GLIBC_2.2.5

이게 어느 정도로 낮은 거냐면,,, glibc에 대한 리눅스 배포본을 조사한 글이 있는데,

glibc Versions
; https://gist.github.com/wagenet/35adca1a032cec2999d47b6c40aa45b1

CentOS 6.10에 glibc 버전이 2.12로 사용 중인 것을 제외하고 그 이외의 배포본들은 2.17 이상으로 나오기 때문에 웬만큼 오래된 컴퓨터가 아니고서는 대부분 실행된다고 봐도 무방할 것입니다.

만약 glibc의 높은 버전과 바인딩하고 있다면 어떤 함수들의 사용으로 인한 것인지 알아내는 것도 가능합니다. 실제로 닷넷 AOT 빌드 결과물에 대해 적용해 보면,

$ readelf -Ws ConsoleApp1 | grep 2.32
    44: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_getattr_np@GLIBC_2.32 (8)

$ readelf -Ws ConsoleApp1 | grep 2.34
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutexattr_init@GLIBC_2.34 (4)
    12: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_setspecific@GLIBC_2.34 (4)
    28: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dladdr@GLIBC_2.34 (4)
    30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_condattr_setclock@GLIBC_2.34 (4)
    46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.34 (4)
    57: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_rwlock_rdlock@GLIBC_2.34 (4)
    72: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_setname_np@GLIBC_2.34 (4)
    88: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_kill@GLIBC_2.34 (4)
    91: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_rwlock_unlock@GLIBC_2.34 (4)
   110: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutexattr_settype@GLIBC_2.34 (4)
   111: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutexattr_destroy@GLIBC_2.34 (4)
   113: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_rwlock_wrlock@GLIBC_2.34 (4)
   120: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_key_create@GLIBC_2.34 (4)
   124: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_attr_getstack@GLIBC_2.34 (4)
   140: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_create@GLIBC_2.34 (4)

대부분 pthraed 관련 함수들인 것을 확인할 수 있습니다. 아쉽게도 닷넷 AOT의 경우 저걸 알았다고 해서 바꿀 수 있는 제어권은 없지만, C/C++의 경우에는 특정 버전의 함수와 바인딩하는 방법들이 제공되는 듯하니,

How can I link to a specific glibc version?
; https://stackoverflow.com/questions/2856438/how-can-i-link-to-a-specific-glibc-version

활용해도 좋을 것입니다.




잠깐 쓸데없을 것 같은 검색 기록을 남겨 보면, 아래의 글에서,

What are Linker Symbols __TMC_END__ and __TMC_LIST__ for?
; https://stackoverflow.com/questions/17605794/what-are-linker-symbols-tmc-end-and-tmc-list-for

TMC는 tm_clone_table을 의미한다고 하면서 아래의 소스코드를 제시합니다.

gcc/libgcc/crtstuff.c
; https://github.com/gcc-mirror/gcc/blob/master/libgcc/crtstuff.c

crtstuff.c 파일은 빌드되면 crtend.o로 바뀌고 그것이 gcc 빌드 도중 collect2를 실행할 때 인자로 넘겨진다고 합니다.

/usr/lib/gcc/x86_64-linux-gnu/9/collect2 -plugin ...[생략]... --end-group /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/crtn.o


실제로 crtend.o에 정의된 심벌을 보면,

$ nm /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o
0000000000000000 r __FRAME_END__
0000000000000000 D __TMC_END__

"__TMC_END__"가 있군요. ^^





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/15/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1756정성태9/23/201427487기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434280오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424659VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420611오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441071Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438951.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423832.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423733.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425385개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428493오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426106.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423037개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431064.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420985오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426973개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421334.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432523.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426554.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422050.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419765VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425579VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418228.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419888오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426363.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434480Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427086개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...