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