6

I need to make a routine that will convert a memory address into a string of bytes. That string would then be the input for a function that prints null-terminated strings (which I was already able to make). For example, if I have an address 0x1bf9 I need to print the text "1bf9" to the screen. The book hasn't gone into 32 bit mode yet, but it kind of hinted that we would need it for that too. This is what I have so far:

TABLE:
db "0123456789ABCDEF", 0

STRING:
db 0

hex_to_char:
    lea bx, TABLE
    mov ax, dx

    mov ah, al ;make al and ah equal so we can isolate each half of the byte
    shr ah, 4 ;ah now has the high nibble
    and al, 0x0F ;al now has the low nibble
    xlat ;lookup al's contents in our table
    xchg ah, al ;flip around the bytes so now we can get the higher nibble 
    xlat ;look up what we just flipped
    inc STRING
    mov [STRING], ah ;append the new character to a string of bytes
    inc STRING
    mov [STRING], al ;append the new character to the string of bytes

    ret
Hugo
  • 2,186
  • 8
  • 28
  • 44

3 Answers3

7

This attempts to increment a literal label, which isn't correct. Also, your STRING memory location only allocates one byte (char) instead of a larger number to accommodate the size of string you intend.

STRING:
    db 0

    inc STRING   ;THIS WON'T WORK
    mov [STRING], ah ;append the new character to a string of bytes
    inc STRING   ;THIS WON'T WORK
    mov [STRING], al ;append the new character to the string of bytes

A neutral comment: the character table used for xlat does not need to be zero terminated.

Also I'd recommend saving and restoring some registers as good asm programming practice. That way, the calling function doesn't need to worry about registers being changed "behind its back". Ultimately then, you probably want something like this:

TABLE:
    db "0123456789ABCDEF", 0

hex_to_char:
    push bx

    mov   bx, TABLE
    mov   ax, dx

    mov   ah, al            ;make al and ah equal so we can isolate each half of the byte
    shr   ah, 4             ;ah now has the high nibble
    and   al, 0x0F          ;al now has the low nibble
    xlat                    ;lookup al's contents in our table
    xchg  ah, al            ;flip around the bytes so now we can get the higher nibble 
    xlat                    ;look up what we just flipped

    mov   bx, STRING
    xchg  ah, al
    mov   [bx], ax          ;append the new character to the string of bytes

    pop bx
    ret

    section .bss

STRING:
    resb  50                ; reserve 50 bytes for the string

EDIT: some desirable tweaks based upon Peter Cordes' input.

lurker
  • 56,987
  • 9
  • 69
  • 103
  • 1
    or even `mov [bx], ah`; `mov [bx+1], al` – Bodo Thiesen Sep 19 '13 at 07:31
  • @mbratch I tried using your solution, but instead of loading STRING's effective address into bx I loaded it into cx. It's giving me an error on the lines where I stick each byte into [cx] and [cx + 1]. nasm is saying they are invalid effective addresses. – Hugo Sep 20 '13 at 14:33
  • @mbratch okay. So why can't I use `lea` for that? Isn't that what it's for? – Hugo Sep 23 '13 at 17:24
  • @BodoThiesen wouldn't loading the converted characters into [bx] overwrite the table that's already there? – Hugo Sep 23 '13 at 17:25
  • @Hugo, indeed. Maybe you could clarify your prior comment, *It's giving me an error on the lines where I stick each byte into [cx] and [cx + 1]* and state the exact error on which instruction. – lurker Sep 23 '13 at 17:45
  • @mrbratch the lines that say `mov [cx], ah`; and `mov [cx + 1], al`; – Hugo Sep 28 '13 at 00:41
  • @Hugo what happens if you replace those two lines with `xchgb al,ah` followed by `mov [cx],ax`? (If there's an error, please indicate what the error message is.) – lurker Sep 28 '13 at 00:44
  • @mbratch the line `mov [cx], ax` generates an error that says `invalid effective address` (same one that I said before). – Hugo Sep 28 '13 at 15:37
  • @Hugo, sorry my mistake. I'm constantly reminded how non-orthogonal the x86 instruction set is. Only `bx` or an index register such as `si` or `di` can be used for indirect addressing in that manner. I've revamped my answer accordingly. See how that works. – lurker Sep 28 '13 at 16:12
  • @mbratch so the only time I can use `[ ]` to address something is when it's either a label, `bx`, or an index register? And also, do `sp` and `bp` count as index registers? – Hugo Sep 28 '13 at 21:04
  • @Hugo, You can use `[]` on `bp` and `sp` as well. The `p` generally stands for `pointer` (like "stack pointer" and "base pointer", which is how they are used). You can combine them like `[si+bx]`. There are more details here: http://cs.smith.edu/~thiebaut/ArtOfAssembly/CH04/CH04-2.html#HEADING2-54. – lurker Sep 28 '13 at 21:39
  • 1
    If you're going to optimize for code-size with `xlat`, use `mov bx, OFFSET TABLE` to save another byte. (Or without the OFFSET if this is NASM). And `mov [STRING], ax` saves an instruction vs. putting the address in BX if you're going to optimize for xchg + a word store instead of two byte stores – Peter Cordes Mar 29 '20 at 05:38
  • 1
    Also, it's generally fine to let AX be call-clobbered; let function use that as a scratch reg. Preserving BX makes sense, though. – Peter Cordes Mar 29 '20 at 05:39
  • You *can* change your code if you agree it's not the best advice for future readers, especially for simple changes that are possible without having to re-think the whole function to verity it, like removing push/pop ax. – Peter Cordes Mar 29 '20 at 11:45
  • @petercordes I shall do so later today. I'm on my phone right now. Not my favorite editing tool. – lurker Mar 29 '20 at 12:42
0

Please take a look at my answer on this page for to convert a 32 bit value in EAX to 8 hexadecimal ASCII bytes: Printing out a number in assembly language?

Community
  • 1
  • 1
0

You can further optimize this for size if you replace the byte splitting with the undocumented "AAM 10h" (D4 10).

Olorin
  • 123
  • 5
  • 1
    `aam 0x10` is documented. [Intel's manual entry for it](https://www.felixcloutier.com/x86/aam) documents form with an immediate other than 0x0a (10 decima), but incorrectly says assemblers don't recognize it. In fact NASM will assemble `aam 10h` just fine. But it's very slow compared to shift / AND for splitting a byte into two nibbles; it's about as slow as `div`. AAM would optimize for code size at the expense of a lot of speed. (Although using `xchg` and `xlat` is already optimizing for size, but in ways that were fast on actual old CPUs.) – Peter Cordes Nov 10 '20 at 04:23
  • Wasn't it undocumented in the early days? Also I vaguely recall some second-sourced 8086 clones made by NEC that would crash if you did this trick... can't remember where I read that. – puppydrum64 Dec 20 '22 at 12:47