Author Topic: Linux program to read two integers and add them together.  (Read 19734 times)

Offline Teol

  • Jr. Member
  • *
  • Posts: 12
Linux program to read two integers and add them together.
« on: September 06, 2016, 07:37:58 PM »
Hi,
I am writing a NASM program in linux to read two integer then add them and print to terminal however my code is not functional. It prints out gipperish.
Below is the code. Help to get this to work is appreciated. I am trying to learn!
Code: [Select]
; 1. Assemble: nasm -f elf64 SumIntegers.asm
; 2. Link: ld -s -o SumIntegers SumIntegers.o
; 3. Run: ./SumIntegers.o
bits 64
section .data
requestText: db 'Please enter integer!', 10 ;'text plus linefeed character'
requestTextLen: equ $-requestText ;set the length of requestText.

section .bss
inputSum resb 4

section .text
global _start

_start:
call askIntegerAndRead
mov esi, [inputSum] ;Stores the first given value to esi.
call askIntegerAndRead
add esi, dword  [inputSum] ;Calculate the inputed sum.
mov [inputSum], esi

mov ecx, inputSum ;Set the inputSum address for write.
mov eax, 4 ;The system call for write.
int 80h ;"Call" for write.

call exit


;Asks for integer and reads from standard input.
askIntegerAndRead:
mov eax, 4 ;The system call number for write (sys_write)
mov ebx, 1 ;File descriptor 1 -standard output
mov ecx, requestText ;Copy the offset
mov edx, requestTextLen ;Copy the length to edx, is constant since defined equ so no need for []
int 80h ;Call the kernel by initiating the interrupt
mov eax, 3 ;The system call number for read (sys_read)
mov ecx, inputSum ;ecx should contain the variables address for read value, here we set it
int 80h
ret

;Calls for exit for this process.
exit:
mov eax, 1 ;System call number for exit (sys_exit)
mov ebx, 0 ;Copy the return code for exit (0=no error)
int 80h ;Call the kernel again :-)


Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Linux program to read two integers and add them together.
« Reply #1 on: September 06, 2016, 09:42:42 PM »
Hi Teol,

I am not familiar with 64-bit code. However, I may be able to point out a couple of issues...
Code: [Select]
; 1. Assemble: nasm -f elf64 SumIntegers.asm
; 2. Link: ld -s -o SumIntegers SumIntegers.o
; 3. Run: ./SumIntegers.o
Your comment says you're trying to run the linkable object file. I doubt if that will work at all... so I doubt if that's what you're really doing.

Then you go on to use the 32-bit parameters and int 80h interface with the kernel. This will "probably" work - I think it depends on how your 64-bit kernel is compiled. You may wish to assemble/link this as a 32-bit program or use the 64-bit system call numbers, parameters, and "syscall" to get a real 64-bit program. There's a big difference!

But the fundamental problem is that you can't read an integer from the keyboard - only text - and you can't print an integer on the screen - only text. What you need to do is convert from text representing a number to the number - twice - add 'em together, and then convert back to text representing the sum and print that. Considering that this is about the most frequently asked question of all time, it isn't too easy to find a good example of this!

One common way is to call the C library functions "scanf" and "printf". There are libraries written in assembly language that would include these functions. Or you could "hand code" functions on your own to do it. In my humble opinion, you learn the most by the latter method... but the C library is better tested and probably better optimized than your routine or mine will ever be.

This may give you an idea what you're up against:
https://forum.nasm.us/index.php?topic=1514.msg6228#msg6228

I may be able to help you with 32-bit code, but not 64. The principle is about the same. Where do you want to go from here?

Best,
Frank


Offline Teol

  • Jr. Member
  • *
  • Posts: 12
Re: Linux program to read two integers and add them together.
« Reply #2 on: September 07, 2016, 08:42:44 PM »
Ok, i changed the program to 32 bits. Now i link with ld -m elf_i386 SumIntegers.o -o SumIntegers
Was a bit silly for me to use 64 bits and not use RAX,RCX etc, the code was coded still like 32 bit.
I was thinking that i dont want to use any libraries so i must create an integer from the character input. As far as i know i must then know the encoding and generate the correct bits to some register or variable to calculate them together with add. I tried code to be able to calculate simple one characters together if they dont sum up above 10. I m expecting UTF-8 to be the encoding here.
Code: [Select]

; 1. Assemble: nasm -f elf32 SumIntegers.asm
; 2. Link: ld -m elf_i386 SumIntegers.o -o SumIntegers
; 3. Run: ./SumIntegers
bits 32
section .data
requestText: db 'Please enter integer!', 10 ;'text plus linefeed character'
requestTextLen: equ $-requestText ;set the length of requestText.

