-2

I have to Write a nasm (or other) assembler program for an x86 processor that prints a 32-bit hexadecimal number on the standard output, such as printf("%x\n",123456), and use the write system call to display. I wrote some code but it seems to not work. Can comeone help me?

section .data
    message db '0x',0
    number dq 123456

section .text
    global _start

_start:
    ; Print "0x"
    mov eax, 4
    mov ebx, 1
    mov ecx, message
    mov edx, 2
    int 0x80

    ; Print number in hex
    mov eax, number
    push eax
    call print_hex
    add esp, 4

    ; Exit
    mov eax, 1
    xor ebx, ebx
    int 0x80

; Function to print number in hex
print_hex:
    push ebx
    push edx
    mov ebx, 16
    mov edx, 0
    mov ecx, 0

print_hex_loop:
    xor edx, edx
    div ebx
    add edx, '0'
    cmp edx, '9'
    jg print_hex_digit
    push edx
    inc ecx
    test eax, eax
    jne print_hex_loop

print_hex_done:
    mov eax, 4
    mov ebx, 1
    mov edx, ecx
    int 0x80

print_hex_pop_loop:
    pop edx
    mov [esp], edx
    inc esp
    dec ecx
    jne print_hex_pop_loop
    pop edx
    pop ebx
    ret

print_hex_digit:
    add edx, 'A' - '9' - 1
    jmp print_hex_done

I am new to assembler so I don't have many ideas how to get this to work properly

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 2
    How does it not work? Your stack usage is confusing and the `inc esp` is just wrong. By the way, do not use division for powers of two. Computers work in binary, you can just use shifting and masking. As always, use a debugger to single step the code and see where it goes wrong. – Jester Jan 16 '23 at 22:05
  • [How to convert a binary integer number to a hex string?](https://stackoverflow.com/q/53823756) has working asm for uint32_t to hex. – Peter Cordes Jan 17 '23 at 03:50
  • Using `inc esp` is just asking for trouble. The CPU assumes that the stack is always aligned to a multiple of four. If you really want to adjust `esp` you should either always add/subtract a multiple of 4, or use push/pop. – puppydrum64 Jan 18 '23 at 12:14

1 Answers1

0

I assume the function takes an unsigned 32 bit number like below

void printhex( uint32_t number );

The idea here is to reserve the maximum length for a 32 bit integer (16 characters plus the null char at the end) and then start filling the hex number backwards.

Once the number becomes zero, we print and return.

/************ print 32-bit hexadecimal number ***********/
.globl  printhex
printhex:
        // On entry EDI contains number to be printed
        // We will print from the back to the front
        subq    $24, %rsp      // Reserve stack (multiple of 8)
        movb    $0, 18(%rsp)   // Set a null \0 at the end
        leaq    18(%rsp), %rax // ptr = pointer to that null
loop:
        movl    %edi, %esi     // Save number to ESI
        decq    %rax           // Decrement pointer
        movl    %edi, %edx     // Store number into EDX
        shrl    $4, %edi       // number = number / 16
        andl    $15, %edx      // digit = number % 16
        leal    87(%rdx), %ecx // hexdigit = digit + ('a'-10)
        cmpl    $9, %edx      // is digit > 10?
        ja      greater10
        leal    48(%rdx), %ecx // hexdigit = digit + '0'
greater10:
        movb    %cl, (%rax)    // *ptr = hexdigit
        cmpl    $15, %esi      // (saved) number > 15?
        ja      loop           // yes, loop back
                               // no, print and finish
        movb    $120,-1(%rax)  // store '0x'
        movb    $48,-2(%rax)
        leaq    -2(%rax), %rdi // 1st arg to puts = ptr
        call    puts           // print
        addq    $24, %rsp      // Restore stack
        ret                    // Return

The caller can be

.globl _start
_start:
        movl    $0x12abcdef, %edi  // 4660 = 0x1234
        call    printhex
        movq    $60, %rax    // Finish with syscall
        movl    $0,%edi
        syscall

Running it produces

$ gcc printhex.S -o printhex -nostartfiles
$ ./printhex
0x12abcdef
Something Something
  • 3,999
  • 1
  • 6
  • 21
  • The question is using 32-bit code in NASM syntax. This is AT&T syntax for 64-bit mode. Also, it's not a good idea to mix stdio calls with a raw `_exit` system call; if you redirect stdout to a file so the stream is full-buffered, nothing will flush it after `puts`. – Peter Cordes Jan 17 '23 at 07:07
  • Also, this is buggy: for EDX == 10, it prints `10 + '0'` = `':'` (off-by-one in the compare). For EDX>10, it prints `EDX + 'a'` instead of `EDX + 'a'-10`. Testing with `mov $0x789abc, %edi`, it prints `0x789:lm` (lower-case L is the 12th letter of the alphabet, index 11 = 0xb.) Also, you can just write constants as characters or hex, instead of using decimal and then just describing it in a comment. Like `lea 'a'(%rdx), %ecx` or `mov $'x', -1(%rax)`. – Peter Cordes Jan 17 '23 at 07:14
  • But for the `0x`, you actually should just `movw $0x7830, -2(%rax)` to store the `'0x'` prefix; GAS doesn't support multi-character integer literals. NASM is better; `mov word [rdi], '0x'` Just Works. Also, you don't need to make a copy in ESI to test if it was >15 before the shift; you can simply `test %edi,%edi` / `jnz` to keep looping while non-zero. – Peter Cordes Jan 17 '23 at 07:14
  • As I linked in the comments on the question, [How to convert a binary integer number to a hex string?](https://stackoverflow.com/q/53823756) has working u32 -> ASCII hex code, by coincidence written for 32-bit in NASM syntax. [How do I print an integer in Assembly Level Programming without printf from the c library? (itoa, integer to decimal ASCII string)](https://stackoverflow.com/a/46301894) shows using a tmp buffer on the stack for a `write` system call in 64-bit code; notice that you can start with a `push 10` to store a newline, then store digits backward like you're doing. – Peter Cordes Jan 17 '23 at 07:16
  • @PeterCordes Hmm no it is explicitly said any assembly. I fixed the bug. The others are just optimization tricks. Thanks!!! – Something Something Jan 17 '23 at 07:36
  • Oh, fair enough, they do say they're open to any asm syntax, despite having tagged it NASM. They might still be implying 32-bit mode, though, although "x86" does include "x86-64" unless we're talking about Windows. – Peter Cordes Jan 17 '23 at 07:38
  • @PeterCordes Thanks for the bug report!!! I really missed that edge case! – Something Something Jan 17 '23 at 07:39
  • 2
    Don't forget to update the comments, too. If you'd written `lea 'a'-10(%rdx), %ecx`, there wouldn't be a separate comment that needed fixing in the first place. IDK why you use decimal for all the immediates in your program; many of them can be written much more clearly in other ways. Especially the one in `main`; `movl $313249263, %edi` is a really obscure way to write `$0x12abcdef`. Just because GCC compiler output always uses decimal doesn't make it better for hand-written asm for humans to read. – Peter Cordes Jan 17 '23 at 07:42
  • @PeterCordes I am not an expert in writing assembly like you seem to be. I usually do it at the level of `asm()` or Intel intrinsics. That is why on occasion I get this small examples as an exercise. – Something Something Jan 17 '23 at 07:48