Замена общего объекта (.so файл) во время работы основной программы - PullRequest
16 голосов
/ 14 октября 2011

У меня есть общий объект gateway.so (в Linux / C).И приложение .out использует его.

ВОПРОС A

Я предполагаю: при запуске процесса a.out загрузчик загружает gateway.so (я не использую функции dl, такие как dlopen).Таким образом, все разрешения символов времени выполнения для gateway.so будут происходить в памяти.Ему больше не нужно обращаться к gateway.so с диска.

Я прав?

Поэтому я не могу заменить gateway.so обновленной версией, пока работает a.out, верно?

ВОПРОС B

Еще один связанный с этим вопрос: Однажды, когда я подставил и устарел версию файла gateway.so, я получил сообщение

"a.out: не удается распознать символ 'Test_OpenGateway' "

Какой программный компонент (загрузчик / компоновщик ...) отправляет этот вывод?Этот компонент выполняется как часть того же контекста процесса?

Ответы [ 4 ]

28 голосов
/ 15 октября 2011

Вопрос A

Вы можете заменить библиотеку во время ее использования приложением, если вы сделаете это правильно.

Прежде чем мы туда доберемся, давайте посмотримпосмотрите на основной двоичный файл программы.Вот пример программы:

#include <unistd.h>

void justsit(void) {
  for (;;) {
    sleep(1);
  }
}

int main(int argc, char **argv) {
  printf("My PID is %d\n", getpid());
  justsit();
  return 0;
}

Скомпилируйте и запустите ее:

$ gcc -Wall -o example example.c
$ ./example
My PID is 4339

Теперь она просто будет сидеть там, поэтому откройте новый терминал, чтобы сделать это:

$ gcc -Wall -o example-updated example.c
$ cp example-updated example
cp: cannot create regular file `example': Text file busy

Что случилось сейчас?Ядро отказалось изменить файл пример , поскольку у него есть процесс, который запускает этот файл.

Теперь давайте попробуем удалить его:

$ rm example

Что?Что сработало?Почему файл можно удалить, но не заменить?Да, или, скорее, файл не был действительно удален, просто «имя», ядро ​​сообщает файловой системе, чтобы сохранить содержимое файла.Если файл больше не открыт, содержимое также удаляется.( dentry удаляется немедленно, но inode освобождается, когда у него нет пользователей, как сказали бы люди из файловой системы)

Это можно увидеть в / proc: (Вот почему программа печатает свой PID, чтобы вы могли легко проверить это)

$ readlink /proc/4339/exe
/tmp/t/example (deleted)

Во всяком случае.Тот факт, что он работает так, означает, что можно безопасно обновить программу, удалив старый двоичный файл и поместив новый в то же место.Для этого есть программа: install (1).

Хорошо, вернемся к вашему вопросу - общие объекты.

Давайте разберем пример на две части, main.c и shared.c:

/* main.c */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void justsit(void);

int main(int argc, char **argv) {
  printf("My PID is %d\n", getpid());
  justsit();
  return 0;
}

и

/* shared.c */
#include <stdio.h>
#include <unistd.h>

void justsit(void) {
  for (;;) {
    sleep(1);
  }
}

Скомпилируйте их так:

$ gcc -Wall --shared -o libshared.so shared.c 
$ gcc -Wall -L. -o main main.c -lshared

Теперь, надеюсь, если мы попытаемся заменить libshared.so, мы получим подобное "Текстовый файл занят "ошибка?Посмотрим.Сначала запустите основную программу - текущий каталог не находится в пути поиска lib, поэтому скажите динамическому компоновщику искать там:

$ LD_LIBRARY_PATH=. ./main 
My PID is 5697

Перейдите на другой терминал и замените библиотеку чем-то явно неработающим:

$ echo "junk" > libshared.so 
$

Во-первых - не было отказано, как замена двоичного файла программы.А в другом терминале произошло что-то интересное, программа перестала работать со следующим сообщением об ошибке:

Segmentation fault
$

Поэтому НЕ ЗАМЕНЯЕТСЯ заменить библиотеку, используемую программой!Но, как видно из приведенного выше примера, это может иметь катастрофические последствия.

К счастью, тот же «трюк», который использовался для замены работающего двоичного файла, можно использовать для замены используемой библиотеки.Перезапустите основную программу (не забудьте также перекомпилировать libshared.so, поскольку она была заменена нежелательной) и посмотрите, насколько безопасно выполнять rm в библиотеке./ proc / PID / maps можно проверить, какие общие объекты использует процесс:

$ cat /proc/5733/maps  | grep libshared.so
008a8000-008a9000 r-xp 00000000 08:01 2097292    /tmp/t/libshared.so
008a9000-008aa000 r--p 00000000 08:01 2097292    /tmp/t/libshared.so
008aa000-008ab000 rw-p 00001000 08:01 2097292    /tmp/t/libshared.so
$ rm libshared.so 
$ cat /proc/5733/maps  | grep libshared.so
008a8000-008a9000 r-xp 00000000 08:01 2097292    /tmp/t/libshared.so (deleted)
008a9000-008aa000 r--p 00000000 08:01 2097292    /tmp/t/libshared.so (deleted)
008aa000-008ab000 rw-p 00001000 08:01 2097292    /tmp/t/libshared.so (deleted)

Основная программа продолжает работать нормально.Опять же, это потому, что с диска было удалено только имя (dentry), а не фактическое содержимое (inode).После удаления можно безопасно создать новый файл с именем libshared.so, не затрагивая работающую программу.

Итак, подведем итог - просто используйте команду install для установки программ и двоичных файлов.

Вопрос B

Да, распечатывается динамическим компоновщиком в пользовательском пространстве.

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    execl("./main", "main", NULL);
    printf("exec failed?\n");
    return 0;
}

Скомпилируйте его с помощью gcc -Wall -o execit execit.c.Помните, что execl заменяет текущий процесс указанной командой.

$ ./execit 
main: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory
$ rm main
$ ./execit 
exec failed?

Что произошло и что это нам говорит?Сначала есть error while loading shared libraries без exec failed?.Никакой «сбой exec» не предполагает, что процесс был успешно заменен.Это означает, что ядро ​​передало управление динамическому компоновщику, который вышел из строя.После удаления «main» он рано выходит из строя и процесс не заменяется.

10 голосов
/ 14 октября 2011

Нет, возможно, файл все еще нужно будет прочитать с диска, как только компоновщик времени выполнения (ld.so) отобразит его в адресном пространстве процесса.Это сопоставление происходит с помощью системного вызова mmap(2) и флага PROT_EXEC, чтобы разрешить выполнение.

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

ОтносительноВторой вопрос, это компоновщик времени выполнения (ld.so), который жалуется на это.Код, который загружает ld.so, генерируется компоновщиком времени компиляции в качестве кода запуска программы (ld), поэтому он выполняется в пространстве пользователя до вызова main.

2 голосов
/ 14 октября 2011

К: Да, действительно, когда общая библиотека отображается в памяти, вы больше не можете ее заменить. Может даже случиться, что система уже загрузила предыдущую версию библиотеки для какого-либо другого процесса и обнаружит, что она уже сопоставлена ​​с памятью, и переназначит ее как часть процесса запуска. Вот почему вы всегда должны перезапускать (даже * nixes) после критических обновлений;)

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

0 голосов
/ 14 октября 2011

а.Правильно.В этом случае вы должны работать с dl_*() и закрыть файл как можно скорее.

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

...