Самомодифицирующийся код - Self-modifying code

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

Этот метод часто используется для условного вызова кода тестирования / отладки, не требуя дополнительных вычислительных затрат для каждого цикла ввода / вывода .

Модификации могут быть выполнены:

  • только во время инициализации - на основе входных параметров (когда процесс чаще описывается как программная « конфигурация » и несколько аналогичен в аппаратном отношении установке перемычек для печатных плат ). Изменение указателей входа в программу является эквивалентным косвенным методом самомодификации, но требует сосуществования одного или нескольких альтернативных путей выполнения команд, увеличивая размер программы .
  • на протяжении выполнения («на лету») - на основе определенных состояний программы, которые были достигнуты во время выполнения

В любом случае модификации могут быть выполнены непосредственно в самих инструкциях машинного кода путем наложения новых инструкций на существующие (например: изменение сравнения и перехода на безусловный переход или, альтернативно, « NOP »).

В наборе инструкций IBM / 360 и Z / Architecture инструкция EXECUTE (EX) логически перекрывает второй байт своей целевой инструкции 8 младшими битами регистра 1. Это обеспечивает эффект самомодификации, хотя фактическая инструкция в хранилище не переделано.

Приложение на языках низкого и высокого уровня

Самомодификация может выполняться различными способами в зависимости от языка программирования и его поддержки указателей и / или доступа к динамическим «движкам» компилятора или интерпретатора:

  • наложение существующих инструкций (или частей инструкций, таких как код операции, регистр, флаги или адрес) или
  • прямое создание целых инструкций или последовательностей инструкций в памяти
  • создание или изменение операторов исходного кода, за которыми следует «мини-компиляция» или динамическая интерпретация (см. оператор eval )
  • динамическое создание всей программы и ее выполнение

язык ассемблера

Самомодифицирующийся код довольно просто реализовать при использовании языка ассемблера . Инструкции могут создаваться динамически в памяти (или же накладываться на существующий код в незащищенном программном хранилище) в последовательности, эквивалентной той, которую стандартный компилятор может генерировать как объектный код . В современных процессорах могут возникнуть непредвиденные побочные эффекты в кеш-памяти ЦП, которые необходимо учитывать. Этот метод часто использовался для тестирования условий «первого раза», как в этом надлежащим образом прокомментированном примере ассемблера IBM / 360 . Он использует наложение инструкций для уменьшения длины пути инструкции на (N × 1) -1, где N - количество записей в файле (-1 - служебные данные для выполнения наложения).

SUBRTN NOP OPENED      FIRST TIME HERE?
* The NOP is x'4700'<Address_of_opened>
       OI    SUBRTN+1,X'F0'  YES, CHANGE NOP TO UNCONDITIONAL BRANCH (47F0...)
       OPEN   INPUT               AND  OPEN THE INPUT FILE SINCE IT'S THE FIRST TIME THRU
OPENED GET    INPUT        NORMAL PROCESSING RESUMES HERE
      ...

Альтернативный код может включать проверку «флажка» каждый раз. Безусловный переход выполняется немного быстрее, чем инструкция сравнения, а также сокращает общую длину пути. В более поздних операционных системах для программ, находящихся в защищенном хранилище, этот метод нельзя было использовать, поэтому вместо него можно было бы использовать изменение указателя на подпрограмму . Указатель будет находиться в динамическом хранилище и может быть изменен по желанию после первого прохода, чтобы обойти OPEN (необходимость сначала загрузить указатель вместо прямого перехода и ссылки на подпрограмму добавит N инструкций к длине пути, но при этом будет - соответствующее сокращение N для безусловного перехода, которое больше не потребуется).

Ниже приведен пример на языке ассемблера Zilog Z80 . Код увеличивает регистр «B» в диапазоне [0,5]. Инструкция сравнения "CP" модифицируется в каждом цикле.

;======================================================================
ORG 0H
CALL FUNC00
HALT
;======================================================================
FUNC00:
LD A,6
LD HL,label01+1
LD B,(HL)
label00:
INC B
LD (HL),B
label01:
CP $0
JP NZ,label00
RET
;======================================================================

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

