Подтвердить что ты не робот

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

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

ВОПРОС A

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

Я прав?

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

ВОПРОС B

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Вопрос 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, мы получим аналогичную ошибку "Text file busy"? Посмотрим. Сначала запустите основную программу - текущий каталог не находится в пути поиска lib, поэтому скажите динамическому компоновщику для поиска там:

$ LD_LIBRARY_PATH=. ./main 
My PID is 5697

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

$ echo "junk" > libshared.so 
$

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

Segmentation fault
$

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

К счастью, тот же "трюк", который использовался для замены исполняемого двоичного файла, может быть использован для замены используемого lib. Перезагрузите основную программу (не забудьте перекомпилировать 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 failed" предполагает, что процесс был успешно заменен. Это означает, что ядро ​​передало управление динамическому компоновщику, который потерпел неудачу. После того, как "main" был удален, он не срабатывает раньше и процесс не заменяется.

Ответ 3

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

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

Ответ 4

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

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