#assembly #eval
#сборка #оценка
Вопрос:
Я начал изучать самый базовый язык ассемблера и узнал, что скомпилированный код попадает в специальный сегмент с именем Code Segment
, который (по крайней мере, в современных архитектурах) является сегментом, защищенным от записи.
Но возникает вопрос: в некоторых языках программирования (например, ECMAScript, Python и т.д.) Существует волшебная eval()
функция, которая принимает строку, анализирует ее и затем выполняет.
Поскольку код оценивается во время выполнения (затем после заполнения сегмента кода) и сегмент кода защищен от записи, какое колдовство он выполняет?
Я предполагаю, что это связано с JIT-компиляцией, но пока нет никаких подсказок о том, как это работает на низком уровне.
Комментарии:
1. интерпретируется python (если только не используется pypi, но даже там вы можете вызвать интерпретатор динамически). Поэтому
eval
просто вычисляет выражение с помощью встроенного интерпретатора. Это невозможно в ассемблере или скомпилированных языках.2. нет, см. Мою правку. интерпретируемые коды… интерпретируется! система байт-кода позволяет сэкономить время на синтаксическом анализе, но это то же самое: интерпретируется. JIT генерирует машинный код «на лету» в сегменте, недоступном только для чтения, но у вас нет простого доступа к этому сегменту (как в C), поэтому он довольно безопасен.
Ответ №1:
Давайте возьмем пример python.
Интерпретируется Python (если только не используются движки с поддержкой pypi или JIT, но даже там вы можете вызывать интерпретатор динамически). При запуске программа всегда имеет доступ к интерпретатору, встроенному в исполняемый файл python, который выполняется в данный момент (оценка является частью времени выполнения, что является следствием)
Поэтому eval
просто вычисляет выражение с помощью встроенного интерпретатора.
Поскольку python означает производительность, ваш код преобразуется в байт-код при загрузке модулей для экономии времени разбора текста (Java делает это, например, во время компиляции), но реальные машинные инструкции, которые выполняются, содержатся в python
исполняемом файле (который интерпретирует байт-код и выполняет соответствующие действия) или загруженных .pyd
файлах, которыеэто библиотеки DLL.
JIT — это просто еще одна оптимизация поверх байт-кода: она генерирует собственный код «на лету» в сегменте памяти, но у вас нет простого доступа к этому сегменту (как у вас в C с адресами функций), поэтому было бы очень сложно взломать этот код из программы python.
Это невозможно (по крайней мере, нелегко) на ассемблере или скомпилированных языках (C, C , Ada …), На самом деле не из-за защиты сегмента кода от записи (которая не гарантируется), а просто из-за неспособности запущенной программы собрать / скомпилировать код: он не встраиваеткомпилятор / ассемблер. Среда выполнения, если она существует, минимальна и, конечно же, не содержит оценки исходного кода.
Проще всего было бы создать временный файл с вашей программой, вызвать для него компилятор / ассемблер из вашей программы и выполнить его в отдельном процессе или динамически загружать DLL, но это не тривиально.
Другой возможной вещью, как заметил Фрэнк, было бы создание виртуальной машины в вашей программе для оценки инструкций машинного кода, как это сделал бы реальный процессор (или высокоуровневые инструкции, как это сделал бы компилятор). Излишне говорить, что это не тривиально, но некоторые уже существующие библиотеки делают это (например, QEMU), и даже с существующим материалом это далеко не просто реализовать.
Комментарии:
1. Спасибо за ваш ответ. Пожалуйста, извините меня, но низкоуровневый все еще довольно неясен для меня. По сути, мы скомпилировали байт-код (во время выполнения с помощью Python, во время компиляции с Java), который является эквивалентом ассемблера и который выполняется через Pyton (или JVM также). Таким образом, вводимый пользователем код оценки компилируется во время выполнения в байт-код, который динамически загружается (интерпретируется)?
2. @Fylax: Конечно, в этих виртуальных машинах нет ничего волшебного. Вам просто нужно реализовать компилятор самостоятельно. Просто реализуйте компилятор и интерпретатор байт-кода самостоятельно или генерируйте машинный код напрямую и отправляйте выходные данные в блок памяти, выделенный с правами записи и выполнения. Конечно, для нетривиального языка программирования выполнение этого на ассемблере — большая утомительная работа, но вы можете достаточно легко проверить концепцию чего-то вроде, скажем, графического калькулятора, компилирующего функции для повторного вычисления.
3. Ваш ответ выше звучит так, как будто вы не можете написать динамический интерпретатор на ассемблере? Конечно , вы можете, поскольку это просто управление и оценка данных по сравнению с генерацией и компиляцией машинного кода.
4. @FrankC. невозможно для большинства простых смертных 🙂 Я отредактировал свой пост.
5. Спасибо вам всем, ребята. У меня есть представление о том, что происходит
Ответ №2:
Ответ с другой точки зрения…
Флаг «code segment», недоступный для записи, — это просто расположение, выполняемое ОС во время загрузки исполняемого файла. На уровне HW нет ничего, что мешало бы ОС подготовить записываемую исполняемую страницу памяти, это просто стало удобной мерой безопасности и предотвращения ошибок для запуска исполняемых файлов на странице памяти, защищенной от записи. И создатели приложений уважают это и больше не используют самодиагностируемый код (это была обычная практика в раннем программировании на ассемблере). (если только они не выделяют дополнительную память из ОС именно для этой цели, чтобы записать туда и выполнить ее после)
Кроме того, весь «сегмент кода» является абстракцией высокого уровня, сам процессор не знает о чем-то подобном.
(x86) процессор имеет только текущий уровень привилегий и карту виртуальной памяти, поэтому любой адрес памяти, к которому он обращается, он преобразует в адрес физической памяти через определение виртуальной карты, проверяя привилегии этой «страницы» памяти (can-read / can-write) в соответствии с запрошенной операцией.
В случае, если доступ недействителен, он будет перенаправлен в обработчик ошибок, который обычно предоставляется ОС.
Независимо от того, загружено ли приложение с кодом и данными на отдельных страницах памяти, или даже разделы данных имеют четкое различие между записываемыми и доступными только для чтения, все зависит от ОС и загрузчика приложений, чтобы настроить его с помощью простой механики отображения памяти, предоставляемой процессором. привилегии / флаг. Если у вас есть собственная ОС, вы также можете отобразить всю память в один большой незащищенный блок с разрешенными для чтения выполнения записи для всех.
Комментарии:
1. Также легко перейти на страницу, доступную для записи, а затем попросить ОС изменить ее на чтение выполнение без разрешения на запись. Таким образом, даже в строгой ОС, которая отказывается когда-либо разрешать страницы W X, вы все равно можете использовать JIT. Но не самоизменяющийся код, который изменяет себя на лету.
Ответ №3:
Я не разбираюсь в Python, но да во встроенных системах. На ПК я полагаю, что операционная система (Windows / Unix / Android / etc) будет резервировать через MMU (Memory Management Unit) области физической памяти для каждого сегмента и назначать им свои права доступа. Чтобы динамически загружать исполняемую программу для ее последующего выполнения, как это может сделать Python, для этой цели должен быть предварительно объявлен сегмент с правами чтения / записи / выполнения. Python должен делать это по умолчанию, но он не должен быть «Сегментом кода», поскольку вы говорите, что он доступен только для чтения. То есть не весь «программный код» попадает в один и тот же сегмент. Например, в коде на ассемблере можно исправить, где они будут предварительно расположены на фрагменте кода / данных, объявив его в «имени сегмента / раздела». Компоновщик возьмет сегменты с одинаковыми именами из файлов, содержащихся в проекте, последовательно соединяя те, которые имеют одинаковые имена, и распределит их по адресам, установленным «файлом директивы компоновщика» (иногда файл с расширением «.ld»). Разные компиляторы имеют соглашения об именах для сегментов (типичными являются «код», «данные», «текст», «bss» и т. Д.). У каждого из них обычно есть свои атрибуты для прав доступа на чтение / запись / выполнение.