Языки высокого уровня

Некоторые компилируемые языки явно разрешают самомодифицирующийся код. Например, команда ALTER в COBOL может быть реализована как инструкция ветвления, которая изменяется во время выполнения. Некоторые методы пакетного программирования включают использование самомодифицирующегося кода. Clipper и SPITBOL также предоставляют возможности для явной самомодификации. Компилятор Algol в системах B6700 предлагал интерфейс для операционной системы, посредством которого выполняемый код мог передавать текстовую строку или именованный дисковый файл компилятору Algol, а затем мог вызывать новую версию процедуры.

В интерпретируемых языках «машинный код» является исходным текстом и может быть подвержен редактированию на лету: в SNOBOL выполняемые исходные операторы являются элементами текстового массива. Другие языки, такие как Perl и Python , позволяют программам создавать новый код во время выполнения и выполнять его с помощью функции eval , но не позволяют изменять существующий код. Иллюзия модификации (даже если на самом деле машинный код не перезаписывается) достигается путем изменения указателей функций, как в этом примере JavaScript:

    var f = function (x) {return x + 1};

    // assign a new definition to f:
    f = new Function('x', 'return x + 2');

Макросы Lisp также позволяют генерировать код во время выполнения без анализа строки, содержащей программный код.

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

Составная модификация

До появления нескольких окон системы командной строки могли предлагать систему меню, включающую модификацию выполняющегося командного сценария. Предположим, что файл сценария DOS (или «пакетный») Menu.bat содержит следующее:

   :StartAfresh                <-A line starting with a colon marks a label.
   ShowMenu.exe

После запуска Menu.bat из командной строки ShowMenu представляет экранное меню с возможной справочной информацией, примерами использования и т. Д. В конце концов, пользователь делает выбор, который требует выполнения команды somename : ShowMenu завершает работу после перезаписи файла Menu.bat, чтобы он содержал

   :StartAfresh
   ShowMenu.exe
   CALL C:\Commands\somename.bat
   GOTO StartAfresh

Поскольку интерпретатор команд DOS не компилирует файл сценария, а затем выполняет его, а также не считывает весь файл в память перед запуском выполнения и не полагается на содержимое буфера записи, при выходе из ShowMenu интерпретатор команд находит новый команда для выполнения (это вызов файла сценария somename , в расположении каталога и через протокол, известный ShowMenu), и после завершения этой команды она возвращается к началу файла сценария и повторно активирует ShowMenu, готовое к следующему выбору . Если в меню будет выбрано «Выход», файл будет перезаписан обратно в исходное состояние. Хотя это начальное состояние не используется для метки, оно или эквивалентное количество текста требуется, потому что интерпретатор команд DOS вызывает позицию байта следующей команды, когда он должен запустить следующую команду, таким образом, перезаписанный файл должен поддерживать выравнивание, чтобы точка начала следующей команды действительно была началом следующей команды.

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

Таблицы управления

Интерпретаторы управляющих таблиц можно рассматривать как в определенном смысле «самоизменяющиеся» значениями данных, извлеченными из записей таблицы (а не специально закодированными вручную в условных операторах формы «IF inputx = 'yyy'»).

Канальные программы

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

История

IBM SSEC , продемонстрировал в январе 1948 года, имел возможность вносить изменения в инструкции или иначе относиться к ним так же , как данные. Однако на практике эта возможность использовалась редко. На заре компьютеров самомодифицирующийся код часто использовался для уменьшения использования ограниченной памяти или повышения производительности, либо того и другого. Он также иногда использовался для реализации вызовов подпрограмм и возврата, когда набор инструкций предоставлял только простые инструкции перехода или пропуска для изменения потока управления . Это использование все еще актуально в некоторых архитектурах ultra- RISC , по крайней мере, теоретически; см. например один компьютер с набором команд . Дональд Кнут «s MIX архитектура также используется самомодифицирующийся код для реализации вызовов подпрограмм.

