NASM - The Netwide Assembler

NASM Forum => Programming with NASM => Topic started by: captaincode on November 28, 2014, 05:16:05 AM

Title: What is the correct way to print a value into the screen?
Post by: captaincode on November 28, 2014, 05:16:05 AM
For example i have this program
Code: [Select]
section .bss
  x resd 1

section .text
  global _start

  ;macro with the linux system call
  %macro print32b 1
      mov eax, 4
      mov ebx, 1
      mov ecx, %1
      mov edx, 4 ;size of 32 bits
      int 80h
  %endmacro

  %define ascii(register)

_start:
  mov eax, 100
  ascii(eax)
  mov [ x ], eax
  [b]print32b x[/b]
 
  mov eax, 1
  mov ebx, 0
  int 80h


Title: Re: What is the correct way to print a value into the screen?
Post by: Frank Kotler on November 29, 2014, 11:32:03 AM
What's this supposed to do?
Code: [Select]
  %define ascii(register)
I guess you want it to print a "1", a "0", and another "0". Since you haven't %defined it "as" anything, I would expect it to do nothing, and your code to print a 'd'. Lemme try it... Yeah.

Maybe what you're looking for is printf()... or maybe itoa(). We can show you how to "just call printf", but it sounds like you want to "do it yourself". The way (one way) to do that is to divide the value repeatedly by ten. Each time, we get the quotient in eax and the remainder in edx. It is the remainders we're interested in. We need to add '0' to each to convert the value to the ascii character representing that value (this is the character '0', not the number 0!). Unfortunately, we get the digits in the "wrong order" - we want to print '1' first, but we get it last. There are different ways to deal with that. A simple way, but not particularly "good" is to push 'em on the stack as we get 'em (when the quotient become zero, we're done) - then pop 'em off and put 'em in our buffer to print (or print 'em one at a time - even worse). Or we could put 'em in the buffer as we get 'em and do a "string reverse" at the end. Or we could start at the "end" of the buffer and work "backwards" towards the beginning. We probably won't make it all the way to the start of the buffer - we could, if we wanted to, pad the rest of the buffer with spaces (or zeros) - this looks nice for printing a column of numbers. Try it different ways if you're looking for exercises.

Besides being slow, the "div" instruction is a little tricky. It has a couple of "hidden" operands, and they depend on the one operand we provide! "div bl" divides ax by bl, quotient in al and remainder in ah. "div bx" divides dx:ax by bx, quotient in ax, remainder in dx. "div ebx" divides edx:eax by ebx, quotient in eax, remainder in edx. This is almost certainly the one we want. Notice that in each case the remainder "trashes" one of the "hidden" input registers! Failing to account for this is probably the most common newbie error of all time. If edx is bigger than ebx (or other register/memory we provide) the quotient won't fit in the register provided. This causes a "divide overfow error" - but DOS reported "divide by zero error" and Linux reports "floating point error". The remainder itself won't cause this, but we're going to add '0'... If we do this and then "div" again we crash and get an unhelpful error message.

Here's a starter...
Code: [Select]
section .bss
  x resd 1
  ; big enough for "100" but a full
  ; 32-bit number can take up to
  ; 10 digits - plus you might want
  ; a terminating zero, perhaps a
  ; '+' or "-" sign...
  ; 16 is a nice round number

section .text
  global _start

  ;macro with the linux system call
  %macro print32b 1
      mov eax, 4 ; sys_write
      mov ebx, 1 ; stdout
      mov ecx, %1 ; buffer
      mov edx, 4 ;size of 32 bits
      ; more than enough for "100"
      ; not enough for "1000000000"
     
      int 80h
  %endmacro

  %define ascii(register)
  ; you could make this a macro
  ; but I don't think you can do
  ; it as a single-line macro

_start:
  mov eax, 100
;  ascii(eax)
;  mov [ x ], eax

  mov ebx, 10 ; to divide by
  xor ecx, ecx ; make digit counter zero
pushloop:
  xor edx, edx
  div ebx
  push edx
  inc ecx
  cmp eax, 0
  jnz pushloop

; we've got 'em, put 'em in the buffer
  mov esi, x
poploop:
  pop edx
  add edx, '0'
  mov [esi], dl
  inc esi
  loop poploop

; throw in a linefeed? just for looks?
  mov [esi], byte 10

  print32b x
 
  mov eax, 1 ; sys_exit
  mov ebx, 0 ; exit code
  int 80h

Have fun!

Best,
Frank