Изменение двоичного файла во время выполнения

Если Ваша система имеет соединение с Интернетом, используйте NTP для установки времени. Для дуги Linux можно найти инструкции здесь.

10
23.05.2017, 15:39
3 ответа

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

Как вы, возможно, знаете, системы UNIX обычно делятся на две подсистемы: файловую подсистему и подсистему процессов. Теперь, если в системном вызове не указано иное, ядро ​​не должно иметь этих двух подсистем, взаимодействующих друг с другом. Однако есть одно исключение: загрузка исполняемого файла в текстовые области процесса . Конечно, можно утверждать, что эта операция также запускается системным вызовом ( execve ), но обычно это один случай, когда подсистема процесса делает неявный запрос в файловую подсистему.

Поскольку подсистема процессов, естественно, не имеет возможности обрабатывать файлы (иначе не было бы смысла делить все на две части), она должна использовать все, что файловая подсистема предоставляет для доступа к файлам. Это также означает, что подсистема процесса подчиняется любой мере, которую файловая подсистема принимает в отношении редактирования / удаления файла. По этому поводу я бы рекомендовал прочитать ответ Жиля на этот вопрос U&L . Остальная часть моего ответа основана на более общем ответе Жиля.

Первое, что следует отметить, это то, что внутри файлы доступны только через inodes .Если ядру задан путь, его первым шагом будет преобразование его в индексный дескриптор, который будет использоваться для всех других операций. Когда процесс загружает исполняемый файл в память, он делает это через свой индексный дескриптор, который был предоставлен файловой подсистемой после преобразования пути. Inodes могут быть связаны с несколькими путями (ссылками), а программы могут только удалять ссылки. Чтобы удалить файл и его индексный дескриптор, пользовательское пространство должно удалить все существующие ссылки на этот индексный дескриптор и убедиться, что он полностью не используется. Когда эти условия выполнены, ядро ​​автоматически удалит файл с диска.

Если вы посмотрите на заменяющие исполняемые файлы часть ответа Жиля, вы увидите, что в зависимости от того, как вы редактируете / удаляете файл, ядро ​​будет реагировать / адаптироваться по-разному, всегда с помощью механизма, реализованного в файловой подсистеме.

  • Если вы попробуете стратегию один ( открыть / обрезать до нуля / записать или открыть / записать / обрезать до нового размера ), вы увидите, что ядро ​​не беспокоит обработка вашего запроса. Вы получите сообщение об ошибке 26: Текстовый файл занят ( ETXTBSY ). Никаких последствий.
  • Если вы попробуете стратегию номер два, первым шагом будет удаление исполняемого файла. Однако, поскольку он используется процессом, файловая подсистема включится и предотвратит истинное удаление файла (и его индексного дескриптора) с диска.С этого момента единственный способ получить доступ к содержимому старого файла - это сделать это через его индексный дескриптор, что и делает подсистема процесса всякий раз, когда ей необходимо загрузить новые данные в текстовые разделы (внутри нет указывает на использование путей, кроме случаев их перевода в inodes). Даже если вы отключили файл (удалили все его пути), процесс все равно может использовать его, как будто вы ничего не сделали. Создание нового файла со старым путем ничего не меняет: новому файлу будет присвоен совершенно новый индексный дескриптор, о котором запущенный процесс не знает.

Стратегии 2 и 3 также безопасны для исполняемых файлов: хотя запущенные исполняемые файлы (и динамически загружаемые библиотеки) не являются открытыми файлами в смысле наличия файлового дескриптора, они ведут себя очень похоже. Пока какая-то программа выполняет код, файл остается на диске даже без записи в каталоге.

  • Третья стратегия очень похожа, поскольку операция mv является атомарной. Это, вероятно, потребует использования системного вызова rename , а поскольку процессы не могут быть прерваны в режиме ядра, ничто не может помешать этой операции, пока она не завершится (успешно или нет). Опять же, нет изменения inode старого файла: создается новый, и уже запущенные процессы не будут знать о нем, даже если он был связан с одной из ссылок старого inode.

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

