3

I'm looking to output an integer using pure assembly. I'm using nasm on a 64-bit linux machine. At the moment I'm looking for a way to output integers to debug a compiler, but I want to use the same code for writing an OS, which is also the reason I don't simply use printf(). After much searching and frustration I have come up with this code

    SECTION .data
var:    db  "      ",10,0

    SECTION .text
global main
global _printc
global _printi

main:
    mov rax, 90
    push    rax
    call    _printi

    xor rbx, rbx
    mov rax, 1
    int 0x80

_printi:
    pushf
    push    rax
    push    rbx
    push    rcx
    push    rdx

    mov rax, [rsp+48]
    mov rcx, 4
.start:
    dec rcx
    xor rdx, rdx
    mov rbx, 10
    div rbx
    add rdx, 48
    mov [var+rcx], dl
    cmp rax, 0
    jne .start

    mov rax, [var]
    push    rax
    call    _printc
    pop rax

    pop rdx
    pop rcx
    pop rbx
    pop rax
    popf
    ret

_printc:
    push    rax
    push    rbx
    push    rcx
    push    rdx

    mov rax, [rsp+40]
    mov [var], rax
    mov rax, 4
    mov rbx, 1
    mov rcx, var
    mov rdx, 4
    int 0x80

    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret

Note that I'll be replacing 0x80 calls with BIOS calls when porting to OS development.

My question is how to optimize, or even prettify, this code further. My first thought would be to replace pushing all the registers individually, but there isn't any 64-bit pusha instruction...

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Sean Kelleher
  • 1,952
  • 1
  • 23
  • 34

1 Answers1

2

Here are some possible changes to the routine:

_printi:
    pushf
    push    rax
    push    rbx
    push    rcx
    push    rdx

    mov rax, [rsp+48]
    mov rcx, 4
    mov rbx, 10 ; --moved outside the loop
.start:
    dec rcx
    xor rdx, rdx
    div rbx
    add rdx, 48
    mov [var+rcx], dl
    cmp rax, 0
    jne .start

    ; mov rax, [var] -- not used
    ; push    rax -- not used
    call    _printc
    ; pop rax -- not used

    pop rdx
    pop rcx
    pop rbx
    pop rax
    popf
    ret

I also noted some limitations in the algorithm. If the number is larger than 9999, the code will continue to put digits outside of the allocated space, overwriting some other data. The routine is not fully reusable, i.e. if you print 123, then 9 it will come out as 129.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • @eZanmoto: Regarding the pushing and poping, consider what the code does. You put a value in `rax` that is neither used by `_printc` not later in the code. The `_printc` routine does itself preserve `rax`, so you wouldn't need to push and pop it even if it was needed later. If `_printc` is only used by `_printi`, you can inline it's code, saving you a call and a set of pushes and pops. – Guffa Nov 07 '10 at 13:30
  • Sorry for my ignorance, but I still don't follow... I'm pushing the data in the variable `var` so that it can be passed as a parameter in the `_printc` routine. I know `_printc` is only used by `_printi` at the moment, but I intend to use it in a library of functions while building my compiler and OS. But you're right, in pertaining to my original question of a more efficient way of outputting an integer it's much better to just use `_printc` inline, thanks for the answer, and sorry for the ambiguity in the question. – Sean Kelleher Nov 07 '10 at 18:19
  • @eZanmoto: I see... you get the first part of the string, passes it as a parameter to `_printc`, which puts it back where it was, overwriting the characters with the same characters... It was just so pointless that I didn't see it. :) – Guffa Nov 07 '10 at 18:58
  • Yeah, sorry... I've never coded in assembly before so I don't know any idioms or the correct way to do anything... Like I've done lots of disassembly, but actually writing it is a different beast... My way of thinking for this is that I'll be including it in other assembly files using the `%include` directive, so instead of having to remember the variable name I figured I can put the value or character I want to output on the stack and call `_printc` or `_printi`, depending on what functionality I want... And again it'll be easy to replace the 0x80 linux call with a BIOS call... – Sean Kelleher Nov 07 '10 at 19:50