Проста перевірка на наявність невирішених символів у спільних бібліотеках?


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

3
+1 ця відповідь може допомогти хлопцям, які походили з фонового режиму Windows, в якому зовнішні символи DLL повинні бути вирішені під час компіляції. Хороший фрагмент коду!
Кот

@ShmilTheCat: Привіт, я спробував вставити -Wl, -z, defs у свій файл make, все одно я отримую невизначену помилку символу під час запуску, але не під час компіляції. Що я можу зробити?. У моєму сценарії у мене є дві папки одна в іншій (скажімо, A / B, i, e B знаходиться всередині A), і я бачу кілька символів або B у "libA.so". Я не впевнений, чи кожен символ у B доступний у "libA.so". Як я можу це переконатись?
Vinay

@ShmilTheCat Щоб зробити речі ще більше схожими на бібліотеку DLL Windows, ви можете додати -Bsymbolicдо командного рядка компонувальника. Навіть якщо forgot_to_defineзараз існує в бібліотеці завдяки -zперевірці, виконуваний файл все одно може замінити його своїм власним визначенням, і власні визначення бібліотеки перейдуть до цього заміщення; -Bsymbolicзмушує речі так, що власні визначення бібліотеки своїх функцій переходять до її функцій.
Каз

Працює лише для фактично використовуваних функцій. Якщо я // forgot_to_define(fp);це зроблю, то не повідомляю про помилку
agentmith

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. Згрупуйте свої функції у відомі та добре зрозумілі розділи, щоб ви могли згрупувати функції / заглушки, щоб переконатися, що кожна відповідна функція має свою заглушку. Якщо ви витратите час на те, щоб це добре документувати, ви можете написати, можливо, скрипт, який перевіряє визначення (наприклад, за допомогою коментарів до кисню) та перевіряє відповідний файл .cpp для нього.
  3. Виконайте кілька тестових виконуваних файлів, які завантажують один і той же набір бібліотек, і вкажіть прапор RTLD_NOW, який потрібно відкрити (якщо ви перебуваєте у * NIX). Вони сигналізуватимуть про відсутні символи.

Сподіваюся, що це допомагає.


у рядках RTLD_NOW, чи існує env var, щоб змусити негайне (не ледаче) зв’язування?
Стефано Боріні

1
Так. тут це LD_BIND_NOW (libc5; glibc з 2.1.1). Якщо встановлено не пустий рядок, змушує динамічний компонувальник вирішувати всі символи під час запуску програми, замість того, щоб відкласти вирішення виклику функції до точки, на яку вони вперше посилаються. Це корисно при використанні налагоджувача.
Стефано Боріні

Це також може бути корисним для цілей OP: LD_WARN (лише ELF) (glibc з 2.1.3) Якщо встановлено значення, що не є порожнім рядком, попереджайте про невирішені символи.
Стефано Боріні

Стефано: Так, саме так. Ці змінні існують. Однак, якщо ви відкладаєте динамічне завантаження на потім (скажімо, коли користувач завантажує модуль), ці змінні не будуть корисними, якщо ви насправді не напишете програму тестування, яка завантажує передбачувані (майбутні) бібліотеки.
Дієго Севілья
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.