Как сделать машинный код

Добавил пользователь Евгений Кузнецов
Обновлено: 04.10.2024

Маши́нный код (платфо́рменно-ориенти́рованный код), маши́нный язы́к — система команд (набор кодов операций) конкретной вычислительной машины, которая интерпретируется непосредственно процессором или микропрограммами этой вычислительной машины. [1]

Компьютерная программа, записанная на машинном языке, состоит из машинных инструкций, каждая из которых представлена в машинном коде в виде т. н. опкода — двоичного кода отдельной операции из системы команд машины. Для удобства программирования вместо числовых опкодов, которые только и понимает процессор, обычно используют их условные буквенные мнемоники. Набор таких мнемоник, вместе с некоторыми дополнительными возможностями (например, некоторыми макрокомандами, директивами), называется языком ассемблера.

Есть ли простой способ визуализировать шаг между сборкой кода в машинный код?

Например, если вы откроете бинарный файл в блокноте, вы увидите текстовое представление машинного кода. Я предполагаю, что каждый байт (символ), который вы видите, является соответствующим символом ascii для его двоичного значения?

Но как нам перейти от сборки к бинарному, что происходит за кулисами?

Посмотрите документацию по набору инструкций, и вы найдете записи, подобные этой, из микроконтроллера pic для каждой инструкции:

пример инструкции addlw

Это утомительно, но не так сложно кодировать и декодировать. У меня был класс старшекурсников, где мы должны были делать это вручную на экзаменах.

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

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

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

Первое, что вам нужно, это что-то вроде этого файла . Это база данных команд для процессоров x86, используемая ассемблером NASM (которую я помогал написать, хотя не части, которые фактически переводят инструкции). Давайте выберем произвольную строку из базы данных:

Как только мы получили строку из базы данных, мы смотрим на третий столбец, который для этой инструкции:

Это набор инструкций, которые описывают, как генерировать инструкцию машинного кода, которая требуется:

Дальше есть /0 . Это определяет некоторые дополнительные биты, которые нам понадобятся в байте modr / m, и заставляет нас их генерировать. modr/m Байт используется для регистров кодируют или ссылки косвенных памяти. У нас есть один такой операнд, регистр. У регистра есть номер, который указан в другом файле данных :

Мы проверяем, что reg32 соответствует требуемому размеру инструкции из исходной базы данных (это делает). Это 0 номер регистра. modr/m Байт представляет собой структуру данных , указанная с помощью процессора, который выглядит следующим образом :

Поскольку мы работаем с регистром, mod поле есть 0b11 .

Полная инструкция собран поэтому: 0x83 0xC0 0x2A . Отправьте его в модуль вывода вместе с примечанием о том, что ни один из байтов не является ссылками на память (модуль вывода может знать, если они это делают).

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

Спасибо. Отличное объяснение, но не должно быть "0x83 0xC0 0x2A", а не "0x83 0xB0 0x2A", потому что 0b11000000 = 0xC0

@Kamran - $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003 . да, ты совершенно прав. :)

На практике ассемблер обычно не создает непосредственно некоторый двоичный исполняемый файл , но некоторые объектные файлы (которые будут переданы позднее компоновщику ). Однако есть исключения (вы можете использовать некоторые ассемблеры для непосредственного создания некоторого двоичного исполняемого файла; они редки).

Во-первых, обратите внимание, что многие ассемблеры сегодня являются бесплатными программами. Поэтому скачайте и скомпилируйте на свой компьютер исходный код GNU как (часть binutils ), так и из nasm . Затем изучите их исходный код. Кстати, я рекомендую использовать Linux для этой цели (это очень удобная для разработчиков и свободная от программного обеспечения ОС).

Объектный файл, созданный ассемблером, содержит, в частности, сегмент кода и инструкции по перемещению . Он организован в хорошо документированном формате, который зависит от операционной системы. В Linux этот формат (используемый для объектных файлов, общих библиотек, дампов ядра и исполняемых файлов) - это ELF . Этот объектный файл позже вводится в компоновщик (который в итоге создает исполняемый файл). Перемещения определяются ABI (например, x86-64 ABI ). Читайте Левина книга Linkers и погрузчики для более.

Сегмент кода в таком объектном файле содержит машинный код с отверстиями (заполняется с помощью информации о перемещении компоновщиком). (Перемещаемый) машинный код, сгенерированный ассемблером, очевидно, специфичен для архитектуры набора команд . В x86 или x86-64 (используются в большинстве ноутбуков или настольных процессоров) ИСАС является очень сложным в деталях. Но для целей обучения было изобретено упрощенное подмножество, называемое y86 или y86-64. Читайте слайды на них. Другие ответы на этот вопрос также объясняют немного этого. Вы можете прочитать хорошую книгу по компьютерной архитектуре .

Как исполняемый файл запускается ядром ОС (например, как execve работает системный вызов в Linux) - это другой (и сложный) вопрос. Обычно он устанавливает некоторое виртуальное адресное пространство (в процессе, выполняющем execve (2) . ), а затем повторно инициализирует внутреннее состояние процесса (включая регистры пользовательского режима ). Динамический компоновщик -such , как ld-linux.so (8) на Linux так может быть вовлечен во время выполнения. Прочитайте хорошую книгу, такую ​​как Операционная система: Три Легких Части . OSDEV вики также дает полезную информацию.

PS. Ваш вопрос настолько широк, что вам нужно прочитать о нем несколько книг. Я дал некоторые (очень неполные) ссылки. Вы должны найти больше из них.

Что касается форматов объектных файлов, я бы порекомендовал взглянуть на формат RDOFF, созданный NASM. Это было сделано специально, чтобы быть настолько простым, насколько это реально возможно, и все же работать в самых разных ситуациях. Источник NASM включает в себя компоновщик и загрузчик для формата. (Полное раскрытие - я разработал и написал все это)

У тех, кто только начинает знакомиться с Java, довольно часто возникает путаница в понятиях машинный и байт код. Что они собой представляют? В чём различия? В короткой заметке мы постараемся максимально просто и понятно расписать их особенности, чтоб раз и навсегда закрыть этот вопрос.

Машинный код и байт код: на каком языке говорит ваша программа? - 1

Машинный код

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

Врожденная несовместимость

CISC (англ. Complex Instruction Set Computing) — концепция проектирования процессоров, которая характеризуется следующим набором свойств:

  • много команд, разных по длине;
  • много режимов адресации;
  • сложная кодировка инструкции.

Байт-код

Еще и виртуальная ОС

Впрочем, байт код содержит не только процессорные инструкции. В нем также содержится логика взаимодействия с виртуальной операционной системой, которая делает поведение приложения независящим от используемой на компьютере операционной системы. Это отлично видно в JVM, где работа с системными вызовами и GUI зачастую не зависят от ОС, на которой запущена программа. По большому счету JVM эмулирует запуск процесса программы, в отличие от решений вроде Virtual Box, которые создают только виртуальную систему/железо.

Я не очень хорошо знаком с магией компилятора. Акт преобразования кода, читаемого человеком (или не действительно читаемых инструкций ассемблера) в машинный код, для меня представляет собой ракетостроение в сочетании с колдовством.

Я сузлю тему этого вопроса до исполняемых файлов Win32 (.exe). Когда я открываю эти файлы в специализированном средстве просмотра, я могу найти строки (обычно 16 байт на символ), разбросанные в разных местах, а все остальное - просто мусор. Я полагаю, что нечитаемая часть (большинство) - это машинный код (или, возможно, ресурсы, такие как изображения и т. Д.).

Есть ли простой способ чтения машинного кода? Открывая exe как файловый поток и читая его побайтно, как можно превратить эти отдельные байты в сборку? Есть ли прямое соответствие между этими байтами инструкций и инструкцией Assembly?

Как пишется .exe? Четыре байта на инструкцию? Более? Меньше? Я заметил, что некоторые приложения могут создавать исполняемые файлы точно так же: например, в ACD See вы можете экспортировать серию изображений в слайд-шоу. Но это не обязательно должно быть слайд-шоу в формате SWF, ACD See также может создавать EXE-файлы для презентаций. Как это сделать?

