Как писать на машинном коде
Перейти к содержимому

Как писать на машинном коде

  • автор:

Машинный код и компиляция в него — это как?

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

Отслеживать
6,651 6 6 золотых знаков 30 30 серебряных знаков 52 52 бронзовых знака
задан 21 апр 2020 в 8:02
Krutos VIP Krutos VIP
69 2 2 серебряных знака 9 9 бронзовых знаков

Если идти от сложного: изучить мануалы intel по платформе x86 — чтобы знать как писать машинный код, перед этим еще изучить ассемблер, хотя бы на минимальном уровне; изучить формат исполняемых файлов для вашей системы (portable executable для Windows или ELF для Linux); изучить как вообще создаются компиляторы — тут «Книга Дракона» в помощь.

21 апр 2020 в 8:22

Если от более простого — отказаться от идеи самому создавать бинарник, а генерировать например промежуточный Си код, который уже будет компилироваться в бинарный (или воспользоваться инструментарием LLVM). Но опять же в этом случае все равно нужно будет изучить «Книгу Дракона».

21 апр 2020 в 8:25
Связанный вопрос: Генерация exe файла
21 апр 2020 в 8:25
Не совсем по теме, но, возможно, вдохновит — история одного байта, fermi paradox
– user249284
21 апр 2020 в 9:54

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Baremetal

Каждый конкретный процессор (например, Intel Core i3-4160 или ARM Cortex-A9) имеет свою микроархитектуру и реализует архитектуру уровня набора команд (англ. instruction set architecture).

  • Микроархитектура определяет структуру процессора на уровне электронных компонентов и логических вентилей.
  • Архитектура уровня набора команд (ISA), грубо говоря, определяет то, какие команды может выполнять процессор. Эта архитектура абстрагированна от микроархитектуры. Процессоры разных комнаний могут реализовывать одну и ту же архитектуру (например, многие процессоры Intel и AMD реализует одно и то же семейство архитектур x86).

Если два процессора реализуют одну и ту же ISA, то они могут исполнять одни и те же программы. ISA определяет, какие команды доступны программисту, какие регистры он может использовать, как он может использовать страничную адресацию, виртуальную память и т. д. Кроме того, она определяет формат команд, которые понимает процессор.

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

Написать программу в машинных кодах достаточно просто — нужно лишь взять справочник по ISA (например, Intel 64 and IA-32 Architectures Software Developer Manuals), которую реализует ваш процессор и написать нужные команды байт за байтом.

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

Например, одна инструкция ассемблера x86 MOV может кодировать около 20 различных инструкций процессора MOV 1 . Ассемблер читает вашу программу на языке ассемблера и переводит ее в бинарный файл 2 , который, опять же, является просто последовательность байт, кодирующих подряд идущие инструкции процессора.

Вот так может выглядет отрывок программы на языке ассемблера:

cli lgdt (gdtr) mov %cr0, %eax or $0x1, %eax mov %eax, %cr0 

Вот так выглядит программа на машинном языке:

0000000 05ea 007c 3100 8ec0 8ed8 bcd0 7c00 1688 0000010 7cdb c031 c08e 00bb 8a80 db16 b67c b100 0000020 b502 b000 e830 0053 59e8 8400 75c0 fa30 0000030 010f f416 0f7c c020 8366 01c8 220f eac0 0000040 7c44 0008 b866 0010 d88e c08e e08e e88e 0000050 d08e 00bc 07c0 e800 03a4 0000 ebf4 befd 0000060 7cbc 03e8 f400 fdeb 5350 30fc b4ff ac0e 0000070 c084 0474 10cd f7eb 585b b4c3 cd02 7213 0000080 3102 c3c0 1e9c 0657 fa56 c031 d88e 10bf 0000090 f705 8ed0 bec0 0500 058a 2650 048a 2650 00000a0 04c6 c600 be05 8026 be3c 2658 0488 8858 00000b0 3105 74c0 4001 075e 1f5f c39d 3241 2030 00000c0 7369 6420 7369 6261 656c 2e64 4820 6c61 00000d0 2074 6874 2065 5043 2e55 0000 0000 0000 00000e0 0000 0000 ffff 0000 9a00 00cf ffff 0000 00000f0 9200 00cf 0017 7cdc 0000 0000 0000 0000 

Очевидно, что асссемблерный код и читать, и писать проще.

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

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

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

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

Если брать в пример ОС Windows 10, она работает с исполняемыми файлами .exe , которые имеют специальный формат, называемый Portable Executable. Он имеет довольно сложную структуру. Помимо собственно набора машинных команд он содержит в себе информацию необходимую для определения адреса и размера секций, таблиц импорта и экспорта, специальную сигнатуру и т. д.

Поэтому чтобы вручную написать программу в машинных кодах, которая будет запускаться в Windows 10, например, нам, по-мимо написания самой программы, потребуется привести ее к формату Portable Executable.

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

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