section .bss
inputSum resb 4

section .text
global _start

_start:
call askIntegerAndRead
mov al, byte [inputSum] ;Stores the first given value to esi.
sub al, 48
call askIntegerAndRead
mov dl, byte [inputSum]
sub dl, 48
add al, dl ;Calculate the inputed sum.
add eax, 48
mov [inputSum], eax

mov ecx, inputSum ;Set the inputSum address for write.
mov eax, 4 ;The system call for write.
int 80h ;"Call" for write.

call exit


;Asks for integer and reads from standard input.
askIntegerAndRead:
mov eax, 4 ;The system call number for write (sys_write)
mov ebx, 1 ;File descriptor 1 -standard output
mov ecx, requestText ;Copy the offset
mov edx, requestTextLen ;Copy the length to edx, is constant since defined equ so no need for []
int 80h ;Call the kernel by initiating the interrupt
mov eax, 3 ;The system call number for read (sys_read)
mov ecx, inputSum ;ecx should contain the variables address for read value, here we set it
int 80h
ret

;Calls for exit for this process.
exit:
mov eax, 1 ;System call number for exit (sys_exit)
mov ebx, 0 ;Copy the return code for exit (0=no error)
int 80h ;Call the kernel again :-)


For 5 + 5 it works, but it fails to many. Can you point me a direction here?

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Linux program to read two integers and add them together.
« Reply #3 on: September 08, 2016, 03:05:39 AM »
Hi Teol,

I'm not in the mood right now to discus this at length. Maybe later - don't hesitate to ask questions, if any.

Code: [Select]
; Compiling this code for 32-bit use:
;    nasm -f elf file.asm
;    ld -melf_i386 -o file file.o
;

global _start

;~.~. Definitions for readability: ~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.
        %define  SYS_EXIT   1
        %define  SYS_READ   3
        %define  SYS_WRITE  4
        %define  STDIN      0
        %define  STDOUT     1
        %define  STDERR     2
%define LF 10
        %define  MAX_NUMBER   16
SECTION .data
    format: db "The number is: "

SECTION .bss
    buffer resb MAX_NUMBER
    number resd 1

SECTION .text
_start:
    nop
GetInput:
    mov EAX, SYS_READ
    mov EBX, STDIN
    mov ECX, buffer
    mov edx, MAX_NUMBER
    int 80H
    cmp byte [ecx + eax - 1], LF
    jz Calculate

; pesky user has tried to overflow us!
; flush the buffer!
    sub esp, 4 ; temporary "buffer"
flush:
    mov eax, SYS_READ
    ; ebx still okay
    mov ecx, esp ; buffer is on the stack
    mov edx, 1
    int 80h
    cmp byte [ecx], LF
    jnz flush
    add esp, 4 ; "free" our "buffer"

Calculate:
    push buffer
    call atoi
    add esp, 4
    mov [number], eax ; not really used...

    add eax, 1 ; just to do something

    mov esi, buffer
    mov ecx, MAX_NUMBER
    call binasc
    mov byte [esi + ecx], LF
    inc ecx
   
Display:
    mov edx, ecx
    mov ecx, esi
    mov ebx, STDOUT
    mov eax, SYS_WRITE
    int 80h

exit:
    mov eax, SYS_EXIT
    xor ebx, ebx
    int 80h
;--------------------

;--------------------
; converts text to number
; expects pointer to string on stack
; returns number in eax

atoi:
    push ebx
   
    mov edx, [esp + 8]  ; pointer to string
    xor ebx, ebx ; assume not negative
   
    cmp byte [edx], '-'
    jnz notneg
    inc ebx ; indicate negative
    inc edx ; move past the '-'
notneg:

    xor eax, eax        ; clear "result"
.top:
    movzx ecx, byte [edx]
    inc edx
    cmp ecx, byte '0'
    jb .done
    cmp ecx, byte '9'
    ja .done
   
    ; this "trick" from Herbert Kleebauer
    ; we have a valid character - multiply
    ; result-so-far by 10, subtract '0'
    ; from the character to convert it to
    ; a number, and add it to result.
   
    lea eax, [eax + eax * 4]
    lea eax, [eax * 2 + ecx - '0']

    jmp short .top
.done:
    test ebx, ebx
    jz notminus
    neg eax
notminus:
    pop ebx
    ret
;------------------------

