Search code examples
assemblyx86-16fasm

How to update character of string in Assembly?


I have done my research and stumbled upon many solutions to change some characters of the string. I am trying to print a hex code in it's string form. But I have tried all the solutions but it won't compile on "Flat assembler". Following is my code :

mov bx, HELLO
call print_string

mov dx, 0x1fb6
call print_hex

jmp $

print_hex:
pusha
mov cx, HEXi
mov al, byte ptr [cx]
back:

popa
ret

include "print_string.asm"

HELLO: db 'Hello, World!',0
HEXi: db '0x0000',0

times 510 -( $ - $$ ) db 0
dw 0xaa55

On Compilation it just shows invalid expression error.


Solution

  • From the flatassembler board:

    1. FASM whines with "reserved word used as symbol"

    Instructions like MOV AX, [CX] or JMP WORD [AX] cause this error - FASM bug ?

    Your BUG. Only BX, BP, SI and DI are available for indexing in 16-bit code. FASM's report is bad, but it originates from internal design. This is a "problem" of 8086 design and 16-bit code. Using more registers like EAX etc. for addressing is a privilege of 32-bit code on 80386 and later CPU's.

    Not every register 16-bit register can be used as an address register, only the following combinations are allowed:

    • Only displacement: [displacement]
    • Only base register: [BX+displacement]
    • Only base pointer: [BP+displacement]
    • Only index register [SI+displacement] or [DI+displacement]
    • Base and index register: [BX+SI+displacement] or [BX+DI+displacement]
    • Base pointer and index register: [BP+SI+displacement] or [BP+DI+displacement]

    The displacement can be omitted when 0. It can be a variable name here, for example. In this case you could write

    mov al, [HEXi]
    

    instead or use one of the allowed registers instead:

    mov di, HEXi
    mov al, [di]
    

    The register size does not have to be specified here with byte, as it is clearly specified by the target register.


    How can I manipulate the characters of the HEXi string?

    Assuming the value to be output is in dx and should be written as a hexadecimal number in the string HEXi. With

      mov [bx+HEXi], al
    

    a character from the register al can be written to the bx-th position of HEXi, starting from zero. Since the string already starts with 0x and that should not be overwritten, the first two characters are skipped using

      mov [bx+HEXi+2], al
    

    The value HEXi+2 is encoded as the immediate value mentioned above. Now 4 bits should each be converted to a hexadecimal digit, i.e. the characters

    • 0to 9 (character codes 0x30 to 0x39) and
    • A to F (character codes 0x41 to 0x46`). This is done in the following steps:

    First the lower 4 bits of dx are isolated and converted into character codes 0x30 up to 0x3F with

    mov  al, dl    ; Copy the lower 8 bits of dx to al
    and  al, 0x0F  ; Set the upper 4 bits of al to zero
    or   al, 0x30  ; Convert to charcode
    

    The character codes 0x3A to 0x3F must be shifted to 0x41 to 0x46. For this purpose, it is first checked whether the value lies in it, in order to shift it if necessary:

        cmp  al, 0x39
        jbe  noShiftNeeded
        add  al, 7
    noShiftNeeded:
    

    This should happen for all for 4-bit nibbles of the 16-bit value in dx. After each step, dx is shifted to the right by 4 bits so that the previous code can be used again. The final code is then:

    print_hex:
        pusha
    
        mov  bx, 3     ; Start with last character
    hexLoop:
        mov  al, dl    ; Copy the lower 8 bits of dx to al
        and  al, 0x0F  ; Set the upper 4 bits of al to zero
        or   al, 0x30  ; Convert to charcode
    
        cmp  al, 0x39
        jbe  noShiftNeeded
        add  al, 7
    noShiftNeeded:
        mov  [bx+HEXi+2], al  ; Write the character to HEXi
    
        shr   dx, 4    ; To be able to repeat this with the next 4-bit nibble
        sub  bx, 1     ; If this wasn't the forth character, repeat
        jnc  hexLoop
    
        popa
        ret