특정 glibc 버전에 연결하려면 어떻게해야합니까?


110

Ubuntu Lucid 10.04 PC에서 무언가를 컴파일하면 glibc와 연결됩니다. Lucid는 2.11의 glibc를 사용합니다. 이전 glibc가있는 다른 PC에서이 바이너리를 실행하면 glibc 2.11이 없다는 명령이 실패합니다.

내가 아는 한 glibc는 기호 버전 관리를 사용합니다. gcc가 특정 기호 버전에 대해 링크하도록 강제 할 수 있습니까?

내 구체적인 사용에서 ARM 용 gcc 크로스 툴체인을 컴파일하려고합니다.


58
이것은 솔루션이 항상 "그렇게하면 안된다"는 것과 같은 정말 성가신 리눅스 문제 중 하나입니다. 이것은 물론 "작동하지 않고 아무도 아직 고치지 않았습니다"를 의미합니다.
Timmmm

3
사람들은 Windows의 DLL 지옥에 대해 불평했습니다. 나는 그것을 Windows 세계에서 특히 끔찍한 사례로 올리려고 시도하는 일부 리눅스 애호가를 기억한다 . 에 나는 처음 실행했을 때 내가 한 모든 전에 십 년간 일을 리눅스 개발은 내 손에 내 얼굴을 묻어 있었다.
0xC0000022L

답변:


69

glibc가 기호 버전 관리를 사용한다는 점에서 정확합니다. 궁금하다면 glibc 2.1에 도입 된 기호 버전 관리 구현이 여기 에 설명되어 있으며 여기 에 설명 된 Sun의 기호 버전 지정 체계의 확장입니다 . .

한 가지 옵션은 바이너리를 정적으로 링크하는 것입니다. 이것은 아마도 가장 쉬운 옵션 일 것입니다.

chroot 빌드 환경에서 또는 glibc- new => glibc- old를 사용하여 바이너리를 빌드 할 수도 있습니다. 크로스 컴파일러를 .

에 따르면 http://www.trevorpounds.com 블로그 게시물 이전 버전 화 기호 (glibc에)에 연결 , 같은 사용하여 너무 오래가 유효한 경우 이전 하나에 링크 할 수있는 기호를 강제로 할 수 있습니다 .symver의사를 -op는 처음에 버전이 지정된 기호를 정의하는 데 사용됩니다. 다음 예는 블로그 게시물 에서 발췌 한 것 입니다.

다음 예제는 glibc의 realpath를 사용하지만 이전 2.2.5 버전과 연결되어 있는지 확인합니다.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

__asm__(".symver realpath,realpath@GLIBC_2.2.5");
int main()
{
    const char* unresolved = "/lib64";
    char resolved[PATH_MAX+1];

    if(!realpath(unresolved, resolved))
        { return 1; }

    printf("%s\n", resolved);

    return 0;
}

18
glibc는 정적 링크를 지원하지 않습니다. 정적으로 링크 된 glibc 프로그램은 다른 libc 버전이있는 시스템에서 제대로 작동하지 않습니다.
모니카 기억

5
glibc는 libc.a계속 존재하지만 glibc는 권장되지 않지만 일부 경우에이를 지원 합니다 (Drepper) . 중요하지 않은 프로그램, 특히 NSS를 사용하는 모든 프로그램에 문제가 발생 합니다 (FAQ의 해결 방법 ).
mr.spuratic

20

-static 과 링크하십시오 . -static 과 연결할 때 하면 링커는 실행 파일 내에 라이브러리를 포함하므로 실행 파일은 더 커지지 만 프로그램이 시스템 대신 자체 라이브러리를 사용하기 때문에 이전 버전의 glibc가있는 시스템에서 실행할 수 있습니다. .


55
종종이 작업을 수행하려는 이유는 폐쇄 소스 애플리케이션을 배포하기 때문입니다. 이 경우 라이센싱 이유로 정적으로 링크하는 것이 허용되지 않는 경우가 많으므로 (모든 소스 코드를 해제해야 함) -static에주의해야합니다.
Malvineous

3
한편 적어도 하나는 종종 musl-libc에 의지 할 수 있지만 C ++ 프로그램에서는 상황이 더 복잡해질 수 있으므로 여전히 기호 버전을 지정해야 할 수 있습니다.
0xC0000022L