;--------------------
; from Chuck Crayne - RIP, Chuck.
;convert binary to ascii
;call with eax = signed binary number
; esi = address of output string
; ecx = length of output string
;returns esi = 1st printed digit
; ecx = no of digits printed (includes sign if any)
; other registers preserved
binasc: push edx
push ebx
push edi
push eax
mov edi,esi ;save start of string
ba1: mov byte [esi],' ' ;fill string with blanks
inc esi
loop ba1
mov ebx,10 ;initialize divisor
or eax,eax ;value negative?
jns ba2 ;no problem
neg eax ;make it positive
ba2: xor edx,edx ;clear high part of dividend
div ebx ;divide by 10
add dl,'0' ;convert to ascii digit
dec esi ;step backwards through buffer
mov [esi],dl ;store digit
inc ecx
cmp esi,edi ;out of space
jz ba4 ;yes - quit
or eax,eax ;all digits printed?
jnz ba2 ;no - keep trucking
pop eax ;get original value
or eax,eax ;negative?
jns ba3 ;no - quit
dec esi ;place for sign
mov byte [esi],'-'
inc ecx ;add to char count
ba3: pop edi
pop ebx
pop edx
ret
ba4: pop eax
jmp ba3
;-------------------

This expects ASCII input (and output), but should be the same as UTF-8 in this range.

Your 4 byte buffer may be a little too small. A 32-bit number can take 10 digits - I'd add one for a possible zero terminator - or linefeed like sys_read will give you. Possible '+' or '-'... 16 is a nice round number. You should be able to incorporate this into your "askIntegerAndRead" with minor changes.

I'll try to get back to this...

Later,
Frank


Offline Teol

  • Jr. Member
  • *
  • Posts: 12
Re: Linux program to read two integers and add them together.
« Reply #4 on: September 12, 2016, 06:07:49 PM »
I was not at home during the weekend so this project was in shelves meanwhile. Now getting again back to this.
Why must the result be multiplied by 10 when converting the ascii to integer? Can i compile two different files into one? For example i was thinking if i could put the code for converting the ascii to integer into another file for reusablity later on.
« Last Edit: September 12, 2016, 06:15:28 PM by Teol »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Linux program to read two integers and add them together.
« Reply #5 on: September 12, 2016, 08:27:15 PM »
In a multi-digit number - a base 10 (decimal) number - each "place" represents a power of 10. If the pesky user types "12345" we can figure out the length and multiply the 1 by 10000, the 2 by 1000. the 3 by 100, the 4 by 10, and the 5 by 1 (do nothing), and add 'em all together. The way I showed you, we don't need to know in advance how many digits we've got. (the "trick" of using lea is just a "trick")

Sure you can link two (or more) files together. Declare each routine "global" in the file where it exists and "extern" in the file where it's used, and put the object files on the command line to ld. If you put each routine into a separate object file and combine them with ar (I forget the command line switches) only the routines which are actually used will be linked into the executable. This is a "static" library. A shared object or dynamic library has one copy of the routines  in memory, and they can be used by multiple files - like the C library. We probably don't have multiple files that would use an asm-written  shared object... but we could... Take small steps.

Best,
Frank


Offline Teol

  • Jr. Member
  • *
  • Posts: 12
Re: Linux program to read two integers and add them together.
« Reply #6 on: September 22, 2016, 04:25:47 PM »
How can i add the linefeed character to my inputSum variable after the calculations? For example the requestText is defined like this:
requestText: db 'Please enter integer!', 10 ... Is there an easy way to add the linefeed 10 to the variable?

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Linux program to read two integers and add them together.
« Reply #7 on: September 22, 2016, 07:09:08 PM »
Hi Teol,

I see I didn't get back to you with a better example. Sorry.

If you're going to define your variable with "resb" - which is usually good - there really isn't an easy way to put a linefeed at the end of it. You could use "times NUM_DIGITS db ' ' " and a "db 10" (or LF) after it. If you've got something you can reuse, you may not "always" want a linefeed right after the number. In that case you might be better off with a separate "print_NL" routine.

In that half-baked example I posted, I make sure there's room in the buffer for more than the number of digits in a dword, and stuff the linefeed in at the end. This "binasc" routine returns multiple values, which is handy 'cause you can get right or left justified text out of it, but isn't compatible with any "calling convention". Okay for assembly language, but it doesn't mix well with other languages. Probably not ideal.

It is the nature of a "integer to ascii" routine that we get the digits in the "wrong" order. We can start at the "end" of the buffer and work leftwards - which may not go all the way to the "start" of the buffer. In that case we can put the linefeed "first" (if we "always" want it). Or we can do a "string reverse" of some sort (and perhaps put the linefeed last). To print it, we need to know where the digits start and how many of 'em. C handles this with a zero-terminated string - we can return multiple values. The most flexible way to deal with the linefeed is probably a separate routine.

... that's a lot of babble that may not really answer the question...

Best,
Frank