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

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

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

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

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

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

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

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

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

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

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

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

SUBRTN NOP ОТКРЫТ ВПЕРВЫЕ ЗДЕСЬ? * NOP - x'4700 ' OI SUBRTN + 1, X'F0' ДА, ИЗМЕНИТЬ NOP НА НЕОБХОДИМЫЙ ФИЛИАЛ (47F0 ...) ОТКРЫТЬ ВВОД И ОТКРЫТЬ ФАЙЛ ВВОДА В ПЕРВЫЙ РАЗ ПРОХОДИМ ПОЛУЧИТЬ ВХОД НОРМАЛЬНАЯ ОБРАБОТКА ВОЗОБНОВЛЕНА ЗДЕСЬ ...

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

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

;======================================================================ORG 0HВЫЗОВ FUNC00HALT;======================================================================FUNC00:LD А,6LD HL,label01+1LD B,(HL)label00:INC BLD (HL),Blabel01:CP $0JP NZ,label00RET;======================================================================

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

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

Некоторые компилируемые языки явно разрешают самомодифицирующийся код. Например, глагол ALTER в КОБОЛ может быть реализована как инструкция ветвления, которая изменяется во время выполнения.[1] Немного партия методы программирования включают использование самомодифицирующегося кода. Машинка для стрижки и СПИТБОЛ также предоставляют возможности для явного самомодификации. Компилятор Algol на Системы B6700 предлагал интерфейс для операционной системы, посредством которого выполняющийся код мог передавать текстовую строку или именованный дисковый файл компилятору Algol, а затем мог вызывать новую версию процедуры.

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

    вар ж = функция (Икс) {возвращаться Икс + 1};    // присваиваем новое определение f:    ж = новый Функция('Икс', 'return x + 2');

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

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

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

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

   : StartAfresh <-A строка начало двоеточием обозначает метку. ShowMenu.exe

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

   : StartAfresh ShowMenu.exe ВЫЗОВ C:  Commands какое-то имя.bat НАЙТИ StartAfresh

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

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

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

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

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

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

История

В IBM SSEC, продемонстрированный в январе 1948 года, имел возможность изменять свои инструкции или иным образом обрабатывать их точно так же, как данные. Однако на практике эта возможность использовалась редко.[3] На заре компьютеров самомодифицирующийся код часто использовался для уменьшения использования ограниченной памяти, повышения производительности или того и другого. Он также иногда использовался для реализации вызовов подпрограмм и возврата, когда набор инструкций предоставлял только простые инструкции перехода или пропуска для изменения поток управления. Это использование по-прежнему актуально в некоторых ультра-RISC архитектуры, по крайней мере теоретически; см. например один компьютер с набором команд. Дональд Кнут с СМЕШИВАНИЕ архитектура также использовала самомодифицирующийся код для реализации вызовов подпрограмм.[4]

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

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

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

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

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

повторить N раз {если СОСТОЯНИЕ равно 1, увеличить A на единицу, иначе уменьшить A на единицу, сделать что-нибудь с A}

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

 повторять N раз { увеличивать A по одному сделайте что-нибудь с A, когда STATE должен переключиться {заменить код операции "увеличение" выше на код операции для уменьшения, или наоборот}}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Синтез ядро представлен в Алексия Массалин с Кандидат наук. Тезис[7] крошечный Unix ядро, которое принимает структурированный, или даже объектно-ориентированный, подход к самомодифицирующемуся коду, где код создается для индивидуального объекты, как дескрипторы файлов. Генерация кода для конкретных задач позволяет ядру Synthesis (как и интерпретатор JIT) применять ряд оптимизации Такие как постоянное сворачивание или же исключение общего подвыражения.

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

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

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

Недостатки

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

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

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

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

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

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

Рекомендации

  1. ^ «Заявление ALTER». Справочник по языку COBOL. Микро Фокус.
  2. ^ "Push, PushGP и Pushpop".
  3. ^ Bashe, C.J .; Buchholz, W .; Хокинс, Г. В .; Ingram, J. J .; Рочестер, Н. (сентябрь 1981 г.). «Архитектура ранних компьютеров IBM» (PDF). Журнал IBM по разработке систем. 25 (5): 363–376. CiteSeerX  10.1.1.93.8952. Дои:10.1147 / rd.255.0363. SSEC был первым операционным компьютером, способным обрабатывать свои собственные сохраненные инструкции точно так же, как данные, изменять их и воздействовать на результат.
  4. ^ «MMIX 2009 (описание устаревших функций MIX)».
  5. ^ «Об самомодифицирующемся коде и ОС космического корабля - Карлос Энрике Ортис».
  6. ^ Юрген Шмидхубер публикации о самомодифицирующийся код для самореферентных систем машинного обучения
  7. ^ Пу, Калтон; Массалин, Генрих; Иоаннидис, Джон (1992). Синтез: эффективное внедрение фундаментальных сервисов операционной системы (PDF) (Кандидатская диссертация). Нью-Йорк, Нью-Йорк, США: Департамент компьютерных наук, Колумбийский университет. UMI Заказ № GAX92-32050. В архиве (PDF) из оригинала на 2017-07-04. Получено 2012-04-25. Сложить резюме (2008-02-20). [1]
  8. ^ Хаберли, Пол; Карш, Брюс (1994-02-03). «Ио Ной Боччони - Предыстория футуристического программирования». Grafica Obscura. В архиве из оригинала на 2017-07-04. Получено 2017-07-04.

внешняя ссылка