Oryon
Oryon

Reputation: 127

Beginner Tutorial: Assembler, Segmentation fault

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

Answers (2)

Oryon
Oryon

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

rkhb
rkhb

Reputation: 14409

  1. 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 ¹
    ...
    
  2. 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
    ...
    

    Here is an excellent article written by Gunner.

  3. 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

Related Questions