использование

Самомодифицирующийся код можно использовать для различных целей:

  • Полуавтоматическая оптимизация цикла, зависящего от состояния.
  • Во время выполнения генерации коды, или специализация алгоритма во время выполнения или время загрузки (который является популярным, например, в области график реального времени) , такие как общая полезность сортировки - подготовка коды для выполнения сравнения ключей , описанное в конкретном призыв.
  • Изменение встроенного состояния объекта или имитация высокоуровневой конструкции замыканий .
  • Исправление вызова адреса подпрограммы ( указателя ), обычно выполняемое во время загрузки / инициализации динамических библиотек или при каждом вызове, исправление внутренних ссылок подпрограммы на ее параметры, чтобы использовать их фактические адреса. (т.е. косвенная «самомодификация»).
  • Эволюционные вычислительные системы, такие как нейроэволюция , генетическое программирование и другие эволюционные алгоритмы .
  • Скрытие кода для предотвращения обратного проектирования (с помощью дизассемблера или отладчика ) или для уклонения от обнаружения программным обеспечением для сканирования вирусов / шпионского ПО и т.п.
  • Заполнение 100% памяти (в некоторых архитектурах) повторяющимися кодами операций для стирания всех программ и данных или для записи оборудования.
  • Сжатие кода для распаковки и выполнения во время выполнения, например, когда память или дисковое пространство ограничены.
  • Некоторые очень ограниченные наборы инструкций не оставляют другого выбора, кроме как использовать самомодифицирующийся код для выполнения определенных функций. Например, компьютер с одним набором команд (OISC), который использует только «команду» вычитания и ветвления, если отрицательный, не может выполнять косвенное копирование (что-то вроде эквивалента «* a = ** b» в C язык ) без использования самомодифицирующегося кода.
  • Загрузка . Ранние микрокомпьютеры часто использовали самомодифицирующийся код в своих загрузчиках. Поскольку загрузчик вводился через переднюю панель при каждом включении, не имело значения, изменялся ли загрузчик сам. Однако даже сегодня многие загрузчики начальной загрузки самоперемещаются , а некоторые даже самомодифицируются.
  • Переделка инструкции по отказоустойчивости.

Оптимизация цикла, зависящего от состояния

Пример псевдокода :

repeat N times {
    if STATE is 1
        increase A by one
    else
        decrease A by one
    do something with A
}

Самомодифицирующийся код в этом случае будет просто переписать цикл следующим образом:

repeat N times {
    increase A by one
    do something with A
    when STATE has to switch {
        replace the opcode "increase" above with the opcode to decrease, or vice versa
    }
}

Обратите внимание, что замена кода операции с двумя состояниями может быть легко записана как «xor var по адресу со значением« opcodeOf (Inc) xor opcodeOf (dec) »».

Выбор этого решения должен зависеть от значения N и частоты изменения состояния.

Специализация

Предположим, что набор статистических данных, таких как среднее значение, экстремумы, расположение экстремумов, стандартное отклонение и т. Д., Должен быть рассчитан для некоторого большого набора данных. В общей ситуации может существовать опция связывания весов с данными, так что каждый x i связан с aw i, и вместо проверки наличия весов для каждого значения индекса может быть две версии расчета, одна для использования с отягощениями, а с одним нет, с одним тестом в начале. Теперь рассмотрим еще один вариант: каждое значение может быть связано с логическим значением, указывающим, должно ли это значение быть пропущено или нет. С этим можно справиться, создав четыре пакета кода, по одному для каждой перестановки и результатов раздувания кода. В качестве альтернативы массивы веса и пропуска могут быть объединены во временный массив (с нулевыми весами для значений, которые должны быть пропущены) за счет обработки, но при этом сохраняется раздувание. Однако с модификацией кода к шаблону для расчета статистики может быть добавлен соответствующий код для пропуска нежелательных значений и для применения весов. Не будет повторного тестирования опций, и доступ к массиву данных будет осуществляться один раз, как и к массивам веса и пропуска, если они задействованы.

