Как я могу сделать ссылку на конкретную версию glibc?

111

Когда я компилирую что-то на своем ПК с Ubuntu Lucid 10.04, оно связывается с glibc. Lucid использует glibc версии 2.11. Когда я запускаю этот двоичный файл на другом компьютере с более старой версией glibc, команда не сообщает, что glibc 2.11 ...

Насколько мне известно, в glibc используется управление версиями символов. Могу ли я заставить gcc ссылаться на определенную версию символа?

В моем конкретном использовании я пытаюсь скомпилировать кросс-инструментальную цепочку gcc для ARM.

фальстаф
источник
58
Ага, это одна из тех действительно раздражающих проблем Linux, где решение всегда «вы не должны этого делать», что, конечно же, означает «это не работает, и никто еще не исправил это».
Timmmm
4
Люди жаловались на ад DLL в Windows. Я помню, как некоторые поклонники Linux пытались привести это как особенно ужасный пример из мира Windows. Когда я впервые столкнулся с этим при разработке Linux более десяти лет назад, все, что я сделал, - это закрыл лицо руками.
0xC0000022L

Ответы:

70

Вы правы в том, что glibc использует управление версиями символов. Если вам интересно, реализация управления версиями символов, представленная в glibc 2.1, описана здесь и является расширением схемы управления версиями символов Sun, описанной здесь .

Один из вариантов - статически связать ваш двоичный файл. Это, наверное, самый простой вариант.

Вы также можете собрать свой двоичный файл в среде сборки chroot или с помощью кросс-компилятора glibc- new => glibc- old .

Согласно сообщению в блоге http://www.trevorpounds.com Связывание со старыми версиями символов (glibc) , можно принудительно связать любой символ с более старым, если он действителен, используя тот же .symverпсевдо -op, который используется в первую очередь для определения версионных символов. Следующий пример взят из сообщения в блоге .

В следующем примере используется реальный путь glibc, но проверяется, что он связан с более старой версией 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;
}
jschmier
источник
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: скомпилируйте свой собственный glibc без выделенного GCC и используйте его

Поскольку кажется невозможным обойтись только с помощью хаков управления версиями символов, давайте сделаем еще один шаг и скомпилируем glibc сами.

Эта установка может работать и выполняется быстро, поскольку она не перекомпилирует всю цепочку инструментов GCC, а только glibc.

Но это не так надежно , как он использует такие объекты хоста C во время выполнения , как crt1.o, crti.oи crtn.oобеспечивается Glibc. Это упоминается по адресу: 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: первоначальная настройка кросс-стула-NG

Это является альтернативой установке 1, и это наиболее правильная установка я достиг далеко: все правильно, насколько я могу наблюдать, в том числе во время выполнения C объекты , такие как crt1.o, crti.o, и crtn.o.

В этой настройке мы скомпилируем полную специализированную цепочку инструментов GCC, которая использует желаемый glibc.

Единственным недостатком этого метода является то, что сборка займет больше времени. Но я бы не стал рисковать производственной установкой на меньшее.

crossstool-NG - это набор сценариев, который загружает и компилирует для нас все из исходников, включая GCC, glibc и binutils.

Да, система сборки GCC настолько плоха, что для этого нужен отдельный проект.

Эта установка не идеальна только потому, что crossstool-NG не поддерживает сборку исполняемых файлов без дополнительных -Wlфлагов , что кажется странным, поскольку мы создали сам GCC. Но вроде все работает, так что это только неудобства.

Получите crossstool-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: дополнительные конфигурации

То, .configчто мы создали, ./ct-ng x86_64-unknown-linux-gnuимеет:

CT_GLIBC_V_2_27=y

Чтобы изменить это, menuconfigвыполните:

  • C-library
  • Version of glibc

сохраните .configи продолжите сборку.

Или, если вы хотите использовать свой собственный источник glibc, например, чтобы использовать glibc из последней версии git, действуйте следующим образом :

  • 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

Как поясняется ниже, это невозможно с crossstool-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.

следовательно, мы видим, что есть шаги glibc, переплетенные с несколькими шагами GCC, в первую очередь 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 ++).

и, по сути, по-прежнему делает перестройку слишком медленной, чтобы ее можно было реализовать в разработке, и я не вижу, как это преодолеть без исправления crossstool-NG.

Кроме того, начиная с libcшага, похоже, не было повторного копирования источника Custom source location, что еще больше сделало этот метод непригодным для использования.

Бонус: stdlibc ++

Бонус, если вас также интересует стандартная библиотека C ++: как отредактировать и пересобрать исходный код стандартной библиотеки GCC libstdc ++ C ++?

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
musl-libc- это еще один вариант для среды выполнения C.
0xC0000022L 09
0

На мой взгляд, самое ленивое решение (особенно если вы не полагаетесь на новейшие функции C / C ++ или новейшие функции компилятора) еще не упоминалось, поэтому вот оно:

Просто создайте систему с самой старой GLIBC, которую вы все еще хотите поддерживать.

В настоящее время это довольно просто сделать с помощью таких технологий, как chroot, KVM / Virtualbox или docker, даже если вы действительно не хотите использовать такой старый дистрибутив непосредственно на любом компьютере. В деталях, чтобы сделать максимально переносимый двоичный файл вашего программного обеспечения, я рекомендую выполнить следующие шаги:

  1. Просто выберите свой яд в виде песочницы / виртуализации / ... чего угодно, и используйте его, чтобы получить себе виртуальную старую версию Ubuntu LTS и скомпилировать с помощью gcc / g ++, которая есть по умолчанию. Это автоматически ограничивает ваш GLIBC тем, который доступен в этой среде.

  2. Избегайте зависимости от внешних библиотек за пределами основных: например, вы должны динамически связывать системные вещи нижнего уровня, такие как glibc, libGL, libxcb / X11 / wayland, libasound / libpulseaudio, возможно GTK +, если вы это используете, но в противном случае предпочтительнее статически связывать внешние libs / отправьте их, если можете. В частности, в большинстве случаев автономные библиотеки, такие как загрузчики изображений, мультимедийные декодеры и т. Д., Могут вызывать меньше поломок в других дистрибутивах (поломка может быть вызвана, например, если они присутствуют только где-то в другой основной версии), если вы отправляете их статически.

При таком подходе вы получаете двоичный файл, совместимый со старым GLIBC, без каких-либо ручных настроек символов, без выполнения полностью статического двоичного файла (который может сломаться для более сложных программ, потому что glibc ненавидит это и может вызвать проблемы с лицензированием для вас) и без настройки создать любую настраиваемую цепочку инструментов, любую настраиваемую копию glibc или что-то еще.

ET
источник