Почему мой машинный код ведет себя не так, как ожидалось?

Я использую отладчик DOSBox в качестве среды для изучения того, как процессор на базе x86/64 проходит машинный код.

В качестве справки я использую пример «вывода с разделителями длины DOS2», который я нашел по адресу: https://montcs.bloomu.edu/~bobmon/Information/LowLevel/Assembly/hello-asm.html

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

Я использую шестнадцатеричный редактор для ввода байтов вручную, и вот шестнадцатеричный код, который я сейчас сохранил в файле с именем «executable.com»:

68 DD 01 1F B2 00 B6 00 B1 06 B3 01 B4 40 B0 00
CD 21 B4 4C B0 00 CD 21 48 65 6C 6C 6F 21 0A D0
0A 24 20

Выполнение этого файла через отладчик дает следующий обзор кода:

01DD:0100  68DD01              push 01DD
01DD:0103  1F                  pop  ds
01DD:0104  B200                mov  dl,00
01DD:0106  B600                mov  dh,00
01DD:0108  B106                mov  cl,06
01DD:010A  B301                mov  bl,01
01DD:010C  B440                mov  ah,40
01DD:010E  B000                mov  al,00
01DD:0110  CD21                int  21
01DD:0112  B44C                mov  ah,4C
01DD:0114  B000                mov  al,00
01DD:0116  CD21                int  21

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

Есть мысли о том, что происходит?


person DOOMDUDEMX    schedule 02.05.2019    source источник
comment
Вы ставите 0x1dd в ds и 0 в dx. Таким образом, ds:dx равно 1dd:0. Пробовали ли вы сделать дамп памяти в этот момент? Что-то вроде d 1dd:0?   -  person David Wohlferd    schedule 03.05.2019
comment
В дополнение к загрузке dx со смещением строки 0118, вы должны загрузить bx со значением 1 и cx со значением 6. Код, который у вас есть, не инициализирует верхнюю половину bx и cx.   -  person prl    schedule 03.05.2019
comment
Вы можете выполнять пошаговый машинный код в обычном 32- или 64-битном процессе, работающем под вашей обычной ОС, используя отладчик. например скомпилируйте программу C hello world и используйте отладчик, такой как GDB, который имеет представление asm/disassembly. (Предпочтительно тот, который будет отображать шестнадцатеричный дамп машинного кода рядом.) Или, например, функции, написанные в машинном коде x86, см., например, Советы для игры в гольф в машинном коде x86/x64, в котором есть ссылки на различные ответы, написанные в машинном коде x86 (с соответствующей сборкой).   -  person Peter Cordes    schedule 03.05.2019
comment
Кроме того, вот забавный вариант: Определите версию вашего языка одни и те же байты машинного кода декодируются тремя разными способами, создавая функцию, которая возвращает 16 в 16-битном режиме, 32 в 32-битном режиме и 64 в 64-битном режиме.   -  person Peter Cordes    schedule 03.05.2019
comment
@PeterCordes: это помечено как x86-16. Не помогает, когда целевой код использует debug.exe и нацелен на среду, которая либо эмулирует реальный режим (или виртуализируется). GDB на самом деле не подходит для отладки 16-битного кода. Поскольку он использует debug.exe, он может использовать его для пошагового выполнения своего кода ;-)   -  person Michael Petch    schedule 03.05.2019
comment
@MichaelPetch: OP сказал изучить, как процессор на базе x86/64 проходит машинный код. Я отвечал на это, чтобы указать, что игра с DOS и 16-битным машинным кодом не является необходимой для достижения конечной цели, равно как и понимание интерфейса системных вызовов DOS int 21h. (Но, конечно, одношаговое выполнение int 21h может быть проще, чем обход библиотечной функции-оболочки вокруг системного вызова Windows или Linux.)   -  person Peter Cordes    schedule 03.05.2019
comment
@DavidWohlferd Я пытался сделать дамп памяти, и память выглядит случайной. Код, который вы видите, представляет собой всю программу, поэтому я никогда ничего не загружаю в память. Насколько я понимаю (или, может быть, неправильно понимаю) пример, сегмент кода перепрофилирован как хранилище данных, и строка должна быть загружена оттуда. Создает ли msg db «Новый привет, мир!», 0x0d, 0x0a машинный код, который фактически каким-то образом перемещает данные в память?   -  person DOOMDUDEMX    schedule 03.05.2019
comment
@prl Когда я запускаю отладчик, регистры инициализируются всеми нулями, поэтому верхние половины bx и cx уже равны 0.   -  person DOOMDUDEMX    schedule 03.05.2019
comment
@PeterCordes Думаю, я старался быть кратким. Моя конечная цель — узнать больше о различных типах исполняемых файлов, таких как MZ и PE, чтобы разработать простой компилятор и, в конечном итоге, узнать больше об архитектуре языка программирования. COM-формат казался хорошим началом, так как это, по сути, просто чистый машинный код.   -  person DOOMDUDEMX    schedule 03.05.2019
comment
Да, я понял это, но полагаться на него по-прежнему плохо, особенно когда так легко просто загрузить весь регистр.   -  person prl    schedule 03.05.2019
comment
@prl Это правда, что это плохая практика. В настоящее время я читаю таблицы инструкций, чтобы найти, какие коды операций использовать, и это не стоило отвлекать, поскольку я ясно видел, что в регистр было загружено правильное значение. Однако я понимаю, что это может вызвать путаницу у третьих лиц, читающих мой код.   -  person DOOMDUDEMX    schedule 03.05.2019
comment
Хорошо, я думаю, это имеет смысл, но существуют цепочки инструментов, чтобы поместить машинный код в нужный раздел исполняемого файла для вас, например. любой ассемблер позволит вам написать db 0x68, 0xdd, 0x01, ... и ассемблировать+ссылку, чтобы в конечном итоге получить текстовый или кодовый раздел 32- или 64-битного исполняемого файла. Однако мне было проще изучить машинный код x86, написав исходный код на ассемблере и посмотрев на дизассемблирование. По сути, нет необходимости запоминать карту кода операции или писать ее от руки, чтобы изучить приемы машинного кода.   -  person Peter Cordes    schedule 03.05.2019
comment
Тем не менее, узнавать об ограничениях и крайних случаях схемы кодирования ModRM+SIB весело. Это объясняет, почему 32/64-битные режимы адресации имеют несколько ограничений: rbp не допускается в качестве базы SIB?. Конечно, 16-битное кодирование ModRM совершенно другое (поскольку 16-битное не имеет возможности кодирования SIB для 2-регистровых режимов). За исключением этого, 16-битный машинный код в основном такой же, как и другие режимы. Еще одно отличие состоит в том, что префиксы VEX не работают в 16-битном режиме. (Очевидно, незаконная кодировка псевдонимов на самом деле была намеренно использована в качестве ловушки.)   -  person Peter Cordes    schedule 03.05.2019
comment
@PeterCordes Я пытался найти способ сборки в com-файл, но то, что я пробовал до сих пор, привело к более сложному исполняемому файлу. Есть ли у вас какие-либо предложения относительно того, какой ассемблер и / или метод использовать для этой цели?   -  person DOOMDUDEMX    schedule 03.05.2019
comment
nasm foo.asm создает плоский двоичный файл, также известный как .com по умолчанию. т. е. -f bin является форматом вывода по умолчанию. Используйте org 0x100, если вы хотите использовать символы для адресов, чтобы NASM знал, куда будет загружен код.   -  person Peter Cordes    schedule 03.05.2019
comment
@PeterCordes Спасибо! Я попробую это и посмотрю, смогу ли я найти, что не так с моим кодом, и, надеюсь, дать ответ на мой собственный вопрос!   -  person DOOMDUDEMX    schedule 04.05.2019
comment
Когда ваш файл .com загружен, появляется сообщение Hello! строка находится в cs:0118, поэтому я бы предложил загрузить ds из cs (вместо жестко запрограммированного 01DD, что является плохой практикой и, возможно, ненадежным) и dx с 0118 (вместо 0000, что явно неправильно).   -  person Ruud Helderman    schedule 04.05.2019
comment
@RuudHelderman Да, я начал с этого, но, поскольку у меня это не сработало, я изменил значение, чтобы исследовать память. К сожалению, я забыл изменить это, прежде чем публиковать свой код.   -  person DOOMDUDEMX    schedule 04.05.2019


