Anfängerproblem mit einfachem Programm



  • Hallo,
    dieses einfache Programm lässt sich zwar kompilieren, liefert aber einen Speicherzugriffsfehler unter Linux.

    section .text
    global _start
    
    _start:
          push ebp
          mov ebp, esp
          mov eax, [ebp+4] ; argc nach eax
          mov esp, ebp
          pop ebp
          ret
    

    Das Programm sollte eigentlich argc, d.h. die Anzahl der übergebenen Argumente zurückgeben.



  • Weiß denn keiner die Lösung?

    Ich mein, [ebp+4] ist doch die Anzahl der Argumente (in C ist das argc )?

    Und eax enthält den Wert, der zurückgegeben wird.

    In C sollte das Programm so aussehen:

    int main(int argc, char *argv[])
    {
         return argc;
    }
    


  • Assemblerlerner schrieb:

    Weiß denn keiner die Lösung?

    Wenn Du Betriebssystem (uname -s -r), den Assembler (volle Kommandozeile) und den Linker (volle Kommandozeile) nicht angibst, stochert ein Helfer nur im Nebel.

    Deshalb rate ich mal, dass Dein letztes 'ret' denkt, argc wäre eine Rücksprungadresse. Richtig wäre Dein Programm deshalb so:

    section .text
    global _start
    
    _start:
          push ebp
          mov ebp, esp
          mov eax, [ebp+4] ; argc nach eax
          mov esp, ebp
          pop ebp
    
          mov ebx, eax        ; Exitcode
          mov eax, 1          ; system-exit
          int 0x80            ; syscall
    


  • Ja, das ist jetzt aber linux-only mit Kernel-Interrupt 0x80.

    Warum kann ich nicht die normale leave-Funktion verwenden?



  • Assemblerlerner schrieb:

    Ja, das ist jetzt aber linux-only mit Kernel-Interrupt 0x80.

    Warum kann ich nicht die normale leave-Funktion verwenden?

    Ich ahnte es doch...
    Gut getrollt, Poster 👍

    viele grüße
    ralph



  • Ich verstehe nicht?

    Leave = mov ebp, esp
            pop ebp
            ret
    

    ?
    Um ehrlich zu sein, ich verstehe nichtmal, was hier das Problem ist, ich hab den Code mit mehreren Assemblern ausprobiert, das Problem besteht weiterhin.



  • ret in Assembler ist NICHT Return x in C, sondern ein Rücksprungbefehl für den CALL Befehl. Der (near) - ret - Rücksprung greift auf die zwischengespeicherte Sprungmarke zurück, die auf dem Stack liegt.
    siehe auch http://www.i8086.de/asm/8086-88-asm-ret.html

    Eine Funktion mit einem "Ret" am Ende wird typischerweise im Hauptprogramm geCALLt, im Sinne von CALL printf ... oder ähnlich.
    (davon ist hier aber nichts zu sehen, sondern nur der "ret" also der Rücksprung (nach ???))



  • Ok, danke für die Erklärung.

    Nun also die Frage: Ist es möglich, das Problem zu lösen, ohne irgendwelche Kernel-Interrupts zu verwenden?



  • "ohne irgendwelche Kernel-Interrupts zu verwenden" ist sehr ungenau ausgedrueckt. Es gibt prinzipiell mehrere Moeglichkeiten. Manche mehr, manche weniger sinnvoll.
    Generell laesst sich jedoch sagen, dass es nicht moeglich ist, das beschriebene Problem gaenzlich ohne die Verwendung von Systemaufrufen zu loesen. (Uebrigens egal in welcher Programmiersprache.) Du kannst die Interrupts jedoch zumindest mehr oder weniger aus deinen Quellcodes verbannen. Also einige Vorschlaege:

    1. Schreibe ein mit dem Ziel-System (Linux, Windows etc.) kompatibles Programm, das o.g. Binaercode in einen ausfuehrbaren Speicherbereich laedt, eine Ruecksprungadresse zu Code auf den Stack legt, welcher das Programm beendet (oder was auch immer), und dann zur Startadresse des oben geposteten Codes springt.
    Keine Kernel-Interrupts im Programm-Code selbst verwendet.

    2. Benutze sysenter/syscall-Funktionen. Systemaufrufe/Linux (nicht systemunabhaengig, ich weiss. Nur der Vollstaendigkeit halber)

    3. Binde kapselnde/abstrahierende Funktionsbibliotheken ein. QT vielleicht? Eigene funktionsbibliotheken? Diese muessen jedoch zwangslaeufig mit dem Ziel-System kompatibel sein und verwenden intern Systemaufrufe wie Interrupts.

    4. Verwende Macros zur Kapselung. z.B.

    %ifdef WINDOWS
    %macro Exit 0-1 0
    
    	push	%1
    	call	ExitProcess
    
    %endmacro	;	Exit
    %endif
    
    %ifdef DOS
    %macro Exit 0-1 0
    
    	mov	eax, 4C00h | %1
    	int	21h
    
    %endmacro	;	Exit
    %endif
    ;...
    Exit 0
    

    (Beispiel fuer Verwendung von Macros in NASM)

    Edit:
    Bliebe zu ergaenzen, dass die Methode, ein Programm durch ein einfaches "ret" am Programmende geordnet zu beenden, meines Wissens sowohl in DOS als auch in Windows funktioniert. Beide Systeme initialisieren beim Laden von Programmen den Stack entsprechend, so dass dadurch eine Funktion mit Systemaufruf zum Beenden des Programms aufgerufen wird (int 20h bei DOS z.B.).



  • Nobuo T schrieb:

    Generell laesst sich jedoch sagen, dass es nicht moeglich ist, das beschriebene Problem gaenzlich ohne die Verwendung von Systemaufrufen zu loesen.

    Einspruch, Euer Ehren! Ich weiß, dass ich unzulässigerweise gequotemardert habe, aber es geht ja eigentlich darum, etwas weitgehend Systemunabhängiges wie C in Assembler zu programmieren. Und das geht, wenn man GCC als Linker einsetzt:

    ; Name:         test.asm
    ; Assemblieren: nasm -felf32 test.asm
    ; Linken:       gcc -m32 test.o
    ; Aufrufen:     ./a.out hallo
    
    ; C-Äquivalent:
    ; #include <stdio.h>
    ; int main( int argc, char *argv[] )
    ; {
    ;   printf ("%u\n",argc);
    ;   printf ("%s\n",argv[0]);
    ;   return 0;
    ; }
    
    extern printf
    
    section .data
        fmt1 db `%u\n`,0
        fmt2 db `%s\n`,0
    
    section .text
    global main
    
    main:
        push ebp
        mov ebp, esp
        mov eax, [ebp+8]            ; argc
    
        push eax
        push fmt1
        call printf
        add esp, 8
    
        mov eax, [ebp+12]           ; argv
        push dword [eax]            ; argv[0]
        push fmt2
        call printf
        add esp, 8
    
        mov eax, [ebp+8]            ; argc
        cmp eax, 2
        jb .exit
    
        mov eax, [ebp+12]           ; argv
        add eax, 4                  ; argv[1]
        push dword [eax]
        push fmt2
        call printf
        add esp, 8
    
        .exit:
        mov esp, ebp
        pop ebp
        ret
    

    Irgendwo sind zwar immer noch Systemaufrufe, aber nicht mehr im Quelltext. Dann bleibt natürlich die Frage, warum man nicht gleich in C programmiert.

    viele grüße
    ralph



  • Das kostet dich 10ct in die Smartass-Kasse. :p



  • Moin.

    Nobuo T schrieb:

    Bliebe zu ergaenzen, dass die Methode, ein Programm durch ein einfaches "ret" am Programmende geordnet zu beenden, meines Wissens sowohl in DOS als auch in Windows funktioniert. Beide Systeme initialisieren beim Laden von Programmen den Stack entsprechend, so dass dadurch eine Funktion mit Systemaufruf zum Beenden des Programms aufgerufen wird (int 20h bei DOS z.B.).

    Unter DOS sollte dann aber der Stack von unserem Programm nicht korumpiert worden sein und bei Programmen mit dem Speichermodell Large und Huge für multiple Code Segmente muss CS sich im Segment des PSP befinden.

    Weil DOS vor dem Start nur ein einzelnes WORD(OFFSET 0) als Rücksprung-Adresse auf den Stack pusht, aber keine Segmentadresse. Und so kann von einem anderem Code-Segment aus, wo vermutlich kein "int 20"-Befehl am Anfang eingetragen ist, der Rücksprung mit "ret" unser Programm so nicht wie beabsichtigt beenden, sondern es wird dann mit dem dortigen Code unser Programm weiter fortgesetz.

    Die Segmentadresse des PSPs liesse sich aber über AH=62h INT 21h return in BX ermitteln.

    Dirk


Log in to reply