初学者教程:汇编程序,分段错误

时间:2018-01-12 13:29:47

标签: assembly

我是@coding的初学者,这是我关于堆栈溢出的第一个问题,虽然你的一些很好的答案已经给我带来了一些方法..

尝试此assembler-tutorial时,我在运行程序后遇到分段错误。我试着评论每一行,并注意到当我"调用printString"时,程序崩溃了。在第30行。

当我尝试使用gdb调试时(哎呀,我真的不知道我在那里做什么......)我在函数内部得到一个错误" iterateChar"在我的lenString调用中。 (在我的函数文件中 - baseOperators.asm - 第50行)

我怀疑,不知怎的,我搞砸了eax寄存器中的信息,但我不知道为什么,发生了什么以及如何解决这个问题。我的代码看起来非常类似于asmtutor.com上的教程16中的那个代码 - 我认为这样,并且无论出于何种原因都可以。请帮忙。

(我正在编译" $ 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

和print.asm中的print函数:

;------------------------------------------
; 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

我感谢任何tipps,

亲切的问候

2 个答案:

答案 0 :(得分:1)

  1. 如果程序是以正常的,传统的和合法的方式启动的,那么堆栈上已经有始终一个参数:程序本身的路径。因此,第一个POP(pop ecx)至少得到1.另外两个参数的值为3.将ECX寄存器减1或将其与1进行比较:

    ...
    argumentsLoop:
        cmp ecx,    1h
        jz  argumentsEnd         ; See footnote ¹
    ...
    
  2. 第一个命令行参数的地址位于堆栈的第三个位置。你必须弹出程序路径的地址:

    ...
    _start:
    
        pop ecx                 ; Get the arguments count
        mov edx,    0
        pop eax                 ; Pop away the program path
    ...
    

    Here是由Gunner撰写的优秀文章。

  3. 函数atoi将ASCII字符串转换为整数。函数printString打印 - 顾名思义 - 只打印字符串,而不是整数。请改用printInteger

    ...
    argumentsEnd:
        mov eax,    edx
        call    printInteger
    ...
    
  4. ¹可以在没有任何参数(argc = 0)或argv [0]的情况下启动程序,这不是惯例所涵盖的(参见execve(2))。我写了一个例子来证明它:

    <强> 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;
    }
    

    在同一目录中构建两个文件并运行./start_get_argv。被调用的./get_argv将报告argc = 0和argv [0] =(null)。空指针表示“数组的结尾”。处理这种情况很简单:如果argc低于或等于1,则退出:

    ...
    argumentsLoop:
        cmp ecx,    1h
        jbe  argumentsEnd
    ...
    

答案 1 :(得分:0)

你的答案充满了专业知识,帮助我重新学到了很多东西。谢谢。 rkhb的回答使我的代码工作,实现了这些变化:

_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