Как я могу понять, что происходит внутри EXE-файла?

OllyDbg - отличный инструмент, который разбирает EXE на читаемые инструкции и позволяет вам выполнять инструкции одну за другой. . Он также сообщает вам, какие функции API использует программа и, если возможно, аргументы, которые она предоставляет (если аргументы находятся в стеке).

Вы можете взять Учебник по языку ассемблера для ПК доктора Пола Картера, который представляет собой бесплатную книгу начального уровня, в которой рассказывается о сборка и принцип работы процессора Intel 386. Большинство из них применимо даже к современным потребительским процессорам Intel.

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

Содержимое EXE-файла описано в переносимом исполняемом файле. Он содержит код, данные и инструкции для ОС о том, как загрузить файл.

Между машинным кодом и сборкой существует соответствие 1: 1. Программа дизассемблера выполнит обратную операцию.

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

Если это так чуждо вам, как кажется, я не думаю, что отладчик или дизассемблер помогут - сначала вам нужно выучить программирование на ассемблере; изучить архитектуру процессора (множество документации можно загрузить с сайта Intel). А поскольку большая часть машинного кода генерируется компиляторами, вам необходимо понимать, как компиляторы генерируют код - самый простой способ написать множество небольших программ, а затем их дизассемблировать, чтобы увидеть, во что превратился ваш C / C ++.

Пара книг, которые помогут вам понять: -

Чтобы получить представление, установите точку останова на каком-нибудь интересном коде, а затем перейдите в окно ЦП.

Если вас интересует больше, проще скомпилировать короткие фрагменты с помощью Free Pascal, используя параметр -al.

FPC позволяет выводить сгенерированный ассемблер во множестве форматов ассемблера (TASM, MASM, GAS) с помощью параметра -A, и вы можете чередовать исходный код паскаля в комментариях (и других) для облегчения перекрестных ссылок.

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

Я бы посоветовал взять немного исходного кода Windows C и собрать и начать его отладку в Visual Studio. Переключитесь в режим разборки и переходите по командам. Вы можете увидеть, как код C был скомпилирован в машинный код, и посмотреть, как он выполняется шаг за шагом.

Относительно этого вопроса, кто-нибудь все еще читал такие вещи, как CD 21?

Я вспомнил Сандру Баллок в одном шоу, которая фактически читала экран с шестнадцатеричными числами и выясняла, что делает программа. Вроде как текущая версия чтения кода Матрицы.

Если вы читаете такие вещи, как CD 21, как вы запоминаете различные комбинации?

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

(Я понятия не имею, что такое ACD See, так что относитесь к этому с большой долей скепсиса, но я действительно знаю, что некоторые программы создаются таким образом.)

Каждая инструкция находится в машинном коде и хранится в специальной области памяти внутри процессора. В ранних книгах INTEL для своих инструкций использовался машинный код, поэтому следует попытаться получить такие книги, чтобы понять это. Очевидно, что сегодня машинный код нелегко получить. Что было бы неплохо, так это программа, которая может преобразовывать шестнадцатеричный код в машинный. Или сделайте это вручную _ !! скучный

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

Исполняемый файл, который вы видите, имеет формат Microsofts PE (Portable Executable). По сути, это контейнер, который содержит некоторые данные о программе, относящиеся к операционной системе, а сами данные программы разделены на несколько разделов. Например, код, ресурсы, статические данные хранятся в отдельных разделах.

Формат раздела зависит от того, что в нем находится. Раздел кода содержит машинный код в соответствии с исполняемой целевой архитектурой. Чаще всего это Intel x86 или AMD-64 (то же, что и EM64T) для двоичных файлов Microsoft PE. Формат машинного кода - CISC, он восходит к 8086 и более ранним версиям. Важным аспектом CISC является то, что размер его инструкций непостоянен, вы должны начать чтение в нужном месте, чтобы извлечь из него что-то ценное. Intel издает хорошие руководства по набору инструкций x86 / x64.

Вы можете использовать дизассемблер для непосредственного просмотра машинного кода. В сочетании с руководствами вы в большинстве случаев можете угадать исходный код.

Читайте также: