Неопределенная ссылка при связывании с общим объектом

#c #linker #shared-libraries #loader

#c #компоновщик #совместно используемые библиотеки #погрузчик

Вопрос:

Я исследую тему общих библиотек. Насколько я понял, при связывании исходного файла с общей библиотекой для формирования исполняемого файла неразрешенные символы будут оставаться неразрешенными до их первого вызова, затем отложенное связывание разрешит их. Исходя из этого, я предположил, что использование функции, которая нигде не была определена, не приведет к возникновению ошибки компоновщика, так как это оставит задачу разрешения динамическому компоновщику. Но когда я ввел следующие команды в терминале:

 gcc -c foo.c -fPIC
gcc -shared foo.o -o libfoos.so
gcc  main.c -Wl,-rpath=. libfoos.so
 

Я получил сообщение об ошибке «неопределенная ссылка на ‘foo2′».

Все это было сделано со следующими файлами в том же каталоге:

фу.х:

 #ifndef __FOO_H__
#define __FOO_H__

int foo(int num);

#endif /* __FOO_H__ */
 

main.с:

 #include <stdio.h>

#include "foo.h"

int main()
{
    int a = 5;
    printf("%d * %d = %dn", a, a, foo(a));
    printf("%d   %d = %dn", a, a, foo2(a));
    
    return (0);
}
 

и foo.c:

 #include "foo.h"

int foo(int num)
{
    return (num * num);
}
 

Итак, мои вопросы таковы:

  1. Правда ли, что символы остаются неразрешенными до тех пор, пока они не будут вызваны в первый раз? Если да, то почему я получаю сообщение об ошибке во время компоновки?
  2. Я предполагаю, что, возможно, необходимо выполнить некоторую проверку относительно самого существования символов (foo и foo2 в моем примере) в общей библиотеке, уже во время связывания. Если да, то почему бы не разрешить их уже в одно и то же время, поскольку мы все равно получаем доступ к некоторой информации в библиотеке?

Спасибо!

Ответ №1:

  1. Правда ли, что символы остаются неразрешенными до тех пор, пока они не будут вызваны в первый раз?

Я думаю, вы, возможно, путаете требования и семантику исходного языка (C) с семантикой выполнения динамических форматов и реализаций общих объектов, таких как ELF.

Язык C не определяет, когда разрешаются символы, только то, что должно быть определение для каждого идентификатора, который используется для доступа к объекту или вызова функции.

Различные форматы DSO имеют разные свойства внутри и вокруг этого. С ELF, например, разрешение динамических символов может быть отложено до тех пор, пока на символ не будет впервые сделана ссылка, или это может быть выполнено сразу после загрузки DSO. Это настраивается как во время выполнения, так и во время компиляции. Семантика других форматов DSO может отличаться в этом и других отношениях.

Итог: нет, не обязательно верно, что динамические символы разрешаются только при первом обращении к ним, но это может быть по умолчанию для вашей конкретной реализации и среды.

Если да, то почему я получаю сообщение об ошибке во время компоновки?

Компоновщик проверяет требования к языку C во время сборки. Это вполне разумно и на самом деле желательно, чтобы это делалось даже при создании общих объектов, поскольку, если используется неразрешимый символ, тогда хотелось бы узнать о проблеме и исправить ее, прежде чем люди попытаются использовать программу. Это не связано с тем, откладывается ли динамическое разрешение символов во время выполнения.

  1. Я предполагаю, что, возможно, необходимо выполнить некоторую проверку относительно самого существования символов (foo и foo2 в моем примере) в общей библиотеке, уже во время связывания.

Да, в принципе, так оно и есть.

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

Откуда ты знаешь, что этого не произойдет?

В системе DSO, которая не поддерживает перемещение символов, это может быть сделано и сделано. Динамизм в такой системе DSO заключается прежде всего в том, загружается ли данная библиотека вообще. DSO в такой системе имеют фиксированные адреса загрузки, и символы, экспортируемые из них, также имеют фиксированные адреса. Это позволяет исполняемым файлам быть (намного) меньше и использовать системную память (намного) эффективнее по сравнению со статически связанными исполняемыми файлами.

Но с таким подходом возникают большие практические проблемы. Например, вам приходится сталкиваться с конфликтами адресного пространства между различными DSO, обновление DSO является сложным и рискованным, а наличие хорошо известных адресов представляет угрозу безопасности. Поэтому большинство современных систем DSO имеют функцию перемещения символов. В такой системе адреса загрузки DSO определяются динамически во время выполнения, и обычно даже относительные смещения, представленные их экспортируемыми символами, не являются фиксированными. Это система DSO, которая поддерживает отложенное разрешение символов, и с такой системой символы из других DSO не могут быть разрешены во время сборки, потому что они неизвестны до времени выполнения, и они могут даже меняться от запуска к запуску.