NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: XxWh1t3gh0stxX on November 13, 2012, 01:09:03 AM
-
Hi, I am having trouble when I read in an integer from the keyboard and I try to display it to the screen back as the same integer. It pops out as an ASCII character. Am I doing this wrong or is there a way to read it as an integer and not a character, or do I need to convert it back? Thanks
; Compiling this code for 32-bit use:
; nasm -f elf file.asm
; gcc -m32 -o file file.o
;
;~.~. 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 MAX_NUMBER 5000
SECTION .data
format: db "The number is %d." , 10
SECTION .bss
input: resd MAX_NUMBER
SECTION .text
extern printf
global main
main:
nop
GetInput:
mov EAX, SYS_READ
mov EBX, STDIN
mov ECX, input
int 80H
mov DWORD[input + EAX - 1], 0
Calculate:
mov EAX, DWORD[input]
Display:
push EAX
push format
call printf
add ESP, 8
ret
-
Hopefully my comments will point you in the right direction. Just search for <~~
; Compiling this code for 32-bit use:
; nasm -f elf file.asm
; gcc -m32 -o file file.o
;
;~.~. 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 MAX_NUMBER 5000
SECTION .data
format: db "The number is %d." , 10
SECTION .bss
input: resd MAX_NUMBER
SECTION .text
extern printf
extern atoi ; <~~ converts string to int32.
global main
main:
nop
GetInput:
mov EAX, SYS_READ
mov EBX, STDIN
mov ECX, input
mov EDX, MAX_NUMBER ; <~~ You must specify the length of the buffer.
int 80H
mov DWORD[input + EAX - 1], 0
Calculate:
;; <~~ atoi is a C function used in converting strings to integers.
push dword input
call atoi
add ESP, 4
Display:
push EAX
push format
call printf
add ESP, 8
ret
-
What if I choose to not use C calls would this end up being more difficult?
-
Sure. But keep in mind, you are already using C. printf and the setup code for main (__libc_start_main). Since you are already using these, there is absolutely no overhead for using atoi(). In fact, by using _more_ of the CRT, which is already loaded, you can make your code EASIER.
; Compiling this code for 32-bit use:
; nasm -f elf file.asm
; gcc -m32 -o file file.o
;
;~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.
SECTION .data
output_format: db "The number is "
input_format: db "%d" ; <~~ this is a bit of memory trickery, we reuse this part
end_of_format: db 0, 0 ; <~~ and change the first byte to a newline before output.
;~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.
SECTION .bss
input: resd 1 ; <~~ scanf handles conversion for us
;~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.
SECTION .text
extern printf
extern scanf
global main
main:
nop
GetInput:
;; Read input with scanf() function.
push dword input
push dword input_format
call scanf
add ESP, 8
Calculate:
;; No need to calculate anything, scanf() handles input conversion.
Display:
;; Display results using printf().
mov byte [end_of_format], 10 ; <~~ add a newline for printf()
push dword [input] ; <~~ here we pass the contents of our input variable.
push output_format
call printf
add ESP, 8
ret
-
Sure you can do it without the bloated C calls! Whether it's more "difficult" or more "fun" is a matter of opinion... (no, there is no way to read an integer from the keyboard)
; 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: " ; not really used
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
;--------------------
;--------------------
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
; 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
;-------------------
Bryant's version, stripped, 2712 bytes. My version, stripped, 600 bytes. Tell me again about "overhead"?
Best,
Frank
-
Bryant's version, stripped, 2712 bytes. My version, stripped, 600 bytes. Tell me again about "overhead"?
I have to throw a red flag here. This is not an apples-to-apples comparison. For simple ascii integer input/output no doubt Frank's modified version is smaller. However, if/when the requirements expand to include floating point values then the use of scanf/printf is preferred. That is the main difference in overhead - full real number support vs. integer only. In addition, I believe that the C library init code could also be eliminated in this case.
-
True, it's more of a cherry vs watermelon comparison. True, scanf/printf includes much functionallity that we don't need or use. True, my homemade atof/ftoa (which isn't very good!) is larger (but not as large as hooking in the C calls) - 1500 bytes in a simple demo. True, we can do it without the "startup code" (init, CRT, whatever you call it)... to my continued astonishment, we don't need that to use printf/scanf/etc. I can try a version like that, if anyone cares... True, the C library is in memory anyway - might as well use it. But "absolutely no overhead" is a stretch!
XxWh1t3gh0stxX asked if we can do it without the C calls. Yeah, we can.
Best,
Frank
-
Frank,
Of course doing the code in pure assembly is more optimal. In your example, you didn't just switch to system calls, you took away the C library which he was already using completely. My mention of "no overhead" was that he was already using __libc_start_main() and printf() which require the CRT, which means using other features of the CRT is not going to create any overhead for the application in question.
For examples sake, lets take and change your _start declaration back to main and build how he compiled it earlier:
; Compiling this code for 32-bit use:
; nasm -f elf file.asm
; gcc -m32 -o file file.o
; strip ./file
;
;~.~. 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: " ; not really used
SECTION .bss
buffer resb MAX_NUMBER
number resd 1
SECTION .text
global main
main:
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
;--------------------
;--------------------
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
; 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
;-------------------
-rwxr-xr-x 1 bkeller bkeller 3012 Nov 13 20:08 file
My earlier version when built using the same commands (including the strip) is:
-rwxr-xr-x 1 bkeller bkeller 2928 Nov 13 20:12 file
There is no overhead because he has already incurred the primary penalty for using the start-up routine.
-
Thanks guys for all the help I solved it using scanf().
-
No problem XxWh1t3gh0stxX. And Frank is right, if you decide to forgo the CRT itself completely (using _start and making use of the system calls) then the code will be dramatically lighter.
-
What if I choose to not use C calls would this end up being more difficult?
So, to answer your question, doing it yourself is initially "more difficult". There are many design criteria and implementation details you need to factor in. Making use of code already written by professionals is A Good ThingTM.
However, as an assembly language programmer how else do you plan on learning without doing? Practice makes perfect, and writing your own atoi() or any other function for that matter is a great way to experiment, fail, learn from your failures, then succeed.