Перекомпиляция файла : при использовании gcc (и поведение, вероятно, аналогично для многих других компиляторов),вы используете стратегию 2. Вы можете увидеть, что запустив strace процессов вашего компилятора:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • Компилятор обнаруживает, что файл уже существует, с помощью stat и lstat системные вызовы.
  • Файл несвязан . Здесь, хотя он больше не доступен через имя a.out , его индексный дескриптор и содержимое остаются на диске до тех пор, пока они используются уже запущенными процессами.
  • Новый файл создается и становится исполняемым под именем a.out . Это совершенно новый индексный дескриптор и совершенно новое содержимое, которое уже запущенным процессам не важно.

Теперь, когда дело доходит до разделяемых библиотек, будет применяться то же поведение. Пока объект библиотеки используется процессом, он не будет удален с диска, как бы вы ни изменили его ссылки. Всякий раз, когда что-то должно быть загружено в память, ядро ​​будет делать это через индексный дескриптор файла и, следовательно, будет игнорировать изменения, внесенные вами в его ссылки (например, связывание их с новыми файлами).

11
27.01.2020, 20:01

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

Это, конечно, должно быть подтверждено.

2
27.01.2020, 20:01

Это можно сделать с помощью одиночного sed :

sed 's/\(.*\)-/\1 /'

или, используя расширенное регулярное выражение:

sed -r 's/(.*)-/\1 /'

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

$ echo 'swp-RedHat-Linux-OS-5.5.0.0-03' | sed 's/\(.*\)-/\1 /'
swp-RedHat-Linux-OS-5.5.0.0 03
-121--14343-

По соглашению мы проверяем переменную errno только при возникновении ошибки (например, при возврате некоторых функций с помощью -1).

Вопрос 1: Какое значение errno используется для 0 перед запуском программы?

Действительно, мы должны проверять errno только в случае, когда произошла ошибка. Это связано с тем, что если ошибка отсутствует, то возможно, что errno будет содержать ненулевое значение (например, если ошибка произошла во время выполнения вызова библиотеки, но ошибка была восстановлена).

Поэтому установка errno на 0 перед «запуском программы» не является необходимой, и я не стал бы следовать этому совету.

Более того, я прочитал, что лучше сохранить номер ошибки в локальной переменной, а затем проверить его

Да! Ваше наблюдение о том, что printf () может засорить errno , является правильным. Если его значение необходимо сохранить, его следует скопировать в локальную переменную как можно скорее после возникновения ошибки.

Вопрос 2: Применяется ли приведенное выше утверждение к perror () и strerror (), поскольку они также являются системными вызовами, и существует вероятность того, что ошибка произошла и с ними.

perror () , вероятно, не вызывает ничего, что изменяет errno , но если это так, то необходимо быть осторожным, чтобы скопировать значение errno , прежде чем это произойдет. Уверен, вы можете предположить, что perror () правильно работает в вашей системе!

strerror () не должен беспокоиться об этом, так как он принимает номер ошибки в качестве параметра, так что, даже если для clobber errno предыдущее значение уже сохранено.

При расширенном программировании в среде UNIX Ричарда Стивенса (Richard Stevens) мы должны проверить значение errno , только если возвращаемое значение функции указывает на то, что произошла ошибка. Я не понимаю, почему?

Потому что системные вызовы и вызовы библиотеки, которые устанавливают errno при сбое, не обязаны устанавливать его на успех, поэтому он сохраняет любое значение, которое он имел ранее.

-121--71595-

Это не всегда происходит при замене JAR-файла. Ресурсы JAR и некоторые загрузчики классов отражения во время выполнения не считываются с диска до тех пор, пока программа явно не запросит информацию.

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

Итак, для исполняемых файлов: да. Для баночных файлов: может быть (в зависимости от реализации).

2
27.01.2020, 20:01

Теги

Похожие вопросы