Как я могу написать необработанный машинный код для x86 без использования сборки? - PullRequest
0 голосов
/ 26 июня 2018

Я хотел бы иметь возможность писать необработанный машинный код без ассемблера или любого другого языка более высокого уровня, который можно поместить непосредственно на флэш-диск и запустить.Я уже знаю, что для этого нужно отформатировать заголовки главной загрузочной записи (что мне удалось сделать вручную) на диск.Я выполнил это и успешно смог получить строку текста для отображения на экране, используя ассемблерный код в первом секторе (в данном случае первые 512 байт) накопителя, на котором установлен мой код.Тем не менее, я хотел бы иметь возможность записывать необработанный шестнадцатеричный код на диск, как я это делал для форматирования MBR, без какого-либо инструмента, такого как сборка, чтобы помочь мне.Я знаю, что есть способ сделать это, но я действительно не смог найти ничего, что не упоминает сборку.Где я могу найти информацию об этом?Поиск в Google машинного кода или x86-программирования - это не то, чего я хочу.

Ответы [ 3 ]

0 голосов
/ 26 июня 2018

Если вы действительно хотите лучше понять машинный код x86, я бы порекомендовал вам начать с просмотра вывода ассемблера, чтобы увидеть, какие байты он собрал в выходной файл для каждой строки исходного кода asm.

nasm -fbin -l listing.txt foo.asm предоставит вам список, включающий необработанные шестнадцатеричные байты и строку источника, или nasm -fbin -l/dev/stdout foo.asm | less передаст список прямо в средство просмотра текста.См. эту функцию смешения цветовых ключей в 13 байтах машинного кода x86, которую я написал на codegolf.SE, для примера того, как выглядит вывод.

Вы также можете разобрать двоичный файл послесоздавая это нормально.ndisasm работает на плоских двоичных файлах и выдает тот же формат шестнадцатеричных байтов + инструкция asm.Также можно использовать другие дизассемблеры, такие как objdump: Разборка плоского двоичного файла с использованием objdump .

Полусвязанное: Как преобразовать шестнадцатеричный код в инструкции x86


В руководствах Intel x86 полностью указано, как кодируются инструкции : см. справочное руководство по набору настроек ins.2 , глава 2 ФОРМАТ ИНСТРУКЦИИ для разбивки префиксов, кодов операций, ModR / M + необязательный SIB и необязательное смещение, а также немедленное.

Учитывая это, вы можете прочитать документацию для каждой инструкции о том, как ее кодировать, например, D1 /4 (shl r/m32, 1) означает, что байт кода операции равен D1, а поле /r в ModRM должно быть равно 4. (Поле /r работает как 3 дополнительных бита кода операции для некоторых инструкций.)

Существует также отображение приложенияОперационные байты возвращаются к инструкциям и другим разделам этого руководства.

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


См. Также советы по игре в гольф в машинном коде x86 для многих изворотов кодировки команд x86: например, существуют однобайтовые кодировки для inc/dec полный регистр (кроме 64-битного режима).Конечно, он сфокусирован на инструкции длина , но если вы не настаиваете на том, чтобы самостоятельно искать фактические кодировки, интересная часть состоит в том, какие формы инструкций имеют разные или специальные кодировки.Несколько ответов на эти советы Q & A содержат вывод objdump -d, показывающий байты машинного кода и разбор синтаксиса AT & T.

0 голосов
/ 27 июня 2018

Просто нарисовать картинку ...

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

Так что посмотрите nopнапример, вы найдете битовый шаблон 10010000 или 0x90.Поэтому, если я хочу добавить инструкцию nop в свою программу, я добавляю байт 0x90.Таким образом, даже если вы вернетесь к очень ранним процессорам, вам все равно хотелось программировать на ассемблере и вручную собирать карандашом и бумагой, а затем использовать DIP-переключатели, чтобы синхронизировать программу в памяти, прежде чем пытаться запустить ее.Потому что это имеет смысл.Спустя десятилетия, даже чтобы продемонстрировать программирование машинного кода, особенно с таким болезненным набором инструкций, как x86, вы начинаете со сборки, сборки, затем разбираете, затем говорите об этом, вот так:

top:
    mov ah,01h
    jmp one
    nop
    nop
one:
    add ah,01h
    jmp two
two:
    mov bx,1234h
    nop
    jmp three
    jmp three
    jmp three
three:
    nop
    jmp top

nasm -f aout so.s -o so.elf
objdump -D so.elf

00000000 <top>:
   0:   b4 01                   mov    $0x1,%ah
   2:   eb 02                   jmp    6 <one>
   4:   90                      nop
   5:   90                      nop

00000006 <one>:
   6:   80 c4 01                add    $0x1,%ah
   9:   eb 00                   jmp    b <two>

0000000b <two>:
   b:   66 bb 34 12             mov    $0x1234,%bx
   f:   90                      nop
  10:   eb 04                   jmp    16 <three>
  12:   eb 02                   jmp    16 <three>
  14:   eb 00                   jmp    16 <three>

00000016 <three>:
  16:   90                      nop
  17:   eb e7                   jmp    0 <top>

