Simple Print to Console Algorithm using NASM Assembly Code on Linux

When searching for a good, clean working example other than perhaps writing out a "Hello World" string to the console in Netwide Assembler (NASM) for standard x86 architecture... I've found only a little.  There are only a small number of places one can go for examples.  I've found the nasm.us downloadable pdf isn't the quickest or easiest way to begin learning Netwide Assembly.  Stackoverflow does offer some enigmatic propositions to ponder, and my print-to-console algorithm that I'm sharing with you today is loosely based upon a mishmash of some things you may find there.  It's obvious that when assembly language is required or preferred in certain scenarios you won't discover much out there that's quick or easy.  For example, here, on the blog there isn't a set available for syntax highlighting of .asm code.  I think I'll try wrapping it in a .cpp bulletin board set of markup brackets and be okay with it.  Please note that this code is for NASM and ought to work on Ubuntu and other Linux platforms that support Netwide Assembler.  When using NASM you'll want to use "elf" and "stabs" in the command line to assemble your code, then use "ld" to link everything together.  I've commented a proper command line example at the top:

;(print2console_lanceregala.asm)
 ;nasm -f elf -g -F stabs print2console_lanceregala.asm
 ;ld -o p2c print2console_lanceregala.o -melf_i386
 ;./p2c
 
;initialized data
    section .data
          nln: dd 0xA
          nln_len: equ $-nln
          val: dd 48 ;remember that dd is a 32 bit double word which is the size of the register
          val_len: equ $-val ;if you use db or dw, keep in mind what can be done with the extended bits
          spacer: dd " "
;uninitialized data
    section .bss
          valueToPrint: resd 1 ;likewise, 1 resd = 4 resb (Dword = 4 Bytes)
  
    section .text
          global _start
_start:
    nop                             ;necessary for ld linking 
 call _newline
    call _print_string         ;print this value in ASCII
  call _newline
  call _print_decimal ;if it's really a decimal, this is the actual, readable value converted to ASCII


 call _newline
    jmp _norm_exit           ;not expecting to return



_print_string:                       ;when you want to print the ASCII [val] it's easy
    mov eax, 4                       ;syswrite
    mov ebx, 1                       ;stdout
    mov ecx, val                     ;whatever value 'val' points to in [ecx] will print
    mov edx, val_len               ;...
    int 0x80                           ;invoke kernel to perform instruction
    jmp _return
  
_print_decimal:                       ;converting that pesky ASCII to it's decimal form
    push '$'                             ;throw a $ sign on my stack for no reason other than to designate a stack base
    mov eax, [val]                    ;[val] to decimal
.conversion:
    mov [val], eax
    xor edx, edx                       ;zero out edx (...can be accomplished in many different ways)
    mov ecx, 10                       ;mod out a base 10 place value 
    idiv ecx                             ;signed divide (you can do unsigned division too) edx:eax by 10 
                                                        ;...result in eax, remainder in edx
    add edx, 0x30                               ;convert the remainder to ascii, ('0' = 0x30)
                                                        ;(extra steps, commented out for hex conversion to print A-F)
    ;cmp edx, 0x39                             ;'9' comparison because numerical digit range is 0-9
    ;jng .hexrange  ;
    ;add edx, 0x07                              ;adding 7 hex to create an ascii
.hexrange:
    push edx ;push REMAINDER on stack (in e.g., it's 0)
    mov [val], eax ;copy the quotient into [val]
    cmp eax, 0
    jnz .conversion ;break from loop when eax == 0 
.printpostfix:
    pop eax
    mov [valueToPrint], eax ;store contents of 'eax' in [valueToPrint] 
    cmp eax, '$'
    je _return
    mov eax, 4 ;syswrite
    mov ebx, 1 ;stdout
    mov ecx, valueToPrint ;whatever value exists in [ecx] will print
    mov edx, 1 ;print only a single byte's worth of data
    int 0x80 ;invoke kernel to perform instruction
    jmp .printpostfix



_spacer:
    mov eax, 4
    mov ebx, 1
    mov ecx, spacer
    mov edx, 1
    int 80h
    jmp _return

 _newline:
    mov eax, 4
    mov ebx, 1
    mov ecx, nln
    mov edx, 1
    int 80h
    jmp _return

 _return:
   ret

_norm_exit:
    mov eax, 1 ;initiate 'exit' syscall
    mov ebx, 0 ;exit with error code 0
    int 0x80 ;invoke kernel
    nop
    nop


 ; Lance
 ; November 5th, 2013

By modifying this example, you could covert to any base and print the ASCII equivalent to the console.  In essence, to write good assembly you just have to know "what data type" the computer is processing at any given moment as you step through the program and also keep in mind "the size of the data" with which you're working -- i.e., the number of place values that you are modifying at any given moment, whether it's a byte or 2 bytes, etc. My structuring of the abstract problem space while coding in assembly usually involves placing my declarations at the top, followed by my most abstracted list of calls similar to a main() style procedural method next, and then finishing up with a defined implementation of those calls in a series of _labels and nested .labels for NASM to identify in its first of always only two passes through the assembly code. I use the stackframe to produce a kind of "postfix" readable format from where the little-endian nature of the register created "prefix" style backwards output. I also have found that it works well in my mind to jump to a '_return' label at the end for the sake of pattern and because I can simply call the '_return' label, and similarly so with the '_spacer' and '_newline' labels.

I hope someone finds this blog as helpful.  Although I don't expect to be blogging to any great extent on assembly code in the future, my study of the x86 assembly language has been very enlightening.  And if you're looking for a good NASM example, there have you.

My name's Lance Regala, and I'm a student programmer and developer currently residing in Sacramento, CA.  Thanks to Paul Steinberg, Student Community manager of the Intel Developer Zone,  for the opportunity to document my learning through blogging here on the official Intel website.

 

For more complete information about compiler optimizations, see our Optimization Notice.