If you have no clue what I am talking about, check out my previous post. I have fixed the flaws, and now it will parse the commands it is handed on the command line, and stick them into an integer. It will now print it to stdout.
This has been an awesome learning experience for me. Especially with regards to how to do recursion, how to debug a pure assembly program with gdb and whatnot.
Here comes the code, as I said before, see my previous thread if you don't know what I am talking about, as well as instructions on how to compile the program.
; File: addnumbers2.asm
; Delta Regeer
;
; 2008-01-27
;
; Function:
; Add numbers together that are provided as arguments to the program in argv[1] and argv[2].
;
; Known limitations:
; This will hopefully be fixed in the next revision. Floating point numbers will not work.
; Any input that is larger than an integer will cause overflows, and thus will not work.
section .data
; Define some strings that are going to be used throughout the program
; This string is to let the user know they failed to provide the proper amount of arguments.
args db "Program addnumbers: ", 0xa, 0x9, "addnumbers <number 1> <number 2>", 0xa, 0x9, "Arguments 1 and 2 are required.", 0xa, 0x9, "Anything that will cause addition to overflow an int (2,147,483,647), will fail! :P", 0xa
largs equ $ - args
; This string contains part of the output that we are going to send to the terminal. The last two
; bytes will be filled automatically by the program, before it is output to stdout.
msg db 'Answer: ', 0
lmsg equ $ - msg
num1 dd 0
num2 dd 0
section .bss
; This is where I am going to store the output of my conversion from an integer to a char
answer resb 64
section .text
global start ; Linker defined entry point. Mac OS X this is start.
global _start ; FreeBSD and others _start.
_start:
start:
push ebp ;
mov ebp, esp ; Set up the stack frame
mov ecx, [ebp + 4] ; Get argc, we check if it set to at least 3
mov edx, ebp ; Put the base pointer into edx, so we can use that in
; our dereferences coming up
add edx, 8 ; Add 8. We want to skip ebp and argc
cmp ecx, 3 ; Check if we have at least 3 arguments to the program.
; At least two arguments are required, and the 3rd one is
; the name of the program
jl exit ; If the value in ecx is less than 3, jump to exit
mov esi, 1 ; Set the index to 1
mov eax, [edx + esi * 4] ; Move the pointer to the character array into eax
push eax ; Push eax onto the stack
push num1 ; Push the pointer to num1 onto the stack
call ctoi ; Call my char to int function
add esp, byte 8 ; Put the stack pointer back to where it was.
inc esi ; Increase the index
mov eax, [edx + esi * 4] ; Move the pointer to the character array into eax
push eax ; Push eax onto the stack
push num2 ; Push the pointer to num2 onto the stack
call ctoi ; Call my char to int function
add esp, byte 8 ; Put the stack pointer back to where it was.
mov eax, [num1] ; Move value stored in num1 into eax
add eax, [num2] ; Add num2 to eax, this will now be stored in eax
push eax ; Push the new calculated number onto the stack
call itoa ; Convert the integer to a character array
push dword lmsg ; Push the length of the string
push msg ; Push the location of the string in memory
push dword 0x1 ; Push the file descriptor to write to
mov eax,4 ; Move the syscall number into eax
push eax ; Push the syscall onto the stack
int 0x80 ; Interrupt 80, go to kernel
add esp, byte 16 ; Clean up the stack
push answer ; Push answer onto the stack
call len ; Get it's length
push edi ; Push the length onto the stack
push answer ; Push the pointer to the character string onto the stack
push dword 0x1 ; Push the file descriptor to write to
mov eax,4 ; Push the syscall number into eax
push eax ; Push the syscall onto the stack
int 0x80 ; Interrupt 80, go to kernel
add esp, byte 16 ; Clean up the stack
jmp done ; Program is done. Jump to done
exit:
; This label is jumped to when we want to exit the program and let the user know how
; to run the program. Like for instance what paramaters to send the program.
; Call sys_write
push dword largs ; Push the length of the string
push dword args ; Push the location of the string in memory
push dword 0x1 ; Push the file descriptor to write to
mov eax,4 ; Move the syscall number into eax
push eax ; Push the syscall onto the stack
int 0x80 ; Interrupt 80, go to kernel
add esp, byte 16 ; Clean up the stack
done:
; This is the label we jump to when we want to exit the program, we set the exit code
; to 0.
; Call sys_exit
push dword 0x0 ; Push the value to return to the operating system
mov eax,1 ; Move the syscall number into eax
push eax ; Push the syscall onto the stack
int 0x80 ; Interrupt 80, go to kernel
; We never return to this function, so no need to clean the stack.
ctoi:
; char to i. We actually convert entire character array's to integers.
;
; We get two paramaters on the stack. The first one we grab is the pointer to the place to store
; the number. The second is the pointer to the character array.
push ebp ; Push the old base pointer onto the stack
mov ebp, esp ; Create a new base pointer
push esi ; Store all the original registers
push eax
push ebx
push ecx
push edx ; Push edx, so that we can overwrite it
sub esp, 4 ; We get another storage space on the stack
mov [esp], dword 10 ; This is the number we are going to multiply by
mov eax, [ebp + 12] ; Move the pointer to the character array into eax
push eax ; Push the pointer to the character array onto the stack
call len ; Call the string length versoin
add esp, byte 4 ; Reclaim the space we lost when we pushed eax onto the stack
mov ebx, [ebp + 8] ; This is where we are going to store the numbers
mov esi, [ebp + 12] ; This is the pointer to the character array
movzx ecx, di ; move with extended zero edi.
mov edi, 0 ; Clean up edi
ctoi_loop:
mov eax, [ebx] ; Move the value stored in ebx into eax
mul dword [esp] ; Move it over a 10s place.
mov [ebx], eax ; Move the new number back into ebx
movzx eax, byte [esi + edi] ; Move the character into eax
movsx eax, al ; We just want the lower part of the character
sub eax, 0x30 ; Subtract 0x30, ASCII 0 so that it is an actual number
add [ebx], eax ; Add the new number to the old number that has been multiplied by 10
inc edi ; Increase the counter
loop ctoi_loop ; Loop into cx is 0
add esp, byte 4
pop edx ; Restore all the registers
pop ecx
pop ebx
pop eax
pop esi
mov esp, ebp ; Make esp the original base pointer again
pop ebp ; Pop the original base pointer into the register
ret ; Return caller
itoa:
; Recursive function. This is going to convert the integer to the character.
push ebp ; Setup a new stack frame
mov ebp, esp
push eax ; Save the registers
push ebx
push ecx
push edx
mov eax, [ebp + 8] ; eax is going to contain the integer
mov ebx, dword 10 ; This is our "stop" value as well as our value to divide with
mov ecx, answer ; Put a pointer to answer into ecx
push ebx ; Push ebx on the field for our "stop" value
itoa_loop:
cmp eax, ebx ; Compare eax, and ebx
jl itoa_unroll ; Jump if eax is less than ebx (which is 10)
xor edx, edx ; Clear edx
div ebx ; Divide by ebx (10)
push edx ; Push the remainder onto the stack
jmp itoa_loop ; Jump back to the top of the loop
itoa_unroll:
add al, 0x30 ; Add 0x30 to the bottom part of eax to make it an ASCII char
mov [ecx], byte al ; Move the ASCII char into the memory references by ecx
inc ecx ; Increment ecx
pop eax ; Pop the next variable from the stack
cmp eax, ebx ; Compare if eax is ebx
jne itoa_unroll ; If they are not equal, we jump back to the unroll loop
; else we are done, and we execute the next few commands
mov [ecx], byte 0xa ; Add a newline character to the end of the character array
inc ecx ; Increment ecx
mov [ecx], byte 0 ; Add a null byte to ecx, so that when we pass it to our
; len function it will properly give us a length
pop edx ; Restore registers
pop ecx
pop ebx
pop eax
mov esp, ebp
pop ebp
ret
len:
; Returns the length of a string. The string has to be null terminated. Otherwise this function
; will fail miserably.
; Upon return. edi will contain the length of the string.
push ebp ; Save the previous stack pointer. We restore it on return
mov ebp, esp ; We setup a new stack frame
push eax ; Save registers we are going to use. edi returns the length of the string
push ecx
mov ecx, [ebp + 8] ; Move the pointer to eax; we want an offset of one, to jump over the return address
mov edi, 0 ; Set the counter to 0. We are going to increment this each loop
len_loop: ; Just a quick label to jump to
movzx eax, byte [ecx + edi] ; Move the character to eax.
movsx eax, al ; Move al to eax. al is part of eax.
inc di ; Increase di.
cmp eax, 0 ; Compare eax to 0.
jnz len_loop ; If it is not zero, we jump back to len_loop and repeat.
dec di ; Remove one from the count
pop ecx ; Restore registers
pop eax
mov esp, ebp ; Set esp back to what ebp used to be.
pop ebp ; Restore the stack frame
ret ; Return to caller