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

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

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

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

Изменения могут быть выполнены:

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

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

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

Содержание
  • 1 Приложение на языках низкого и высокого уровня
    • 1.1 Язык ассемблера
    • 1.2 Языки высокого уровня
      • 1.2.1 Составная модификация
    • 1.3 Таблицы управления
    • 1.4 Канальные программы
  • 2 История
  • 3 Использование
    • 3.1 Оптимизация зависящего от состояния цикла
    • 3.2 Специализация
    • 3.3 Использование в качестве маскировки
    • 3.4 Самореферентные системы машинного обучения
    • 3.5 Операционные системы
    • 3.6 Взаимодействие кеша и самомодифицирующегося кода
    • 3.7 Ядро Massalin's Synthesis
  • 4 Преимущества
  • 5 Недостатки
  • 6 См. Также
  • 7 Ссылки
  • 8 Внешние ссылки
Приложение в низком и языки высокого уровня

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

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

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

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

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

Альтернативный код может включать проверку «флажка» каждый раз. Безусловный переход выполняется немного быстрее, чем инструкция сравнения, а также сокращает общую длину пути. В более поздних операционных системах для программ, находящихся в защищенном хранилище, этот метод нельзя было использовать, поэтому вместо него будет использоваться изменение указателя на подпрограмму . Указатель будет находиться в динамическом хранилище и может быть изменен по желанию после первого прохода, чтобы обойти 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}; // присваиваем новое определение 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 ready для следующего выбора. Если выбрать в меню выход, файл будет перезаписан обратно в исходное состояние. Хотя это начальное состояние не используется для метки, оно или эквивалентное количество текста требуется, потому что интерпретатор команд DOS вызывает позицию байта следующей команды, когда он должен запустить следующую команду, таким образом, перезаписанный файл должен поддерживать выравнивание, чтобы точка начала следующей команды действительно была началом следующей команды.

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

Управляющие таблицы

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

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

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

История

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

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

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

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

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

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

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

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

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

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

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

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

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

Использование в качестве маскировки

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

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

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

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

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

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

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

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

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

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

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

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

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

Ядро Massalin's Synthesis

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

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

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

Преимущества
Недостатки

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

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

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

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

  • Прикладное программное обеспечение, работающее под управлением операционной системы со строгой безопасностью W ^ X, не может выполнять инструкции на страницах, которые разрешено писать to - только операционной системе разрешено как записывать инструкции в память, так и позже выполнять эти инструкции.
  • Многие микроконтроллеры с гарвардской архитектурой не могут выполнять инструкции в памяти чтения-записи, а только инструкции в памяти что он не может записывать в ПЗУ или несамопрограммируемую флэш-память.
  • Многопоточное приложение может иметь несколько потоков, выполняющих один и тот же раздел самомодифицирующегося кода, что может привести к ошибкам вычислений и сбоям приложения.
См. Также
Ссылки
Внешние ссылки
Последняя правка сделана 2021-06-07 09:24:11
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте