Reputation: 127
i am a beginner @coding and this is my first question here on stack overflow, although some of your great answers brought me some way along already..
While trying this assembler-tutorial I am facing a segmentation fault after running the program. I tried to comment every line out and noticed that the programm crashes when I "call printString" on Line 30.
When I try to debug with gdb (heck, i don't really know what i'm doing there..) i get an error inside the function "iterateChar" in my lenString call. (inside from my functions file - baseOperators.asm - line 50)
I suspect, that somehow i messed up information in the eax register, but i don't know why, what has happened and how to fix this. My code looks very similar to the one from tutorial 16 on asmtutor.com - i c&p'ed that, and that worked - for whatever reason. Please help.
(i'm compiling with "$ nasm -f elf assemblerTutorial.asm" + "$ ld -m elf_i386 assemblerTutorial.o -o assemblerTutorial)
;------------------------------------------
; my Assembler learning Environment
;%include "calculate.asm"
%include "baseOperators.asm"
%include "print.asm"
SECTION .text
global _start
_start:
pop ecx
mov edx, 0
argumentsLoop:
cmp ecx, 0h
jz argumentsEnd
pop eax
call atoi
add edx, eax
dec ecx
jmp argumentsLoop
argumentsEnd:
mov eax, edx
call printString
call breakLine
call quit
my baseOperators.asm:
;------------------------------------------
; int atoi(Integer number)
; Ascii to integer function (atoi)
atoi:
push ebx ; preserve ebx on the stack to be restored after function runs
push ecx ; preserve ecx on the stack to be restored after function runs
push edx ; preserve edx on the stack to be restored after function runs
push esi ; preserve esi on the stack to be restored after function runs
mov esi, eax ; move pointer in eax into esi (our number to convert)
mov eax, 0 ; initialise eax with decimal value 0
mov ecx, 0 ; initialise ecx with decimal value 0
.conversionLoop:
xor ebx, ebx ; resets both lower and uppper bytes of ebx to be 0
mov bl, [esi+ecx] ; move a single byte into ebx register's lower half
cmp bl, 48 ; compare ebx register's lower half value against ascii value 48 (char value 0)
jl .conversionEnd ; jump if less than to label finished
cmp bl, 57 ; compare ebx register's lower half value against ascii value 57 (char value 9)
jg .conversionEnd ; jump if greater than to label finished
cmp bl, 10 ; compare ebx register's lower half value against ascii value 10 (linefeed character)
je .conversionEnd ; jump if equal to label finished
cmp bl, 0 ; compare ebx register's lower half value against decimal value 0 (end of string)
jz .conversionEnd ; jump if zero to label finished
sub bl, 48 ; convert ebx register's lower half to decimal representation of ascii value
add eax, ebx ; add ebx to our interger value in eax
mov ebx, 10 ; move decimal value 10 into ebx
mul ebx ; multiply eax by ebx to get place value
inc ecx ; increment ecx (our counter register)
jmp .conversionLoop ; continue multiply loop
.conversionEnd:
mov ebx, 10 ; move decimal value 10 into ebx
div ebx ; divide eax by value in ebx (in this case 10)
pop esi ; restore esi from the value we pushed onto the stack at the start
pop edx ; restore edx from the value we pushed onto the stack at the start
pop ecx ; restore ecx from the value we pushed onto the stack at the start
pop ebx ; restore ebx from the value we pushed onto the stack at the start
ret
;------------------------------------------
; int lenString(String message)
; String length calculation function
lenString:
push ebx
mov ebx, eax
iterateChar:
cmp byte [eax], 0
jz finalize
inc eax
jmp iterateChar
finalize:
sub eax, ebx
pop ebx
ret
;------------------------------------------
; void breakLine()
; Break a line - linefeed
breakLine:
push eax ; push eax on the stack
mov eax, 0x0a ; move linefeed into eax - 0x0a = 0Ah
push eax ; linefeed on stack to get adress
mov eax, esp ; move adress of current pointer into eax
call printString
pop eax
pop eax
ret ; return
;------------------------------------------
; void exit()
; Exit program and restore resources
quit:
mov eax, 1 ; invoke SYS_EXIT (kernel opcode 1)
mov ebx, 0 ; return 0 status on exit - 'No Errors'
int 0x80 ; 0x80=80h
ret
and the print-functions in print.asm:
;------------------------------------------
; void printInteger (Integer number)
; Integer printing function (itoa)
printInteger:
push eax ; preserve eax on the stack to be restored after function runs
push ecx ; preserve ecx on the stack to be restored after function runs
push edx ; preserve edx on the stack to be restored after function runs
push esi ; preserve esi on the stack to be restored after function runs
mov ecx, 0 ; counter of how many bytes we need to print in the end
divideLoop:
inc ecx ; count each byte to print - number of characters
mov edx, 0 ; empty edx
mov esi, 10 ; mov 10 into esi
idiv esi ; divide eax by esi
add edx, 48 ; convert edx to it's ascii representation - edx holds the remainder after a divide instruction
push edx ; push edx (string representation of an intger) onto the stack
cmp eax, 0 ; can the integer be divided anymore?
jnz divideLoop ; jump if not zero to the label divideLoop
printLoop:
dec ecx ; count down each byte that we put on the stack
mov eax, esp ; mov the stack pointer into eax for printing
call printString ; call our string print function
pop eax ; remove last character from the stack to move esp forward
cmp ecx, 0 ; have we printed all bytes we pushed onto the stack?
jnz printLoop ; jump is not zero to the label printLoop
pop esi ; restore esi from the value we pushed onto the stack at the start
pop edx ; restore edx from the value we pushed onto the stack at the start
pop ecx ; restore ecx from the value we pushed onto the stack at the start
pop eax ; restore eax from the value we pushed onto the stack at the start
ret
;------------------------------------------
; void printString(String message)
; String printing function
printString:
push edx
push ecx
push ebx
push eax
call lenString
mov edx, eax ; nbytes - number of bytes to write (len), one for each letter plus the zero terminating byte
pop eax
mov ecx, eax ; buffer - move the memory address of our message string into ecx
mov ebx, 1 ; fd - filedescriptor, write to the STDOUT file
mov eax, 4 ; invoke SYS_WRITE (with fd, buf, nbytes / kernel opcode 4)
int 0x80 ; prozessor interupt 0x80 jump to system call, stack clean, 0x80=80h
pop ebx
pop ecx
pop edx
ret
I appreciate any tipps,
kind regards
Upvotes: 3
Views: 373
Reputation: 127
Your answers, full of expertise helped me learn much again. Thank you. rkhb's answer made my code work, implementing these changes:
_start:
pop ecx ; Get the arguments count
mov edx, 0
pop eax ; Pop away the program path
argumentsLoop:
cmp ecx, 1h
jz argumentsEnd
...
argumentsEnd:
mov eax, edx
call printInteger
Upvotes: 0
Reputation: 14409
If the program was started in a normal, conventional and legitimate way¹, there is always already one argument on the stack: the path to the program itself. So, the first POP (pop ecx
) gets at least 1. With two more arguments the value will be 3. Decrement the ECX register by one or compare it with 1:
...
argumentsLoop:
cmp ecx, 1h
jz argumentsEnd ; See footnote ¹
...
The address of the first command line argument is on the third position on the stack. You have to pop away the address of the program path:
...
_start:
pop ecx ; Get the arguments count
mov edx, 0
pop eax ; Pop away the program path
...
The function atoi
converts an ASCII string to an integer. The function printString
prints - as its name suggests - only strings, not integers. Use printInteger
instead:
...
argumentsEnd:
mov eax, edx
call printInteger
...
¹ It is possible to start the program without any argument (argc = 0) or with an argv[0] which is not covered by convention (see execve(2)). I wrote an example to demonstrate it:
get_argv.asm:
SECTION .data
LineFeed dw 10
nullstr db '(null)',0
argcstr db 'argc = '
argcstr1 db '---------------',0
argvstr db 'argv['
argvstr1 db '---------------',0
argvstr2 db '] = ',0
SECTION .text
global _start
_start:
push ebp
mov ebp, esp
mov eax, [ebp + 4] ; argc
mov edi, argcstr1
call EAX_to_DEC ; Convert EAX to a string pointed by EDI
mov esi, argcstr
call PrintString
mov esi, LineFeed
call PrintString
xor ecx, ecx
.J1:
mov eax, ecx
mov edi, argvstr1
call EAX_to_DEC ; Convert EAX to a string pointed by EDI
mov esi, argvstr
call PrintString
mov esi, argvstr2
call PrintString
mov esi, [ebp+8+4*ecx] ; argv[ECX]
call PrintString
test esi, esi
jz .J2
mov esi, LineFeed
call PrintString
add ecx, 1
jmp .J1
.J2:
.exit:
mov esi, LineFeed
call PrintString
mov esp, ebp
pop ebp
mov eax, 1 ; SYS_EXIT
xor ebx, ebx ; Exit code = 0 = no error
int 0x80 ; Call Linux kernel
PrintString: ; ARG: ESI Pointer to ASCIZ string
pusha
test esi, esi
jne .J0
mov esi, nullstr
.J0:
mov eax, 4 ; SYS_WRITE
mov ebx, 1 ; STDOUT
mov ecx, esi
xor edx, edx ; Count of bytes to send
.J1:
cmp byte [esi], 0 ; Look for the terminating null
je .J2
add edx, 1
add esi, 1
jmp .J1
.J2:
int 0x80 ; Call Linux kernel
popa
ret
EAX_to_DEC: ; ARG: EAX integer, EDI pointer to string buffer
push ebx
push ecx
push edx
mov ebx, 10 ; Divisor = 10
xor ecx, ecx ; ECX=0 (digit counter)
.J1: ; First Loop: store the remainders
xor edx, edx ; Don't forget it!
div ebx ; EDX:EAX / EBX = EAX remainder EDX
push dx ; Push the digit in DL (LIFO)
add cl, 1 ; = inc cl (digit counter)
or eax, eax ; AX == 0?
jnz .J1 ; No: once more
mov ebx, ecx ; Store count of digits
.J2: ; Second loop: load the remainders in reversed order
pop ax ; get back pushed digits
or al, 00110000b ; to ASCII
mov [edi], al ; Store AL to [EDI] (EDI is a pointer to a buffer)
add edi, 1 ; = inc edi
loop .J2 ; until there are no digits left
mov byte [edi], 0 ; ASCIIZ terminator (0)
mov eax, ebx ; Restore Count of digits
pop edx
pop ecx
pop ebx
ret ; RET: EAX length of string (w/o last null)
start_get_argv.c:
#include <stdio.h>
#include <unistd.h>
int main ( int argc, char *argv[] )
{
char* asmprog = "./get_argv";
puts ("execute me\n");
printf ("argc = %d\n",argc);
for (int i=0; i <= argc; ++i)
{
printf ("argv[%d]=%s\n",i,argv[i]);
}
printf ("\nexecve %s\n\n",asmprog);
fflush (0);
execve (asmprog, NULL, NULL);
return 0;
}
Build both files in the same directory and run ./start_get_argv. The called ./get_argv will report argc = 0 and argv[0] = (null). The null pointer means "end of array". It's simple to handle that case: exit if argc is below or equal to 1:
...
argumentsLoop:
cmp ecx, 1h
jbe argumentsEnd
...
Upvotes: 1