Search code examples
assemblyx86nasmbootloaderbios

Why doesnt this print X? Assembly


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

Solution

  • ... 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... here's photo evidence of the true output:

    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