16

설정 1 : 전용 GCC없이 자신의 glibc를 컴파일하고 사용

심볼 버전 관리 해킹만으로는 불가능 해 보이므로 한 단계 더 나아가 glibc를 직접 컴파일 해 보겠습니다.

이 설정은 작동 할 수 있으며 전체 GCC 도구 모음을 다시 컴파일하지 않고 glibc 만 다시 컴파일하므로 빠릅니다.

그것뿐만 호스트 C 런타임 객체는 사용하지만 이는 신뢰성없는 crt1.o, crti.o그리고 crtn.oglibc에 의해 제공. https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location 에서 언급됩니다. 일이 멋진 추락하면 나는 놀라지 않을 것이다, 그래서 그 객체가, glibc는가에 의존 초기 설정을 그리고 놀랍도록 미묘한 방법.

보다 안정적인 설정은 아래의 설정 2를 참조하십시오.

glibc를 빌드하고 로컬에 설치합니다.

export glibc_install="$(pwd)/glibc/build/install"

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make -j `nproc`
make install -j `nproc`

설정 1 : 빌드 확인

test_glibc.c

#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int acnt;
int cnt;

int f(void* thr_data) {
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
    }
    return 0;
}

int main(int argc, char **argv) {
    /* Basic library version check. */
    printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());

    /* Exercise thrd_create from -pthread,
     * which is not present in glibc 2.27 in Ubuntu 18.04.
     * /programming/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

다음으로 컴파일하고 실행하십시오 test_glibc.sh.

#!/usr/bin/env bash
set -eux
gcc \
  -L "${glibc_install}/lib" \
  -I "${glibc_install}/include" \
  -Wl,--rpath="${glibc_install}/lib" \
  -Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \
  -std=c11 \
  -o test_glibc.out \
  -v \
  test_glibc.c \
  -pthread \
;
ldd ./test_glibc.out
./test_glibc.out

프로그램은 예상 된 결과를 출력합니다.

gnu_get_libc_version() = 2.28
The atomic counter is 10000
The non-atomic counter is 8674

https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location 에서 조정 된 명령 이지만 --sysroot다음과 같이 실패했습니다.

cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install

그래서 제거했습니다.

ldd출력 ldd은 방금 빌드 한 및 라이브러리가 실제로 예상대로 사용되고 있는지 확인합니다 .

+ ldd test_glibc.out
        linux-vdso.so.1 (0x00007ffe4bfd3000)
        libpthread.so.0 => /home/ciro/glibc/build/install/lib/libpthread.so.0 (0x00007fc12ed92000)
        libc.so.6 => /home/ciro/glibc/build/install/lib/libc.so.6 (0x00007fc12e9dc000)
        /home/ciro/glibc/build/install/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc12f1b3000)

gcc이전에 언급,하지만 난 그것을 해결할 방법을 알고하지 않는 나쁜 나의 호스트 런타임 개체가 사용 된 것을 컴파일 디버그 출력 쇼, 예를 들어, 그것은 포함 :

COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o

설정 1 : glibc 수정

이제 다음과 같이 glibc를 수정 해 보겠습니다.

diff --git a/nptl/thrd_create.c b/nptl/thrd_create.c
index 113ba0d93e..b00f088abb 100644
--- a/nptl/thrd_create.c
+++ b/nptl/thrd_create.c
@@ -16,11 +16,14 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */

+#include <stdio.h>
+
 #include "thrd_priv.h"

 int
 thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
 {
+  puts("hacked");
   _Static_assert (sizeof (thr) == sizeof (pthread_t),
                   "sizeof (thr) != sizeof (pthread_t)");

그런 다음 glibc를 다시 컴파일하고 다시 설치하고 프로그램을 다시 컴파일하고 다시 실행합니다.

cd glibc/build
make -j `nproc`
make -j `nproc` install
./test_glibc.sh

hacked예상대로 몇 번 인쇄 된 것을 볼 수 있습니다.

이것은 우리가 호스트가 아닌 컴파일 한 glibc를 실제로 사용했음을 확인합니다.

Ubuntu 18.04에서 테스트되었습니다.

설정 2 : crosstool-NG pristine 설정

이 설정 1의 대안이며, 그것은 내가 지금까지 달성 한 가장 올바른 설정입니다 : 모든 것을 내가 C 런타임과 같은 개체를 포함하여 관찰 할 수있는만큼 지금까지 올바른지 crt1.o, crti.o그리고 crtn.o.

이 설정에서는 원하는 glibc를 사용하는 전체 전용 GCC 도구 모음을 컴파일합니다.

이 방법의 유일한 단점은 빌드 시간이 더 오래 걸린다는 것입니다. 그러나 나는 그 이하로 생산 설정을 위험에 빠뜨리지 않을 것입니다.

crosstool-NG 는 GCC, glibc 및 binutils를 포함하여 우리를 위해 소스에서 모든 것을 다운로드하고 컴파일하는 스크립트 세트입니다.

예, GCC 빌드 시스템이 너무 나빠서 별도의 프로젝트가 필요합니다.

crosstool-NG는 추가 플래그 없이 실행 파일 빌드를 지원하지 않기 때문에이 설정은 완벽 하지 않습니다.-Wl . GCC 자체를 빌드했기 때문에 이상하게 느껴집니다. 그러나 모든 것이 작동하는 것처럼 보이므로 이것은 불편할뿐입니다.

crosstool-NG를 가져 와서 구성하십시오.

git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5
export CT_PREFIX="$(pwd)/.build/install"
export PATH="/usr/lib/ccache:${PATH}"
./bootstrap
./configure --enable-local
make -j `nproc`
./ct-ng x86_64-unknown-linux-gnu
./ct-ng menuconfig

내가 볼 수있는 유일한 필수 옵션은 올바른 커널 헤더를 사용하기 위해 호스트 커널 버전과 일치하도록 만드는 것입니다. 다음을 사용하여 호스트 커널 버전을 찾으십시오.

uname -a

나를 보여줍니다 :

4.15.0-34-generic

그래서 menuconfig나는 :

  • Operating System
    • Version of linux

그래서 다음을 선택합니다.

4.14.71

첫 번째 동일하거나 이전 버전입니다. 커널이 이전 버전과 호환되므로 이전 버전이어야합니다.

이제 다음으로 빌드 할 수 있습니다.

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

이제 편집을 위해 약 30 분에서 2 시간을 기다립니다.

설정 2 : 선택적 구성

.config우리는 생성하는 것이 ./ct-ng x86_64-unknown-linux-gnu있다 :

CT_GLIBC_V_2_27=y

이를 변경하려면 다음을 menuconfig수행하십시오.

  • C-library
  • Version of glibc

를 저장하고 .config빌드를 계속합니다.

또는 자신의 glibc 소스를 사용하려면, 예를 들어 최신 git에서 glibc를 사용하려면 다음 과 같이 진행 하십시오 .

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: true로 설정
  • C-library
    • Source of glibc
      • Custom location: 예라고
      • Custom location
        • Custom source location: glibc 소스를 포함하는 디렉토리를 가리 킵니다.

glibc는 다음과 같이 복제되었습니다.

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28

설정 2 : 테스트

원하는 툴체인을 구축했으면 다음을 사용하여 테스트하십시오.

#!/usr/bin/env bash
set -eux
install_dir="${CT_PREFIX}/x86_64-unknown-linux-gnu"
PATH="${PATH}:${install_dir}/bin" \
  x86_64-unknown-linux-gnu-gcc \
  -Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/ld-linux-x86-64.so.2" \
  -Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \
  -v \
  -o test_glibc.out \
  test_glibc.c \
  -pthread \
;
ldd test_glibc.out
./test_glibc.out

이제 올바른 런타임 개체가 사용되었다는 점을 제외하면 모든 것이 설정 1에서와 같이 작동하는 것 같습니다.

COLLECT_GCC_OPTIONS=/home/ciro/crosstool-ng/.build/install/x86_64-unknown-linux-gnu/bin/../x86_64-unknown-linux-gnu/sysroot/usr/lib/../lib64/crt1.o

설정 2 : 효율적인 glibc 재 컴파일 시도 실패

아래에 설명 된대로 crosstool-NG로는 불가능 해 보입니다.

재건 만하면

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

그런 다음 사용자 지정 glibc 소스 위치에 대한 변경 사항이 고려되지만 모든 것을 처음부터 빌드하므로 반복 개발에 사용할 수 없습니다.

우리가 할 경우 :

./ct-ng list-steps

빌드 단계에 대한 멋진 개요를 제공합니다.

Available build steps, in order:
  - companion_tools_for_build
  - companion_libs_for_build
  - binutils_for_build
  - companion_tools_for_host
  - companion_libs_for_host
  - binutils_for_host
  - cc_core_pass_1
  - kernel_headers
  - libc_start_files
  - cc_core_pass_2
  - libc
  - cc_for_build
  - cc_for_host
  - libc_post_cc
  - companion_libs_for_target
  - binutils_for_target
  - debug
  - test_suite
  - finish
Use "<step>" as action to execute only that step.
Use "+<step>" as action to execute up to that step.
Use "<step>+" as action to execute from that step onward.

그러므로, 우리는 몇 가지 GCC 단계 얽혀 glibc는 단계가 볼 특히 libc_start_files전에 제공 cc_core_pass_2과 함께 가능성이 가장 비싼 단계 인 cc_core_pass_1.

한 단계 만 빌드하려면 먼저 .config초기 빌드 옵션에서 "중간 단계 저장"을 설정해야합니다 .

  • Paths and misc options
    • Debug crosstool-NG
      • Save intermediate steps

그런 다음 시도해 볼 수 있습니다.

env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc`

하지만 안타깝게도 https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536에+ 언급 된 필수 항목입니다.

그러나 중간 단계에서 다시 시작하면 설치 디렉토리가 해당 단계 동안의 상태로 재설정됩니다. 즉, 다시 빌드 된 libc가 있지만이 libc로 빌드 된 최종 컴파일러는 없습니다 (따라서 libstdc ++와 같은 컴파일러 라이브러리도 없음).

그리고 기본적으로 여전히 리빌드가 너무 느려서 개발에 적합하지 않으며 crosstool-NG를 패치하지 않고는 이것을 극복하는 방법을 알 수 없습니다.

또한 libc단계 에서 시작 하면에서 소스를 다시 복사하지 않아이 Custom source location방법을 사용할 수 없게되었습니다.

보너스 : stdlibc ++

C ++ 표준 라이브러리에도 관심이 있다면 보너스 : GCC libstdc ++ C ++ 표준 라이브러리 소스를 편집하고 다시 빌드하는 방법은 무엇입니까?


musl-libcC 런타임에 관한 한 또 다른 옵션입니다.
0xC0000022L

0

제 생각에는 가장 게으른 솔루션 (특히 최신 C / C ++ 기능 또는 최신 컴파일러 기능에 의존하지 않는 경우)이 아직 언급되지 않았으므로 여기에 있습니다.

여전히 지원하고 싶은 가장 오래된 GLIBC로 시스템을 구축하십시오.

이것은 chroot, KVM / Virtualbox 또는 docker와 같은 기술을 사용하여 오늘날 매우 쉽게 수행 할 수 있습니다. 심지어 PC에서 직접 이러한 오래된 배포판을 사용하고 싶지 않더라도 말입니다. 자세히, 소프트웨어의 최대 이식 가능한 바이너리를 만들려면 다음 단계를 따르는 것이 좋습니다.

  1. 샌드 박스 / 가상화 / ... 무엇이든 선택하고이를 사용하여 가상의 오래된 Ubuntu LTS를 얻고 기본적으로 포함 된 gcc / g ++로 컴파일하십시오. 그러면 GLIBC가 해당 환경에서 사용 가능한 것으로 자동 제한됩니다.

  2. 기본 라이브러리 외부의 외부 라이브러리에 의존하는 것을 피하십시오. 가능한 경우 libs / 함께 배송하십시오. 특히 이미지 로더, 멀티미디어 디코더 등과 같은 대부분의 자체 포함 된 라이브러리는 정적으로 제공하는 경우 다른 배포판에서 손상을 덜 일으킬 수 있습니다 (예 : 다른 주요 버전에있는 경우에만 손상이 발생할 수 있음).

이러한 접근 방식을 사용하면 완전히 정적 바이너리를 수행하지 않고 (glibc가이를 싫어하고 라이선스 문제를 유발할 수 있기 때문에 더 복잡한 프로그램에서 중단 될 수 있음), 설정없이 수동 기호 조정없이 이전 GLIBC 호환 바이너리를 얻을 수 있습니다. 사용자 지정 도구 모음, 사용자 지정 glibc 복사본 등

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.