Просто нарисовать картинку ...
Прежде всего, вы не найдете способа программирования в машинном коде, с которым не связана сборка, и это должно быть очевидно.Любая приличная ссылка на инструкцию, большинство из которых вы найдете, содержит ассемблер для некоторого ассемблера вместе с машинным кодом, потому что вам действительно нужен какой-то способ ссылки на некоторый битовый шаблон, и язык ассемблера - это тот язык.
Так что посмотрите 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, вы используете язык ассемблера и за один или два шага цепочки инструментов получаете от источника сборки к результату двоичного машинного кода.В худшем случае вы пишете специальную программу для получения из выходных данных цепочки инструментов и манипулируете этими битами в другие биты.Вы не выбрасываете инструменты, доступные для записи необработанных битов вручную, вы просто переформатируете формат выходного файла.