Reputation: 517
Why doesnt this program print X? Could someone explain step by step what is happening to make it print a spade as output.
mov ah, 0x0e ; Set higher bit of ax to 0e (probably the instruction opcode)
mov al, the_secret ; Set lower bit of ax to bytes of H
int 0x10
the_secret:
db 'X'
jmp $ ;Jump to the current address forever
; Padding
times 510 - ($-$$) db 0
dw 0xaa55
Upvotes: 2
Views: 198
Reputation: 67
... to make it print a spade as output.
... now I can't really help you there, as I don't know the exact Hex->ASCII Value for a ♠️ character... although, I can help you understand the simple steps in how to print a standard (keystroke) ASCII character.
Here's the full NASM example of how to go about printing character(s) to the monitor display; I will break it down into video display specific chunks to help you better understand the process (please take care to note that I've used only REAL MODE assembly instructions (16 Bit) within the example):
[bits 16] ; Declare CPU Instructions as [REAL_MODE]
[org 0x0000] ; Organize Memory to Start @ [0x0000]
; We'll Manually setup Memory Organization Later...
jmp _glEntry ; Relocate to Function:
;========================================================
; This Function Prints an Entire Character Stream until
; NULL Terminated Hex Character (0x00) is Detected...
;========================================================
output16_char_array: ; Legacy BIOS Output CHAR_STREAM Function
;push ax ; Store [AX] Register to [STACK_SPACE]
xor ax, ax ; Zero [AX] Register
mov ah, 0x0E ; Assign [0x0E] to [AX] : (Legacy BIOS Function Code for BIOS Display)
; Continue Code Execution:
.output16_char_array_loop:
lodsb ; Collect Single Byte from [DS:SI] and increment SI by 1 : ([AL] = [DS:SI]; inc [SI])
or al, al ; Check Whether [AL] is [0x00]
jz .output16_char_array_done ; [AL] == [0x00] Proceed to Function:
int 0x10 ; [AL] != [0x00] Continue to Display Hex->ASCII [CHAR]
jmp .output16_char_array_loop ; Relocate [CS] to create an Infinite Loop...
.output16_char_array_done:
;pop ax ; Restore [AX] Register from [STACK_SPACE]
ret ; Return to Calling [CS] Memory Position...
;========================================================
; For Simplicity I've created a Single Character Output
; Method, but in the form of a MACRO
;
; There is really no difference in the method if Output
; other than that this method only prints a single
; Character and doesn't require a NULL Terminated Hex
; Character (0x00) for EndOf(..) CHAR_STREAM...
;========================================================
%macro output16_char_byte 1 ; Define Macro with a single input method...
;push ax ; Store [AX] Register to [STACK_SPACE]
xor ax, ax ; Zero [AX] Register
mov ah, 0x0E ; Assign [0x0E] to [AH] : (Legacy BIOS Function Code for BIOS Display)
mov al, %1 ; Assign [MACRO_INPUT] to [AL] : (Refer to ['%macro output16_char_byte <..>'])
int 0x10 ; Legacy BIOS Interrupt for Teletype Output
xor ax, ax ; Zero [AX] Register
;pop ax ; Restore [AX] Register from [STACK_SPACE]
%endmacro ; End of ['output16_char_byte'] Macro
WELCOME_MSG db "Hello, World!", 0x0D, 0x0A, 0x00
;========================================================
; This Function is the Entry Point for the Global Master
; Boot Record
;========================================================
_glEntry:
cli ; Disable CPU Interrupts
xor ax, ax ; Zero [AX] Register
mov ax, 0x07E0 ; Assign [0x07E0] to [AX] : (0x07E0)
mov ss, ax ; Assign [AX] to [SS] : ([AX]*[0x0010]) : (0x7E00)
mov sp, 0x1000 ; Assign [0x1000] to [SP] : (0x1000) ([0x07E0]:[0x1000])
sti ; Enable CPU Interrupts
; Continue Code Execution:
xor ax, ax ; Zero [AX] Register
mov ax, 0x07C0 ; Assign [0x07C0] to [AX] : (0x07C0)
mov ds, ax ; Assign [AX] to [DS] : ([AX]*[0x0010]) : (0x7C00)
; Continue Code Execution:
mov si, WELCOME_MSG ; Assign [WELCOME_MSG] to [SI] : (0x[....])
call output16_char_array ; Call our Output [CHAR_STREAM] Function
mov si, WELCOME_MSG ; Assign [WELCOME_MSG] to [SI] : (0x[....])
output16_char_byte BYTE [ds:si+0x0008] ; Assign (BYTE) [DS:SI+0x0008] to Macro Input Method...
; we should have the (9th - 1) letter from [WELCOME_MSG] 'o'
; Continue Code Execution on Return:
xor ax, ax ; Zero [AX] Register
int 0x16 ; Call BIOS Await Keystroke Interrupt
xor ax, ax ; Zero [AX] Register
int 0x19 ; Call BIOS Reboot Interrupt
; Continue Code Execution if interrupts fail...
cli ; Disable CPU Interrupts
hlt ; Halt CPU Execution:
times 510 - ( $ - $$ ) db 0x00 ; Pad [BOOTSECTOR] with [0x00] up to 510 Bytes...
dw 0xAA55 ; Assign [MAGIC_MBR] Value...
Alright that is the entirety of how one would go about printing characters to the monitor/display...
Most common/efficient method of printing/displaying character(s) to the display:
;========================================================
; This Function Prints an Entire Character Stream until
; NULL Terminated Hex Character (0x00) is Detected...
;========================================================
output16_char_array: ; Legacy BIOS Output CHAR_STREAM Function
;push ax ; Store [AX] Register to [STACK_SPACE]
xor ax, ax ; Zero [AX] Register
mov ah, 0x0E ; Assign [0x0E] to [AX] : (Legacy BIOS Function Code for BIOS Display)
; Continue Code Execution:
.output16_char_array_loop:
lodsb ; Collect Single Byte from [DS:SI] and increment SI by 1 : ([AL] = [DS:SI]; inc [SI])
or al, al ; Check Whether [AL] is [0x00]
jz .output16_char_array_done ; [AL] == [0x00] Proceed to Function:
int 0x10 ; [AL] != [0x00] Continue to Display Hex->ASCII [CHAR]
jmp .output16_char_array_loop ; Relocate [CS] to create an Infinite Loop...
.output16_char_array_done:
;pop ax ; Restore [AX] Register from [STACK_SPACE]
ret ; Return to Calling [CS] Memory Position...
I would like to note that I've commented push ax
& pop ax
as these instructions are not required within this simple example. Although, (off topic) these instructions just preform a memory stack store (push
) and/or restore (pop
) to the AX
register.
mov ah, 0x0E
this is pretty straight forward... it assigns the value 0x0E
to AH
this is the Teletype Output Function for int 0x10
.
lodsb
this is a more efficient way of preforming such:
mov al, BYTE [SI] ; Assign Single [BYTE] from [SI] to [AL]
inc si ; Increment [SI] by 1 equivalent to [SI]++
or al, al
checks whether [AL]
is equal to 0x00
. This is the NULL
terminating character of a character stream.
jz .output16_char_array_done
if [AL]
is equal to 0x00
then execute code at .output16_char_array_done
... if [AL]
is not equal to 0x00
continue to execute the code...
jmp .output16_char_array_loop
a simple way to loop or iterate a certain function/handle without using the loop
instruction (loop
requires more hardware resources to execute, so tend to avoid this instruction unless it's more efficient in your specific circumstance(s)).
ret
a simple instruction to return to previous (calling) memory address.
int 0x10
this is the BIOS Interrupt Handle for Video Services
Upvotes: 3