Вы не можете просто переключить сборку NASM в режим [bits 64]
и ожидать, что qemu выполнит ваш код в длинном режиме. То, как вы вызываете, предполагает использование режима Real 8086, который равен bits 16
(по умолчанию для NASM). Возможно, из-за того, что вы используете многие 8-битные или размерно-зависимые c операции, код работает в любом случае, но не так, как ожидалось.
Кроме того, вы прокомментировали mov al, sil
как "переместить счетчик" al
, но это не счетчик. И начальный mov sil, CHAR
не помещает символ, на который указывает «CHAR», в sil
, он фактически помещает адрес CHAR в регистр (предназначенный как sil
, но независимо от того, что это будет интерпретировано как в R86M) , И add sil, 48
также не имеет никакого смысла. 48 (30h) - это правильное значение, которое нужно добавить, чтобы преобразовать десятичное число (от 0 до 9) из числового значения c в ASCII-ди git этого числа. Это не обобщение c «для отображения в виде ASCII» преобразования, оно работает только для десятичных синглов-ди git чисел.
Вы также не смогли упомянуть, что прогон qemu застревает зацикливаться навсегда, отображение различных символов в бесконечном l oop.
Вот разборка в 16-битном режиме вашего кода:
$ ndisasm -b 16 -k 0x3D,$((512 - 0x3D)) pyramid
00000000 40 inc ax
00000001 B63D mov dh,0x3d
00000003 40 inc ax
00000004 80C630 add dh,0x30
00000007 B40E mov ah,0xe
00000009 B103 mov cl,0x3
0000000B 66BB01006689 mov ebx,0x89660001
00000011 DA80F900 fiadd dword [bx+si+0xf9]
00000015 7424 jz 0x3b
00000017 EB00 jmp short 0x19
00000019 6683FA00 cmp edx,byte +0x0
0000001D 740F jz 0x2e
0000001F 40 inc ax
00000020 88F0 mov al,dh
00000022 CD10 int 0x10
00000024 B020 mov al,0x20
00000026 CD10 int 0x10
00000028 6683C2FF add edx,byte -0x1
0000002C EBEB jmp short 0x19
0000002E B00A mov al,0xa
00000030 CD10 int 0x10
00000032 80C1FF add cl,0xff
00000035 6683C301 add ebx,byte +0x1
00000039 EBD4 jmp short 0xf
0000003B EBFE jmp short 0x3b
0000003D skipping 0x1C3 bytes
Давайте go через шаг дизассемблированного машинного кода шаг за шагом.
Бит mov sil, CHAR
декодируется как inc ax
(байт префикса REX, 40h), затем mov dh, 3Dh
:
00000000 40 inc ax
00000001 B63D mov dh,0x3d
Затем еще один байт префикса REX и add dh, 30h
:
00000003 40 inc ax
00000004 80C630 add dh,0x30
Теперь dh
равно 6Dh ('m').
Следующие две инструкции представляют собой 8-битные операции без байтов префикса REX, поэтому они интерпретируются так, как вы задумали :
00000007 B40E mov ah,0xe
00000009 B103 mov cl,0x3
Затем вы получите mov bx, 1
, который собран с префиксом O16 (OSIZE в 32- или 64-битном режиме). Вместо этого это интерпретируется как O32, так как мы находимся в 16-битном сегменте кода:
0000000B 66BB01006689 mov ebx,0x89660001
Теперь дизассемблер продолжает неверную инструкцию, так как ваш BB с префиксом O32 равен mov ebx, imm32
вместо вашего предполагаемого mov bx, imm16
.
00000011 DA80F900 fiadd dword [bx+si+0xf9]
Это, по сути, инструкция без операций в этом контексте. Затем мы перейдем к прыжку:
00000015 7424 jz 0x3b
00000017 EB00 jmp short 0x19
Я полагаю, что inc ax
оставит флаги в состоянии Not-Zero (NZ), скорее всего (а fiadd
не изменит его), поэтому ваш jz
здесь не разветвляется.
00000019 6683FA00 cmp edx,byte +0x0
0000001D 740F jz 0x2e
Это сравнение выполняется в целом edx
. Из-за оптимизированной формы с 8-разрядным немедленным расширением знака единственное изменение от намеченного O16 к O32 состоит в том, что будет сравниваться весь регистр edx
. Однако, поскольку задействовано старшее слово edx
, этот l oop может выполняться более 4 гигабит итераций.
0000001F 40 inc ax
00000020 88F0 mov al,dh
Снова регистр sil
вместо этого декодируется как байт префикса REX (inc
), затем доступ dh
. По этой причине бесконечный l oop показывает разные символы: вы инициализируете al
из среднего байта счетчика l oop.
00000022 CD10 int 0x10
00000024 B020 mov al,0x20
00000026 CD10 int 0x10
Здесь никаких сюрпризов, все интерпретируются как и предполагалось .
00000028 6683C2FF add edx,byte -0x1
0000002C EBEB jmp short 0x19
Это добавление делает очень длинным l oop, в зависимости от начального значения в edx
, передаваемого вашей программе из qemu.
0000002E B00A mov al,0xa
00000030 CD10 int 0x10
00000032 80C1FF add cl,0xff
00000035 6683C301 add ebx,byte +0x1
00000039 EBD4 jmp short 0xf
Не так много сюрпризов Вот. Однако ebx
увеличивается вместо bx
.
0000003B EBFE jmp short 0x3b
Эта остановка l oop интерпретируется так, как вы хотите.
l oop возвращается к метка pyramid
интерпретирует этот фрагмент кода следующим образом:
$ ndisasm -b 16 -s 0xF -k 0x3D,$((512 - 0x3D)) pyramid
[...]
0000000F 6689DA mov edx,ebx
00000012 80F900 cmp cl,0x0
00000015 7424 jz 0x3b
[...]
Таким образом, он инициализирует счетчик l oop edx
до полного значения ebx
. Это снова делает очень долго oop. cmp cl, 0
интерпретируется как задумано.
Вот исправленная перезапись вашей программы. Он больше не использует sil
, потому что вы не можете использовать sil
в 16-битном режиме, и он все равно не понадобился. Он не использует bx
как внутреннее значение сброса счетчика l oop, поскольку bx
может использоваться службой прерывания 10h ah = 0Eh. Кроме того, он использует полный cx
в качестве внешнего счетчика l oop, что не является обязательным, но позволяет использовать инструкцию loop
вместо dec cl
\ jnz .loop_outer
.
Кроме того, я исправил два больше ошибок в вашей программе:
За последним символом, построившим пирамиду, на строку следовал завершающий пробел. Я изменил программу, чтобы сначала отображалось пустое, а затем другой символ.
Вы отображали только код символа 10 (0Ah, перевод строки) для переноса строки. Правильным является значение 13 (возврат каретки), а затем 10 (перевод строки) для уровня обслуживания прерывания 10 ч.
Еще одна проблема заключается в том, что вы использовали простой jmp
для остановки самого себя. Это потребляет много процессорного времени, поскольку оно зацикливается навсегда. Вместо этого я использовал последовательность sti
\ hlt
\ jmp
, которая сохраняет время ЦП процесса qemu близким к нулю при остановке.
Вот источник:
; cpu 386 ; no 32-bit registers used or needed here!
cpu 8086
bits 16
org 0x7c00
start:
mov ah, 0Eh ; value to call "display TTY" int 10h service
mov cx, 3 ; outer loop counter
mov di, 1 ; inner loop counter initialisation,
; incremented by each outer loop
mov bx, 7 ; bx initialised to 7 for Int10.0E "page" and "colour".
; Note: Do not use bp register, as it may be overwritten by
; the Int10.0E call.
.loop_outer:
mov dx, di ; reset inner loop counter to di
.loop_inner:
mov al, ' '
int 10h ; display a blank
mov al, 'a'
int 10h ; display the character we want to show
dec dx
jnz .loop_inner ; loop inner loop
mov al, 13
int 10h
mov al, 10
int 10h ; display a line break
inc di ; increment reset value for inner loop counter
loop .loop_outer; loop outer loop
halt:
sti
hlt ; halt the system (without using much CPU time)
jmp halt
times 510-($-$$) db 0
dw 0AA55h
Выполнить следующим образом:
$ nasm p.asm -l p.lst -o p.bin
$ qemu-system-x86_64 -drive file=p.bin,format=raw,index=0,media=disk
Отображает следующий вывод на моем QEMU:
[...]
Booting from Hard Disk...
a
a a
a a a
[cursor here]