, так что просто первыйпара инструкций описывает проблему и почему в asm так много смысла ...

Первая, которую вы можете легко запрограммировать в машинном коде b4 01 mov ah, 01h, мы переходим к перегруженной инструкции mov в документации и находимнепосредственный операнд для регистрации.Если у нас есть данные, у нас есть один байт, так что это не слово, поэтому бит слова не установлен, мы должны искать регистр, чтобы найти а, в итоге получим b4, а ближайший - 01h.Не так уж плохо, но теперь прыгаю. Я хочу перепрыгнуть через что-то, ну, как много?Какой прыжок я хочу использовать?Хочу ли я быть консервативным и использовать наименьшее количество байтов?

Я вижу, что хочу перепрыгнуть через две инструкции, мы можем легко найти nops, чтобы узнать, что это один байт, 0x90, инструкции.поэтому внутрисегментное прямое короткое замыкание должно работать по выбору ассемблера.0xEB а каково смещение?0x02, чтобы перепрыгнуть через два БАЙТА инструкций между тем, где я нахожусь и куда я хочу пойти.

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

Сейчас я смотрю руководство по intel 8086/8088 прямо сейчас, внутрисегментная прямая краткая инструкция комментариев к знаку расширена, внутрисегментная прямая не говорит, что знак расширен, хотяПроцессор в это время был 16 бит, но у вас было еще несколько битов сегмента, поэтому, только прочитав руководство, не имея доступа к инженерам-разработчикам и не использовав отладочный ассемблер для справки, как бы я узнал, если бы я мог использовать 16прямой переход для последней инструкции, которая ветвится назад?В этом случае ассемблер выбрал смещение в байтах, но что, если ...

Я использую 16-битные ручные, но 32/64-битные инструменты, поэтому я должен учитывать это, но я мог и сделал это:

three:
    nop
db 0xe9,0xe7,0xff,0xff,0xff

вместо jmp top.

00000016 <three>:
  16:   90                      nop
  17:   e9 e7 ff ff ff          jmp    3 <top+0x3>

для 8086, который был бы 0xe9,0xe7,0xff

   db 0xb4,0x01
   db 0xeb,0x02
   db 0x90
   db 0x90

, так что теперь, если я захочу изменить один из перепрыгиваемых nops на mov

   db 0xb4,0x01
   db 0xeb,0x02
   db 0xb4,0x11
   db 0x90

но он сломан, теперь я должен исправить прыжок

   db 0xb4,0x01
   db 0xeb,0x03
   db 0xb4,0x11
   db 0x90

Теперь измените это на добавление

   db 0xb4,0x01
   db 0xeb,0x03
   db 0x80,0xc4,0x01
   db 0x90

Теперь я должен изменить прыжок снова

   db 0xb4,0x01
   db 0xeb,0x04
   db 0x80,0xc4,0x01
   db 0x90

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

a:
...
jmp x
...
jmp a
...
x:

, когда мы передаем прыжок x, мы выделяем для него 2 байта?затем перейдите к jmp a, выделите для него два байта, и в этот момент мы, возможно, выяснили все остальныеинструкции между jmp a и a: и он просто вписывается в двухбайтовый переход.но затем, в конце концов, мы получаем x: чтобы найти, что jmp x должен быть 3 байта, что слишком сильно толкает jmp, теперь это должен быть трехбайтовый jmp, что означает, что мы должны вернуться к jmp x и отрегулировать длядополнительный байт от jmp a теперь составляет три байта вместо предполагаемых 2.

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

Так что я могу сделать это

    mov ah,01h
top:
    add ah,01h
    nop
    nop
    jmp top

затем

nasm so.s -o so
hexdump -C so
00000000  b4 01 80 c4 01 90 90 eb  f9                       
|.........|
00000009

Или я могу сделать это:

#include <stdio.h>
unsigned char data[]={0xb4,0x01,0x80,0xc4,0x01,0x90,0x90,0xeb,0xf9};
int main ( void )
{
    FILE *fp;
    fp=fopen("out.bin","wb");
    if(fp==NULL) return(1);
    fwrite(data,1,sizeof(data),fp);
    fclose(fp);
}

Я хочу добавить nop в цикл:

    mov ah,01h
top:
    add ah,01h
    nop
    nop
    nop
    jmp top

против

#include <stdio.h>
unsigned char data[]={0xb4,0x01,0x80,0xc4,0x01,0x90,0x90,0x90,0xeb,0xf8};
int main ( void )
{
    FILE *fp;
    fp=fopen("out.bin","wb");
    if(fp==NULL) return(1);
    fwrite(data,1,sizeof(data),fp);
    fclose(fp);
}

Если бы я действительно пытался писать в машинном коде, я бысделать что-то вроде этого:

unsigned char data[]={
0xb4,0x01, //top:
0x80,0xc4,0x01, //add ah,01h
0x90, //nop
0x90, //nop
0x90, //nop
0xeb,0xf8 //jmp top
};

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

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

0 голосов
/ 26 июня 2018

http://ref.x86asm.net/coder32.html

Хотя я действительно не знаю, почему вы это сделаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...