Можете начать с написания программ на языке ассемблера (да, вам придется еще выучить синтаксис конкретного языка ассемблера и диалект Intel или AT&T). «Hello, World» на языке NASM будет выглядеть так:

; ---------------------------------------------------------------------------- ; helloworld.asm ; ; This is a Win32 console program that writes "Hello, World" on one line and ; then exits. It needs to be linked with a C library. ; ---------------------------------------------------------------------------- global _main extern _printf section .text _main: push message call _printf add esp, 4 ret message: db 'Hello, World', 10, 0 

А нужно ли вам это?

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

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

А непрактично это в первую очередь потому, что ничего сложнее «Hello, World!» в машинных кодах вы не напишете. На ассемблере — да, напишете, но потратите на это колоссальное количество времени, которое можно было бы потратить на более полезные вещи.

1. Что интересно, инструкция MOV в x86 является Тьюринг-полной, т. е. любая программа может быть написана с использованием одной только этой инструкции. Есть даже специальный компилятор, который использует только одну эту инструкцию.

2. Некоторые ассемблеры могут сразу формировать исполняемые файлы в нужном формате. В том числе и Portable Executable.

3. Я говорю о современных ОС типа Windows или Linux.

Как написать «Hello World» на машинном коде?

Недавно наткнулся на видео, где некий сумасшедший написал программу, которая выводит в консоль слова «Hello World» на бинарном/машинном коде (если честно я не уверен что это именно).

Расскажите пожалуйста, как можно повторить результат (не советуйте скопировать код из видео, хочу именно понимать как такое сделать)? Может какая-то литература по этому поводу?

  • Вопрос задан более трёх лет назад
  • 32690 просмотров

Комментировать
Решения вопроса 1

Вам достаточно изучить ассемблер и всё станет понятно.
Каждая команда ассемблера транслируется «дословно» в машинный код.
Т.е. например команда pushl %edx превратится в один байт 82

Например программа на ассемблере:

push %ebp mov %esp,%ebp call 0x8048298 cmp $0x41,%eax jne 0x80483ce push $0x80484b0 call 0x80482c8 add $0x4,%esp mov $0x0,%eax mov %ebp,%esp pop %ebp ret

превратится в машинный код:

0x55 0x89 0xe5 0xe8 0xfc 0xff 0xff 0xff 0x83 0xf8 0x41 0x75 0x0d 0x68 0x00 0x00 0x00 0x00 0xe8 0xfc 0xff 0xff 0xff 0x83 0xc4 0x04 0xb8 0x00 0x00 0x00 0x00 0x89 0xec 0x5d 0xc3

Почитать можно здесь и здесь . В целом подойдет любая книга по ассемблеру.

Ответ написан более трёх лет назад

Mrrl

Во времена MSDOS было бы понятно — int 21h, и вывод на консоль в кармане. Можно даже структуры exe-файла не знать, писать сразу в com. А сейчас что делать? Поможет ли ассемблер?

Mrrl: Не понял ваш вопрос. На чистом асме писать нет смысла — только для общего развития или для векторизации SIMD инструкций и для реверс инжиниринга. Если нужен асм, то самый простой способ — делать вставки в код на С/С++. А трансляцией в машинный код должен заниматься компилятор

Mrrl

asd111: Я так понял, что вопрос — как написать программу полностью. Желательно, в 16-ричном редакторе. Любая программа, использующая C/C++ займёт огромный объём, без ошибок вручную его не сгенерируешь и не введёшь. Для com-файла в MSDOS такие трюки были вполне реальны, но как это сделать в современных операционных системах?

Mrrl: В современных системах даже если писать всё вручную, меньше чем код на С в любом случае не получится, т.к. необходимо сохранить формат исполняемого файла т.е. для Linux например нужно будет прописать все заголовки ELF файла в то время как С — по сути дела высокоуровневый ассемблер, т.е. код на С практически дословно транслируется в код на ассемблере + готовые заголовки под нужную ОС + оптимизации компилятора.

Mrrl

asd111: Не забывайте, что если вы пишете на С, то вам будет необходимо иметь правильную версию C-библиотеки, а она тоже занимает место (и в какой-нибудь Embedded XP её может сразу не оказаться). Либо использовать статическую линковку, что очень резко увеличит размер кода. Системные вызовы в этом смысле экономнее. Судя по кодам для Linux, у них есть команда syscall (тоже какое-то прерывание?), которая позволяет написать совсем короткую программу. Про ELF-файл пока не скажу, с исполняемыми файлами для Linux мне разбираться пока не пришлось.

Программирование в машинном коде и на языке ассемблера

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

16 00 58 21 00 00 06 08 29 17 D2 0Е 40 19 05 С2 08 40 С9

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

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

LOAD Temperature

JUMP POSITIVE to Fault_Handler

довольно просто понять все, что происходит.

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

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