Использовать как камуфляж

Самомодифицирующийся код использовался для сокрытия инструкций по защите от копирования в дисковых программах 1980-х годов для таких платформ, как IBM PC и Apple II . Например, на IBM PC (или совместимом ) инструкция доступа к дисководу гибких дисковint 0x13 не будет отображаться в образе исполняемой программы, но она будет записана в образ памяти исполняемого файла после того, как программа начнет выполняться.

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

Самореференциальные системы машинного обучения

Традиционные системы машинного обучения имеют фиксированный, предварительно запрограммированный алгоритм обучения для настройки своих параметров . Однако с 1980-х годов Юрген Шмидхубер опубликовал несколько самомодифицирующихся систем с возможностью изменения собственного алгоритма обучения. Они избегают опасности катастрофического самопереписывания, гарантируя, что самомодификации сохранятся только в том случае, если они будут полезны в соответствии с заданной пользователем функцией пригодности , ошибки или вознаграждения .

Операционные системы

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

Как следствие проблем , которые могут быть вызваны этими подвигами, компонент ОСА называется W ^ X (для «записи исключающего выполнения») была разработано, запрещающей программой от создания любой страницы памяти как для записи , и исполняемого файла. Некоторые системы предотвращают изменение страницы с возможностью записи на исполняемую, даже если разрешение на запись удалено. Другие системы предоставляют своего рода « черный ход », позволяя множественным сопоставлениям страницы памяти иметь разные разрешения. Относительно переносимый способ обойти W ^ X - создать файл со всеми разрешениями, а затем дважды сопоставить файл с памятью. В Linux можно использовать недокументированный флаг разделяемой памяти SysV, чтобы получить исполняемую разделяемую память без необходимости создания файла.

Тем не менее , на мета-уровне программы все еще могут изменять свое собственное поведение, изменяя данные, хранящиеся в другом месте (см. Метапрограммирование ), или используя полиморфизм .

Взаимодействие кеша и самомодифицирующегося кода

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

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

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

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

Ядро синтеза Массалина

Синтез ядро представлены в Алексии Массалин «s Ph.D. thesis - это крошечное ядро Unix, которое использует структурированный или даже объектно-ориентированный подход к самомодифицирующемуся коду, где код создается для отдельных кваъектов , таких как дескрипторы файлов. Генерация кода для конкретных задач позволяет ядру Synthesis (как и интерпретатор JIT) применять ряд оптимизаций, таких как сворачивание констант или исключение общих подвыражений .

Ядро Synthesis было очень быстрым, но было написано полностью на ассемблере. Из-за этого отсутствие переносимости не позволило принять идеи оптимизации Массалина каким-либо производственным ядром. Однако структура методов предполагает, что они могут быть захвачены языком более высокого уровня , хотя и на один более сложный, чем существующие языки среднего уровня. Такой язык и компилятор могут позволить разрабатывать более быстрые операционные системы и приложения.

Пол Хэберли и Брюс Карш возражают против «маргинализации» самомодифицируемого кода и оптимизации в целом в пользу сокращения затрат на разработку.

Преимущества

Недостатки

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

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

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

Самомодифицирующийся код вообще нельзя использовать в некоторых средах, например в следующих:

  • Прикладное программное обеспечение, работающее под управлением операционной системы со строгой безопасностью W ^ X, не может выполнять инструкции на страницах, на которые разрешена запись - только операционной системе разрешено как записывать инструкции в память, так и позже выполнять эти инструкции.
  • Многие микроконтроллеры с архитектурой Гарварда не могут выполнять инструкции в памяти для чтения-записи, а только инструкции в памяти, в которую нельзя записывать, ПЗУ или несамопрограммируемую флэш-память .
  • Многопоточное приложение может иметь несколько потоков, выполняющих один и тот же раздел самомодифицирующегося кода, что может приводить к ошибкам вычислений и сбоям приложения.

Смотрите также

использованная литература

внешние ссылки