Ответы (1)


Я воссоздал пример, используя NASM, предложенный Питером Кордесом, который сначала дал те же результаты, что и одна из моих предыдущих попыток, но когда я добавил «org 0x100» в начало моего исходного кода сборки, я получил результат, который я искал.

По сути, это добавляет смещение ко всем адресам, которое необходимо, поскольку код загружается в память по адресу 0x100, а не 0x00. В этом примере «org 0x100» привело к изменению только одного бита в полученном результате, но этот один бит был разницей между чтением из памяти в правильном месте и чтением 256 байтов раньше.

Вот как в итоге получился машинный код:

BA 13 01 B9 06 00 BB 01 00 B8 00 40 CD 21 B8 00
4C CD 21 48 65 6C 6C 6F 21

И код сборки, используемый для его создания:

org 0x100

mov dx, msg
mov cx, 0x06
mov bx, 1
mov ax, 0x4000
int 0x21
mov ax, 0x4C00
int 0x21

msg db "Hello!"
person DOOMDUDEMX    schedule 04.05.2019
comment
IIRC, .com указывает крошечную модель памяти, в которой все сегментные регистры равны. Таким образом, вам не нужно самостоятельно копировать CS в DS, и вы можете рассчитывать на то, что все, что запускает .com файлов, загружает вашу программу таким образом. - person Peter Cordes; 04.05.2019
comment
На этот раз в вашем коде отсутствует дескриптор устройства в BX ! (mov bx, 1 ; Device handle: STDOUT (screen)). Также функция DOS WriteFile не требует установки AL=0, как вы написали. - person Fifoernik; 04.05.2019
comment
@Fifoernik Вау, должно быть, вчера я был очень рассеян. Что ж... Код дал правильный результат. Однако я обновлю свой ответ правильным кодом на случай, если кто-нибудь придет и скопирует мою ошибку! Спасибо, что указали на это! - person DOOMDUDEMX; 05.05.2019