Assembler Programmierung (AT&T Syntax)



  • Hallo,

    ich habe noch nie mit Assembler programmiert aber habe jetzt mehrere Aufgaben dazu. Dabei handelt es sich um AT&T und 32 bit.

    Kennt jemand von euch ein gutes Tutorial für Neulinge?

    Besonders wichtig sind mir

    - Daten und Speicheradressen hin- und her kopieren zwischen Registern und Variablen (Speicheradressen) in beide Richtungen
    - Schreiben auf der Standardausgabe
    - Grundrechenarten.

    Am besten ein gut erklärtes Tutorial mit guten Beispielen, was möglichst wenig Vorkentnisse im Bereich Assembler vorraussetzt.

    Viele Grüße,
    re342



  • Mir ist mal vor Jahren das hier über den Weg gelaufen:

    http://m-hoeppner.cwsurf.de/

    Sieht ganz gut aus.

    viele grüße
    ralph



  • Danke, das sieht wirklich recht gut aus.

    Eine Frage bleibt aber noch offen ::
    Wie kann ich den Inhalt eines Registers (z.B. ein long) in einen String schreiben?

    Beispiel (in C) :

    #include <stdio.h>
    #include <string.h>
    
    int main(){
    	char a[15];
    	long b = 4711;
    	ltoa(b,a,10);
    	// Jetzt enthält der String a '4711'
    
    	printf("%s",a);
    	return 0;
    }
    

    Hab es in Assembler schon versucht, aber da hat es nicht geklappt.

    .section .data
    
    a:	.ascii	""
    
    b:	.long	4711
    
    .section .text
    
    .global _start
    _start:
    
    	movl 	$a, %ebx
    	movl	(b), %ecx
    	movl    %ecx, (%ebx,1)		# An Speicherstelle %ebx den Wert in %ecx schreiben
    
    	movl	$4, %eax
    	movl	$1, %ebx		
    	movl	$a, %ecx
    	movl	$15, %edx
    	int	$0x80
    
    	movl	$1, %eax
    	movl	$0, %ebx
    	int	$0x80
    

    Hat jemand eine Idee, wie ich die Zahl in den String bekommen kann?



  • re342 schrieb:

    Hat jemand eine Idee, wie ich die Zahl in den String bekommen kann?

    Hab ich den Aufruf von ltoa übersehen?
    Machs erstmal in C ohne ltoa, dann ist Dir klar, wie es geht, und machs dann in assembler (da benutze aber den Stack zum Umdrehen, würde ich vorschlagen).



  • Als erstes brauchst Du genügend Platz für den String. Also "0000" statt "". Für die Umwandlung einer Zahl in einen String kannst du dir meine Homepage anschauen: http://dcla.rkhb.de/umwandlung/int2dez.html.

    Für den Anfang empfehle ich aber, sich die C-Bibliothek dienstbar zu machen:

    # Name:                  EauDeLinux.s
    # Assemblieren & Linken: gcc -m32 EauDeLinux.s
    # Ausführen:             ./a.out
    
    .data
        a:  .asciz  "0000"
        b:  .long   4711
        fmt1: .asciz "%d"
        fmt2: .asciz "%s\n"
    
    .text
    .global main
    main:
    
        pushl (b)
        pushl $fmt1
        pushl $a
        call sprintf
        addl $12, %esp
    
        pushl $a
        pushl $fmt2
        call printf
        addl $8, %esp
    
        pushl $0
        call fflush
        addl $4, %esp
    
        movl $1, %eax
        movl $0, %ebx
        int $0x80
    

    Solltest Du mit 64-bit spielen wollen, dann musst Du das unbedingt mitteilen. Das geht nämlich gaaanz anders. Dann gibts aber auch keine richtigen Anfängerinformationen mehr im Netz.

    Du solltest auch immer folgenden Link griffbereit haben:
    https://sourceware.org/binutils/docs/as/

    viele grüße
    ralph



  • Also... es ist 32 Bit und in der Aufgabe sollte ich das nach Möglichkeit ohne die C-Bibliotheken lösen. Aber vielen Dank, das Programm scheint zu funktionieren und hat mir einen guten Überblick verschaffen.

    Ich habe es erst mal versucht möglichst Assembler-ähnlich in C zu programmieren.

    #include <stdio.h>
    
    int main(){
    	char z = '0';
    	long num = 812345;
    	int a = 10;
    	int b = 1;
    	int c = 1;
    	int i = 0;
    	int j;
    	char string[15] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
    	char stack[15];
    
    	while(1) {
    		c = num / a;
    		c = c * a;
    		c = num - c;
    		c = c / b;
    		a = a * 10;
    		b = b * 10;
    		z += c;
    		if(c==0) goto weiter;
    		stack[i] = z;
    		z = '0';
    		i++;
    	}
    
    	weiter:
    	for(j=0;j<i;j++) string[j] = stack[i-(j+1)];
    
    	printf("%s",string);
    	return 0;
    }
    

    Nur um die sprintf werde ich wohl nicht hinweg kommen, da ich sonst ja nicht auf den String schreiben kann (oder doch?!).



  • re342 schrieb:

    Also... es ist 32 Bit und in der Aufgabe sollte ich das nach Möglichkeit ohne die C-Bibliotheken lösen.

    Bei "Linux" und "long" klingeln bei mir halt die Alarmglocken.

    Nur um die sprintf werde ich wohl nicht hinweg kommen, da ich sonst ja nicht auf den String schreiben kann (oder doch?!).

    Ein String ist auch nur ein Haufen Bytes im Speicher. Und mit denen kannst Du mit Assembler alles machen, was man mit Bytes im Speicher machen kann.

    Einen Algorithmus erst einmal in einer Hochsprache auszuprobieren, ist immer eine gute Idee. Führe Dir aber immer vor Augen, dass Du in C nur mit Variablen arbeitest, in Assembler aber mit Speicher und Register. Was der Compiler daraus gemacht hat, kannst mit " gcc -m32 -S -fno-asynchronous-unwind-tables test.c " in die Datei " test.s " schreiben lassen. In diesem Fall gibts noch eine Besonderheit: In Assembler bekommst du mit DIV Ergebnis und Rest in einem Rutsch.

    Dein C-Beispiel funktioniert zwar, aber ich hätte da Bedenken anzumelden: Versuche, mit sowenig Multiplikationen und Divisionen wie möglich auszukommen. Die kosten Zeit (man nennt das "teuer"). GCC setzt im höchsten Optimierungsgrad ganz abenteuerliche Befehlssequenzen ein, um MUL und DIV zu vermeiden.

    Ich habe in folgendem Beispiel mal spaßeshalber die PUSH/POP-Geschichte weggelassen und lasse die Reste rückwärts in den Dezimalstring schreiben. Das Resultat ist ein Zeiger mitten im String.

    .data
        dez:  .space 16         # Tipp: Denke immer in 16er-Schritten
        last = .-1              # Adresse des letzten Bytes von dez
                                #    (https://sourceware.org/binutils/docs/as/Dot.html#Dot)
        num:  .long 812345
    
    .text
    .global main                # für GCC
    .global _start              # für LD
    
    int2dez:                    # ARG: ESI=Zeiger auf letztes Byte im String, EAX=umzuwandelndes Register
        movl $10, %ebx
        xorl %ecx, %ecx         # Anzahl der Umwandlungen => Größe des Strings
    
        1:                      # Stichwort: local labels
        decl %esi               # Zeiger dekrementieren (allerletztes Zeichen bleibt unberührt)
        xorl %edx, %edx         # DIV berührt auch EDX
        divl %ebx               # EDX:EAX/EBX => EAX Rest EDX
        orb $0x30, %dl          # Umwandlung zu ASCII
        movb %dl, (%esi)        # Abspeichern
        incl %ecx               # ECX inkrementieren
        testl %eax, %eax        # EAX == 0?
        jnz 1b                  # nein: nochmal
    
        movl %ecx, %eax         # Anzahl der Umwandlungen => Größe des Strings
        ret                     # RET: ESI=Zeiger auf erste Ziffer im String, EAX=Größe des Strings
    
    main:
    _start:
    
        movl $last, %esi
        movl (num), %eax
        call int2dez
    
        movl %eax, %edx         # count of bytes to send
        movl %esi, %ecx         # ptr to output buffer
        movl $1, %ebx           # STDOUT
        movl $4, %eax           # write
        int $0x80               # syscall
    
        mov $0, %ebx            # exit (0)
        mov $1, %eax
        int $0x80
    


  • Vielen Dank. Genau soetwas in der Art habe ich gemeint. Und funktionieren tut es auch 🙂

    Ich habe nur eine Verständnisfrage zu Zeile 14 (sorry, bin wirklich noch Anfänger in Assembler) :

    Was macht die XOR-Funktion hier mit dem noch leeren Register %eci? Wird dadurch der Wert 0 ins Register geschrieben?

    re342



  • re342 schrieb:

    Ich habe nur eine Verständnisfrage zu Zeile 14 (sorry, bin wirklich noch Anfänger in Assembler) :

    Was macht die XOR-Funktion hier mit dem noch leeren Register %eci? Wird dadurch der Wert 0 ins Register geschrieben?

    Ja. Das ist die kürzeste und schnellste Methode, ein Register auf Null zu setzen. Es heißt, dass die neuesten Prozessoren in diesem Fall gar kein echtes (bitweise) XOR durchführen, sondern das Register umstandslos auf Null setzen. Ob das Register bereits leer (=0) war, weiß die Funktion nicht. In diesem Fall weiß es auch der Programmierer nicht. Wenn dir ein Debugger ein leeres Register anzeigt, dann muss das nicht heißen, dass das immer so ist.

    viele grüße
    ralph



  • Ich habe noch mal eine Frage zum schreiben in den Speicherbereich.

    .data
        num:  .long 12345
    
    .text
    
    .global main                # für GCC
    
    .global _start              # für LD
    
    main:
    
    _start:
    
        movl $num, %esi			# Speicheradresse von num in esi
        movl $42, %eax			# Wert 42 in eax
        movb %al, (%esi)		# 42 an Speicherstelle von num verschieben
    
        movl 1, %edx         	# 1 byte zu senden
        movl $num, %ecx         # file descriptor
        movl $1, %ebx           # STDOUT
        movl $4, %eax           # write
        int $0x80               # syscall
    
        mov $0, %ebx            # exit (0)
        mov $1, %eax
    
        int $0x80				# syscall
    

    Mit diesem Code habe ich versucht, den in num liegenden Wert von 12345 auf 42 zu ändern. Daraufhin bekam ich eine Fehlermeldung "Speicherzugriffsfehler". Woran liegt das bzw. was habe ich hier falsch gemacht?

    Ich wollte zum üben nur mal ein einfaches Beispiel konstruieren.

    lg



  • Bei AT&T-Syntax hat man immer irgendwo ein Prefix vergessen. Und siehe da:

    movl 1, %edx

    sollte heißen:

    movl $1, %edx

    Ist dir klar, dass der write-syscall nur ASCII-Zeichen (char) ausgibt, du aber für num Platz für ein Long reserviert und belegt hast?

    viele grüße
    ralph



  • Danke, an diesem Präfix hat es gelegen. Dass man ja sowieso nur Strings über write ausgeben kann hatte ich gerade nicht bedacht. Es ging mir aber sowieso erst einmal um das Prinzip des Schreibens in den Speicherbereich.

    Dieser Code macht jetzt das, was ich wollte.

    .data
        num:  .ascii "12345\n"
        num2: .ascii "AB"
    
    .text
    .global main                # für GCC
    .global _start              # für LD
    
    main:
    _start:
    
        movl $num, %esi		# Speicheradresse von num in esi
        movl (num2), %eax		# "AB" in eax
        movb %al, (%esi)		# "A" an Speicherstelle von num verschieben
    
        addl $1, %esi
        movb %ah, (%esi)		# "B" an Speicherstelle von num+1 verschieben
    
        movl $6, %edx         	# count of bytes to send
        movl $num, %ecx         # file descriptor
        movl $1, %ebx           # STDOUT
        movl $4, %eax           # write
        int $0x80               # syscall
    
        mov $0, %ebx            # exit (0)
        mov $1, %eax
    
        int $0x80    # syscall
    


  • Ich habe noch mal eine Frage.

    Ich arbeite mit lokalen Variablen auf dem Stack und würde gerne zur Erleichterung der Arbeit gerne bestimmte Code-Frequenzen durch den Namen der Parameter ersetzen lassen.

    Beispiel :

    -20(%rbp)
    

    steht für die Adresse der lokalen Variable n.
    Dann möchte ich immer

    -20(%rbp)
    

    durch n ersetzen lassen.

    #define n -20(%rbp)
    

    klappt in AT&T leider nicht...

    lg



  • Es ist besser, für eine neue Frage einen neuen Thread zu starten. Da du mit 'RBP' arbeitest, gehe ich mal von einem 64-bit-Programm in Linux aus.

    re342 schrieb:

    #define n -20(%rbp)
    

    klappt in AT&T leider nicht...

    Bei GAS habe ich etwas Vergleichbares (noch) nicht gefunden. Aber Du kannst den Preprozessor von GCC anzapfen, wenn Du als Dateinamen-Suffix ein großes .S nimmst und GCC als Assembler/Linker nimmst:

    # Name:                     hallo.S
    # Assemblieren & Linken:    gcc -m64 hallo.S
    # Ausführen:                ./a.out
    
    .global main
    
    .data
    
        hallo:  .asciz "Welt"
        format: .asciz "%u Hallo %s\n"
    
    .text
    
    #define arg1 16(%rbp)
    #define arg2 24(%rbp)
    #define loc1 -8(%rbp)
    
    upro:
        push %rbp
        mov %rsp, %rbp
        sub $8, %rsp
        movq $1, loc1
    
        mov arg1, %rdi
        mov loc1, %rsi
        mov arg2, %rdx
        xor %rax, %rax
    
        call printf
    
        incq loc1
        mov arg1, %rdi
        mov loc1, %rsi
        mov arg2, %rdx
        xor %rax, %rax
    
        call printf
    
        leave
        ret
    
    main:
        push $hallo
        push $format
        call upro
        add $16, %rsp
    
        mov $format, %rdi
        mov $99, %rsi
        mov $hallo, %rdx
        xor %rax, %rax
        call printf
    
        mov $0, %rax
        ret
    

    viele grüße
    ralph


Anmelden zum Antworten