Легко проверить наличие неразрешенных символов в общих библиотеках?

83

Я пишу довольно большую библиотеку общих объектов C ++ и столкнулся с небольшой проблемой, которая затрудняет отладку:

Если я определяю функцию / метод в файле заголовка и забываю создать для него заглушку (во время разработки), поскольку я создаю как библиотеку общих объектов, а не исполняемый файл, во время компиляции не появляются ошибки, сообщающие мне, что у меня есть забыл реализовать эту функцию. Единственный способ узнать, что что-то не так, - это во время выполнения, когда в конечном итоге приложение, связывающееся с этой библиотекой, падает с ошибкой «неопределенный символ».

Я ищу простой способ проверить, есть ли у меня все символы, которые мне нужны, во время компиляции, возможно, что-то, что я могу добавить в свой Makefile.

Одно из решений, которое я придумал, - это запустить скомпилированную библиотеку, nm -C -Uчтобы получить разобранный список всех неопределенных ссылок. Проблема в том, что здесь также появляется список всех ссылок, которые находятся в других библиотеках, таких как GLibC, которые, конечно, будут связаны вместе с этой библиотекой, когда окончательное приложение будет собрано. Можно было бы использовать вывод nmto для grepвсех моих файлов заголовков и посмотреть, соответствует ли какое-либо из имен .. но это кажется безумным. Конечно, это не редкость, и есть способ лучше ее решить?

Дэвид Кларидж
источник
3
nm -C -uспас меня несколько раз! (обратите внимание на строчные буквы -uв моей системе.) Оставляю этот комментарий здесь, чтобы я мог найти его в следующий раз, когда он мне понадобится.
dpritch

Ответы:

94

Проверьте опцию компоновщика -z defs/ --no-undefined. При создании общего объекта это приведет к сбою ссылки, если есть неразрешенные символы.

Если вы используете gcc для вызова компоновщика, вы будете использовать параметр компилятора, -Wlчтобы передать параметр компоновщику:

gcc -shared ... -Wl,-z,defs

В качестве примера рассмотрим следующий файл:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

Теперь, если вы встроите это в общий объект, все получится:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

Но если вы добавите -z defs, ссылка не будет работать и сообщит вам о пропавшем символе:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed
R Сэмюэл Клатчко
источник
3
+1, этот ответ может помочь ребятам, пришедшим из фона Windows, в котором внешние символы DLL должны быть разрешены во время компиляции. Отличный фрагмент кода!
Кот Шмиль
@ShmilTheCat: Привет, я попытался поместить -Wl, -z, defs в свой файл make, но все равно получаю неопределенную символьную ошибку во время работы, но не во время компиляции. А что я могу сделать?. В моем сценарии у меня есть две папки одна внутри другой (скажем, A / B, i, e B находится внутри A), и я вижу несколько символов или B в "libA.so". Я не уверен, что каждый символ из B доступен в "libA.so". Как мне в этом убедиться?
Vinay
@ShmilTheCat Чтобы сделать вещи еще более похожими на Windows DLL, вы можете добавить -Bsymbolicих в командную строку компоновщика. Даже если forgot_to_defineтеперь он существует в библиотеке благодаря -zпроверке, исполняемый файл все равно может переопределить его своим собственным определением, и собственные определения библиотеки перейдут на это переопределение; -Bsymbolicзаставляет вещи так, чтобы собственные определения библиотеки ее функций переходили в ее функции.
Kaz
Работает только для реально используемых функций. Если я это сделаю, // forgot_to_define(fp);он не сообщит об ошибке
agentmith 05
19

В Linux (который, похоже, вы используете) ldd -r a.outдолжен дать вам именно тот ответ, который вы ищете.

ОБНОВЛЕНИЕ: тривиальный способ создания, a.outпо которому можно проверить:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out
Занятые русские
источник
4
Это делает почти то же самое, что и nm -C -U, что я предлагал в исходном посте. Проблема в том, что нет приложения 'a.out', о котором можно было бы говорить, это разделяемая библиотека, поэтому ldd -r mylibrary.so дает целую кучу вывода, потому что он использует символы из различных других динамических библиотек .. Мне особенно интересно в отсутствующих символах, которые определены в моих файлах заголовков, а не во внешних библиотеках.
Дэвид Кларидж,
2
это следует принять, вопрос в том, как проще всего проверить неопределенные символы, а не как избежать неопределенных символов
http8086
9

А как насчет набора тестов? Вы создаете фиктивные исполняемые файлы, которые ссылаются на нужные вам символы. Если связывание не удалось, это означает, что интерфейс вашей библиотеки неполный.

Стефано Борини
источник
4

Однажды у меня была такая же проблема. Я разрабатывал компонентную модель на C ++, и, конечно же, компоненты должны загружаться во время выполнения динамически. На ум приходят три решения, которые я применил:

  1. Найдите время, чтобы определить систему сборки, которая может компилироваться статически. Вы потеряете время на его разработку, но это сэкономит вам много времени на выявление этих надоедливых ошибок времени выполнения.
  2. Сгруппируйте свои функции по хорошо известным и понятным разделам, чтобы вы могли сгруппировать функции / заглушки, чтобы быть уверенными, что каждая соответствующая функция имеет свою заглушку. Если вы потратите время на его хорошее документирование, вы, возможно, сможете написать сценарий, который проверяет определения (например, через его комментарии doxygen) и проверяет соответствующий файл .cpp для него.
  3. Выполните несколько тестовых исполняемых файлов, которые загружают один и тот же набор библиотек, и укажите флаг RTLD_NOW для dlopen (если вы используете * NIX). Они будут сигнализировать об отсутствующих символах.

Надеюсь, это поможет.

Диего Севилья
источник
в строках RTLD_NOW, существует ли env var для немедленного (не ленивого) связывания?
Стефано Борини,
1
Ага. здесь это LD_BIND_NOW (libc5; glibc с версии 2.1.1). Если установлено значение непустой строки, заставляет динамический компоновщик разрешать все символы при запуске программы вместо того, чтобы откладывать разрешение вызова функции до точки, когда на них впервые ссылаются. Это полезно при использовании отладчика.
Стефано Борини,
Это также может быть полезно для OP: LD_WARN (только ELF) (glibc начиная с версии 2.1.3). Если установлено значение непустой строки, предупреждать о неразрешенных символах.
Стефано Борини,
Стефано: Да, верно. Эти переменные существуют. Однако, если вы откладываете динамическую загрузку на более позднее время (например, когда пользователь загружает модуль), эти переменные бесполезны, если вы не напишете программу тестирования, которая загружает предполагаемые (будущие) библиотеки.
Диего Севилья,