Simple Print to Console Algorithm using NASM Assembly Code on Linux*

Published on November 12 , 2013

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 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 {C} {C}  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.




Product and Performance Information


Intel's compilers may or may not optimize to the same degree for non-Intel microprocessors for optimizations that are not unique to Intel microprocessors. These optimizations include SSE2, SSE3, and SSSE3 instruction sets and other optimizations. Intel does not guarantee the availability, functionality, or effectiveness of any optimization on microprocessors not manufactured by Intel. Microprocessor-dependent optimizations in this product are intended for use with Intel microprocessors. Certain optimizations not specific to Intel microarchitecture are reserverd for Intel microprocessors. Please refer to the applicable product User and Reference Guides for more information regarding the specific instruction sets covered by this notice.

Notice revision #20110804