Популярное

  • Проектирование АСУТП. Книга 2. Методическое пособие
  • П-, ПИ-, ПД-, ПИД — регуляторы
  • В мире АСУТП
  • Показатели качества процесса управления
  • Типовые звенья систем регулирования
  • Законы регулирования: П, ПИ, ПИД
  • Первичные преобразователи. Датчики
  • Определение параметров переходных характеристик
  • Передаточная функция
  • Сигналы и стандарты
  • Классификация систем автоматического регулирования
  • Стадии и этапы создания АСУТП

Как писать на машинном коде

Многие любители не испытывают серьезных трудностей в овладении БЕЙСИКом. Для этого достаточно немного практики. Но рано или поздно они приходят к барьеру «машинного кода». Как это ни печально, но некоторые так перед ним и останавливаются. Это ни в коей мере не связано с отсутствием желания или способностей, просто многие не знают, с чего начать. Если в БЕЙСИКе можно начинать с чего угодно (при ошибке компьютер сам Вас поправит), то здесь Вы оказываетесь с процессором один на один, и такой метод проб и ошибок не срабатывает.

Одним словом, есть некий психологический барьер, который бывает трудно преодолеть в одиночку. Известно, что для того, чтобы научиться программировать, надо взять и начать программировать. «ИНФОРКОМ» предлагает Вам следующий компромиссный подход — сначала в рамках этой главы мы, беря «быка за рога», просто начнем программировать, а затем посвятим оставшуюся часть книги систематическому изложению материала.

Итак, давайте напишем первую программу в машинном коде. Прежде всего, выделим для нее область памяти. Если Вы читали нашу книгу «Большие возможности Вашего «ZX-Spectrum`а», то знаете, что для БЕЙСИКа в оперативной памяти компьютера отведена область памяти, начинающаяся с адреса, на который указывает системная переменная PROG и заканчивается адресом, на который указывает системная переменная RAMTOP. Предположим, что Вы хотите записать программу в машинных кодах, начиная с адреса 30000. Дайте команду CLEAR 29999. Эта команда установит RAMTOP в 29999 и Ваша программа будет защищена от возможной порчи из БЕЙСИКа. Даже если Вы дадите команду NEW, области памяти, находящиеся выше RAMTOP, не будут поражены.

Теперь дайте две прямые команды одну за другой:

Мы сейчас записали два числа в нужные нам адреса. Они образуют простейшую программу. Выполнить ее можно командой RANDOMIZE USR 30000. Попробуйте сами. Вам покажется, что ничего не произошло, но это не так. Сначала процессор обратился по адресу 30000 и нашел там число 0, которое обозначает машинный код операции NOP. Операция NOP ( no operation — нет операции) дает команду процессору, что ничего делать не надо. В течение 0,0000014 сек. он действительно ничего не делает, а затем переходит к следующему адресу, где находит число 201.

Это команда RET ( return — возврат). Она дает процессору указание прекратить в этом месте программу в машинных кодах и вернуться туда, откуда она вызывалась, т.е. в нашем случае — в БЕЙСИК. Это самое процессор и сделал, о чем Вы получили сообщение БЕЙСИКа «О.К.».

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

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

Перевод этих мнемоник в машинные коды тоже можно поручить компьютеру. Для этого существуют специальные программы, которые тоже называют Ассемблерами. Есть и противоположные программы — Дизассемблеры. Они наоборот переводят машинные коды в мнемоники Ассемблера.

И тех программ и других достаточно много. Часто они объединяются в пакеты. Широко распространены пакеты GENS3/MONS3 фирмы HISOFT и EDITAS / MONITOR 16/48 фирмы PICTURESQUE . Здесь GENS 3 и EDITAS — Ассемблеры, а MONS 3, MONITOR 16 и MONITOR 48 — Дизассемблеры.

Теперь давайте вернемся к нашей первой программе и попробуем ее несколько развить, чтобы она все же что-то делала. Процессор Z-80 имеет несколько регистров, у которых есть имена – «А», «В», «С» и т.д. Каждый из них может содержать одно какое-либо целое число от 0 до 255 (т.е. один байт).

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

Так, например, команда Ассемблера LD B,A (машинный код — 71) означает «загрузить содержимое регистра А в регистр В». LD — это сокращение от LOAD (загрузка).

Точно так же LD C,B (машинный код 72) означает «загрузить в регистр С содержимое регистра В». Можно загружать в регистры и целые числа. Например, LD A, n — означает «загрузить в регистр А целое число n », где n может быть числом от 0 до 255. До этого все команды были однобайтными. Эта же команда — двухбайтная. Сначала идет машинный код — 62, а за ним само число — n . Так, например, LD A, 77 (загрузить в регистр А число 77) будет выглядеть так: 62,77. Здесь 62 — код операции, — он сообщает процессору, что надо сделать, а 77 — это операнд. Заметим здесь же, что бывают операции и трехбайтные и четырехбайтные. Первый байт, как правило, — код операции, а следующие за ним — операнды. Мы говорим «как правило» потому, что есть некоторые операции, код которых записывается двумя байтами [прим.1].

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *