Was tut dieser Assembly Code? (Anfängerfrage)
-
@Wade1234 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
das l hinter dem mov ruft eine funktion namens movl auf (ja wer hätte das gedacht?), es gibt auch noch movb usw. damit legst du quasi die registerbreite fest.
% wird vor registernamen gestellt, $ vor konstanten (in zeile 24 wird der wert 0 in das (32 bit) register eax geladen)Beachte auch, dass es verschiedene Syntax für assember gibt, also die AT&T und die von Intel (welche ich leichter lesbar finde, weil sie weniger Sonderzeichen hat). Wenn du dem gcc bei -S noch
-masm=intel
mitgibst, bekommst du die Intel-Syntax.Insbesondere gibt es bei Intel nur ein "mov" und die Länge ergibt sich automatisch aus den Operanden, während du hier explizit mov-Long hast. Wenn sich die Länge z.B. bei Pointern nicht automatisch ergibt, musst du sowas wie "word ptr" dazuschreiben. Ach ja, und die Reihenfolge der mov-Zuweisung ist auch umgedreht Und sowas wie $ und % brauchst du da auch nicht. Vielleicht wäre das auch für dich, @EinNutzer0, einfacher zu verstehen?
Ansonsten wäre noch mein Tipp, mit dem Compiler Explorer ein bisschen rumzuspielen und dir Assember für ein paar kleine Funktionen anzugucken. Der CE ist per Default auf Intel-Syntax eingestellt (du kannst da aber den Haken bei Intel ausmachen, wenn du willst).
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Ne, ich muss für eine Prüfung den (Inline) Assemblercode eines bestimmten Mikrocontrollers verstehen und anwenden können.
Warum lernst du nicht gleich für den Controller.
Es gibt einfachere Syntax als x64 Assembler.
-
Ok, Danke für alle Antworten bisher. Noch eine Frage... Wenn ich zum Beispiel diesen C Code habe:
#include <stdio.h> #include <math.h> int getx(int r, int w) { int x; float w2 = w * (M_PI / 180.0); x = (int) (r * cos(w2)); return x; } int gety(int r, int w) { int x; float w2 = w * (M_PI / 180.0); x = (int) (r * sin(w2)); return x; } void get_kreis(int to) { int i = 0, x, y, x2, y2; while (i <= to) { x = getx(25, i); y = gety(25, i); if (x != x2 || y != y2) { printf("i=%03d x=%03d y=%03d\n", i, x, y); } x2 = x; y2 = y; i++; } printf("ready.\n"); } int main () { get_kreis(720); }
wie komme ich dann zu dem nächsten Schritt, dieses Programm manuell in Assembler zu schreiben?
Der Aufruf
gcc -O0 -S -fverbose-asm -o main.S main.c -lm
ergibt Folgendes:.file "main.c" # GNU C17 (Debian 8.3.0-6) version 8.3.0 (x86_64-linux-gnu) # compiled by GNU C version 8.3.0, GMP version 6.1.2, MPFR version 4.0.2, MPC version 1.1.0, isl version isl-0.20-GMP # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 # options passed: -imultiarch x86_64-linux-gnu main.c -mtune=generic # -march=x86-64 -auxbase-strip main.S -O0 -fverbose-asm # options enabled: -fPIC -fPIE -faggressive-loop-optimizations # -fasynchronous-unwind-tables -fauto-inc-dec -fchkp-check-incomplete-type # -fchkp-check-read -fchkp-check-write -fchkp-instrument-calls # -fchkp-narrow-bounds -fchkp-optimize -fchkp-store-bounds # -fchkp-use-static-bounds -fchkp-use-static-const-bounds # -fchkp-use-wrappers -fcommon -fdelete-null-pointer-checks # -fdwarf2-cfi-asm -fearly-inlining -feliminate-unused-debug-types # -ffp-int-builtin-inexact -ffunction-cse -fgcse-lm -fgnu-runtime # -fgnu-unique -fident -finline-atomics -fira-hoist-pressure # -fira-share-save-slots -fira-share-spill-slots -fivopts # -fkeep-static-consts -fleading-underscore -flifetime-dse # -flto-odr-type-merging -fmath-errno -fmerge-debug-strings -fpeephole # -fplt -fprefetch-loop-arrays -freg-struct-return # -fsched-critical-path-heuristic -fsched-dep-count-heuristic # -fsched-group-heuristic -fsched-interblock -fsched-last-insn-heuristic # -fsched-rank-heuristic -fsched-spec -fsched-spec-insn-heuristic # -fsched-stalled-insns-dep -fschedule-fusion -fsemantic-interposition # -fshow-column -fshrink-wrap-separate -fsigned-zeros # -fsplit-ivs-in-unroller -fssa-backprop -fstdarg-opt # -fstrict-volatile-bitfields -fsync-libcalls -ftrapping-math -ftree-cselim # -ftree-forwprop -ftree-loop-if-convert -ftree-loop-im -ftree-loop-ivcanon # -ftree-loop-optimize -ftree-parallelize-loops= -ftree-phiprop # -ftree-reassoc -ftree-scev-cprop -funit-at-a-time -funwind-tables # -fverbose-asm -fzero-initialized-in-bss -m128bit-long-double -m64 -m80387 # -malign-stringops -mavx256-split-unaligned-load # -mavx256-split-unaligned-store -mfancy-math-387 -mfp-ret-in-387 -mfxsr # -mglibc -mieee-fp -mlong-double-80 -mmmx -mno-sse4 -mpush-args -mred-zone # -msse -msse2 -mstv -mtls-direct-seg-refs -mvzeroupper .text .globl getx .type getx, @function getx: .LFB0: .cfi_startproc pushq %rbp # .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp #, .cfi_def_cfa_register 6 subq $32, %rsp #, movl %edi, -20(%rbp) # r, r movl %esi, -24(%rbp) # w, w # main.c:7: float w2 = w * (M_PI / 180.0); cvtsi2sd -24(%rbp), %xmm1 # w, _1 movsd .LC0(%rip), %xmm0 #, tmp95 mulsd %xmm1, %xmm0 # _1, _2 # main.c:7: float w2 = w * (M_PI / 180.0); cvtsd2ss %xmm0, %xmm2 # _2, tmp98 movss %xmm2, -4(%rbp) # tmp98, w2 # main.c:8: x = (int) (r * cos(w2)); cvtsi2sd -20(%rbp), %xmm3 # r, _3 movsd %xmm3, -32(%rbp) # _3, %sfp # main.c:8: x = (int) (r * cos(w2)); cvtss2sd -4(%rbp), %xmm0 # w2, _4 call cos@PLT # # main.c:8: x = (int) (r * cos(w2)); mulsd -32(%rbp), %xmm0 # %sfp, _6 # main.c:8: x = (int) (r * cos(w2)); cvttsd2si %xmm0, %eax # _6, tmp96 movl %eax, -8(%rbp) # tmp96, x # main.c:9: return x; movl -8(%rbp), %eax # x, _11 # main.c:10: } leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size getx, .-getx .globl gety .type gety, @function gety: .LFB1: .cfi_startproc pushq %rbp # .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp #, .cfi_def_cfa_register 6 subq $32, %rsp #, movl %edi, -20(%rbp) # r, r movl %esi, -24(%rbp) # w, w # main.c:15: float w2 = w * (M_PI / 180.0); cvtsi2sd -24(%rbp), %xmm1 # w, _1 movsd .LC0(%rip), %xmm0 #, tmp95 mulsd %xmm1, %xmm0 # _1, _2 # main.c:15: float w2 = w * (M_PI / 180.0); cvtsd2ss %xmm0, %xmm2 # _2, tmp98 movss %xmm2, -4(%rbp) # tmp98, w2 # main.c:16: x = (int) (r * sin(w2)); cvtsi2sd -20(%rbp), %xmm3 # r, _3 movsd %xmm3, -32(%rbp) # _3, %sfp # main.c:16: x = (int) (r * sin(w2)); cvtss2sd -4(%rbp), %xmm0 # w2, _4 call sin@PLT # # main.c:16: x = (int) (r * sin(w2)); mulsd -32(%rbp), %xmm0 # %sfp, _6 # main.c:16: x = (int) (r * sin(w2)); cvttsd2si %xmm0, %eax # _6, tmp96 movl %eax, -8(%rbp) # tmp96, x # main.c:17: return x; movl -8(%rbp), %eax # x, _11 # main.c:18: } leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size gety, .-gety .section .rodata .LC1: .string "i=%03d x=%03d y=%03d\n" .LC2: .string "ready." .text .globl get_kreis .type get_kreis, @function get_kreis: .LFB2: .cfi_startproc pushq %rbp # .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp #, .cfi_def_cfa_register 6 subq $48, %rsp #, movl %edi, -36(%rbp) # to, to # main.c:22: int i = 0, x, y, x2, y2; movl $0, -4(%rbp) #, i # main.c:23: while (i <= to) { jmp .L6 # .L9: # main.c:24: x = getx(25, i); movl -4(%rbp), %eax # i, tmp87 movl %eax, %esi # tmp87, movl $25, %edi #, call getx # movl %eax, -16(%rbp) # tmp88, x # main.c:25: y = gety(25, i); movl -4(%rbp), %eax # i, tmp89 movl %eax, %esi # tmp89, movl $25, %edi #, call gety # movl %eax, -20(%rbp) # tmp90, y # main.c:26: if (x != x2 || y != y2) { movl -16(%rbp), %eax # x, tmp91 cmpl -8(%rbp), %eax # x2, tmp91 jne .L7 #, # main.c:26: if (x != x2 || y != y2) { movl -20(%rbp), %eax # y, tmp92 cmpl -12(%rbp), %eax # y2, tmp92 je .L8 #, .L7: # main.c:27: printf("i=%03d x=%03d y=%03d\n", i, x, y); movl -20(%rbp), %ecx # y, tmp93 movl -16(%rbp), %edx # x, tmp94 movl -4(%rbp), %eax # i, tmp95 movl %eax, %esi # tmp95, leaq .LC1(%rip), %rdi #, movl $0, %eax #, call printf@PLT # .L8: # main.c:29: x2 = x; movl -16(%rbp), %eax # x, tmp96 movl %eax, -8(%rbp) # tmp96, x2 # main.c:30: y2 = y; movl -20(%rbp), %eax # y, tmp97 movl %eax, -12(%rbp) # tmp97, y2 # main.c:31: i++; addl $1, -4(%rbp) #, i .L6: # main.c:23: while (i <= to) { movl -4(%rbp), %eax # i, tmp98 cmpl -36(%rbp), %eax # to, tmp98 jle .L9 #, # main.c:33: printf("ready.\n"); leaq .LC2(%rip), %rdi #, call puts@PLT # # main.c:34: } nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE2: .size get_kreis, .-get_kreis .globl main .type main, @function main: .LFB3: .cfi_startproc pushq %rbp # .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp #, .cfi_def_cfa_register 6 # main.c:38: get_kreis(720); movl $720, %edi #, call get_kreis # movl $0, %eax #, _3 # main.c:39: } popq %rbp # .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE3: .size main, .-main .section .rodata .align 8 .LC0: .long 2723323193 .long 1066524486 .ident "GCC: (Debian 8.3.0-6) 8.3.0" .section .note.GNU-stack,"",@progbits
Jetzt wird quasi schon beschrieben, was jede Zeile tut. Ich möchte jedoch / muss jedoch auch so ein Programm selber, ohne Hilfe des Compilers in der Prüfung hinbekommen. Kann man das überhaupt lernen?
-
Automatisch generierter Code ist übel zu lesen.
Da sind viele Register-indirekte Zugriffe drin, die du so eigentlich nicht schreiben wirst.
Du wirst auch keine fertige Funktionsin
oder zur Konvertierung haben.Aber ja, man kann das in Assembler schreiben.
Der sieht dann aber ganz anders aus.
-
Ich versuche mal, das selber hinzubekommen. ...
sin
undcos
(-lm
) sind natürlich berechtigte Einwände. Selber werde ich diese Funktionen nicht hin bekommmen...
-
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Ich möchte jedoch / muss jedoch auch so ein Programm selber, ohne Hilfe des Compilers in der Prüfung hinbekommen. Kann man das überhaupt lernen?
Und was für Lehrmaterialien hast du bekommen? Wenn das prüfungsrelevant ist, dann hast du doch bestimmt irgendwas bekommen haben, womit du lernen sollst?! Ein korrektes Assemberprogramm in einer Prüfungssituation zu schreiben, halte ich für relativ schwierig, wenn es über "einfache" Dinge mit "normalen" Register hinausgeht - also z.B. wenn du jetzt auch noch die SSE-Befehle (die xmm-Register) kennen sollst (die Leute scheitern bei Bewerbungsgesprächen schon regelmäßig an einfachen Schleifen - in Hochsprachen!).
Zu sin und cos: sehr häufig kann man, wenn man den Wertebereich, für den sin/cos aufgerufen werden, kennt, die Funktionen durch eine Taylorexpansion annähern. Wenn zum Beispiel nahe 0, dann
-
@DirkB sagte in Was tut dieser Assembly Code? (Anfängerfrage):
@EinNutzer0 sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Ne, ich muss für eine Prüfung den (Inline) Assemblercode eines bestimmten Mikrocontrollers verstehen und anwenden können.
Warum lernst du nicht gleich für den Controller.
Es gibt einfachere Syntax als x64 Assembler.Das da!!!!!111elf
-
@EinNutzer0 Bei deinem Code würde ich erstmal getx und gety zusammen führen, denn der einzige Unterschied ist dass einmal sin und einmal cos genutzt wird, da ist er einzige Unterschied dass du bei einem 90Grad dazurechnen musst um den richtigen Wert zu erhalten. Ich habe früher nur mit FixedPoint Arithmetik gearbeitet und brauchte daher keine Fließkommazahlen. Für Sin/Cos hatte ich eine Look Up Tabelle, musste den Wert also nicht jedes mal neu berechnen, sondern der Winkel war direkt ein Index in einem Array.
Wie man das heute macht, weiß ich nicht. Ich kann nur berichten wie ich das damals auf dem Amiga in Assembler gemacht hatte.
Am besten fängst du einfach mit irgendeinen Assemblerkurs für deinen Microcontroller an. Die Befehle sind simple und Assembler ist viel logischer als C oder C++, denn da hängt es davon ab wie bestimmte Sachen im Standard geregelt sind.
Tja, wie fängt man dann? Wenn man die Assemblerbefehle kennt, dann versucht man wie die CPU zu denken. Ich brauche einen Wert mit dem ich gleich rechnen will? Dann packe ich das in ein CPU Register. Ich brauche einen Wert für später, dann schreibe ich den direkt in eine Speicherstelle wenn nicht mehr genug Register vorhanden sind. Ich will eine Schleife machen? Ich zähle einen Wert im Register auf null runter und springe dann zu einem Label, ist der Wert noch nicht Null, so springe ich dahin wo mein Wert um eins runtergezählt wird und Teste erneut.
Dieses Springen ist wohl leider vielen Programmierern von heute fremd. Früher gab es in Basic Goto und Gosub was man dann in Assembler in der Art auch wieder findet.
Ich hatte große Probleme früher nicht mit Goto und Gosub programmieren zu können, da ich das Konzept von Funktionen nicht kannte. Eine noch größere Hürde war dann die Objekt Orientierte Programmierung, das wollte überhaupt nicht in mein Kopf und auch heute setze ich das nur sehr marginal ein, da ich oft keinen Sinn darin sehe OOP einzusetzen. Ich finde es schrecklich mich durch fremden OOP Code zu wurschteln. Um so mehr abstrahiert wird, umso komplizierter wird es durch zublicken und gute aktuelle Dokumentation habe ich im Arbeitsleben nie erlebt.
-
Also ich weiss nicht, x86 ist für mich nach 68k so ziemlich der einfachste Assembler.
-
Ich fand es damals ziemlich krass, von den 16 Registern auf die wenige vom x86 umzuschwenken. Das war aber alles noch zu 486 Zeiten und mit Code Segment etc. Ich habe das auch nur zwei Wochen probiert und nicht mehr wie so ein Echtzeit Apfelmännchen mit gemacht, dann hatte ich keine Lust mehr auf x86. Bzw dann kam eine kurze Zeit TurboPascal mit Assemblerstücken und irgendwann mal kurz der portable Assembler C. Alles aber nie so intensiv wie damals auf dem Amiga.
Für mein jetziges Projekt werde ich später auch versuchen einige Teile selbst in Assembler zu optimieren, einfach um mal real zu erleben wer besser ist. Ich oder der Compiler, aber wahrscheinlich wird der Compiler gewinnen.
-
Ok, hier nu mein Versuch, zumindest
getx
umzuschreiben:.globl getx .type getx, @function getx: .LFB0: push %ecx push %edx # main.c:7: float w2 = w * (M_PI / 180.0); # main.c:8: x = (int) (r * cos(w2)); # main.c:9: return x; divss %ecx, $0x4048f5c3, $0x43340000 mulss %edx, %ecx, %ebx movs %edx, %xmm0 call cos@PLT movs %xmm0, %edx mulss %eax, %eax, %edx cvttss2si %eax, %eax leave ret
Und gleich auch 1000 Fehlermeldungen bezüglich der 32-bit Register:
main.c: Assembler messages: main.c:42: Error: operand type mismatch for `push' main.c:43: Error: operand type mismatch for `push' main.c:47: Error: number of operands mismatch for `divss' main.c:48: Error: number of operands mismatch for `mulss' main.c:49: Error: operand type mismatch for `movs' main.c:51: Error: operand type mismatch for `movs' main.c:52: Error: number of operands mismatch for `mulss' main.c:53: Error: operand type mismatch for `cvttss2si'
push
gibt es anscheinend für 32-bit nicht unddivss
undmulss
nehmen keine 3 Operanden bei 64-bit. Blöd alles.
-
Du musst sowieso das ganze 64 Bit Register pushen, da im 64 Bit Mode die 32 Bit Befehle die oberen 32 Bit des Ziel-Registers auf 0 setzen. Daher macht
push %eax
im 64 Bit Mode wenig bis gar keinen Sinn.Oder anders gesagt: push einfach das ganze Register.
-
Ok, also alles für den 64 Bit Mode umschreiben.
-
Mal ne Frage am Rande: Muss man diese Prozentzeichen nutzen? Hier in einem Beispiel für InlineAssembler unter VisualStudio sieht das schon sehr viel lesbarer aus.
// Power2_inline_asm.c // compile with: /EHsc // processor: x86 #include <stdio.h> int power2( int num, int power ); int main( void ) { printf_s( "3 times 2 to the power of 5 is %d\n", \ power2( 3, 5) ); } int power2( int num, int power ) { __asm { mov eax, num ; Get first argument mov ecx, power ; Get second argument shl eax, cl ; EAX = EAX * ( 2 to the power of CL ) } // Return with result in EAX }
Quelle:Link Text
-
@chris4cpp sagte in Was tut dieser Assembly Code? (Anfängerfrage):
Mal ne Frage am Rande: Muss man diese Prozentzeichen nutzen?
Lies meinen Text oben.
Intel Syntax: ohne % für Register und ohne $ für Immediates und ohne Längenangabe, dabei bedeutet
mov eax, 0
dasselbe wieeax = 0;
AT&T Syntax: mit % und $. Derselbe Befehl istmovl $0, %eax
- also auch noch mit verdrehten Operanden.Offensichtlich verwendest du in VS Intel Syntax.
(PS: und ja, ich weiß, dass man xor nehmen würde, um ein Register auf 0 zu setzen)
-
Danke noch mal, ja sorry nicht gelesen.
PS: Na ich vermute mal, solche Kniffe wie mit xor brauch man heute nicht mehr. Und Speicher soll wohl auch ein ziemlicher Flaschenhals geworden sein, da dessen Geschwindigkeit nicht so schnell gewachsen ist wie bei den CPUs, also wird es heute wohl weniger LUTs geben als früher, wenn die Berechnung weniger kostet als ein Speicherzugriff. Aber in der Materie bin ich nicht mehr drin.
-
Der Opcode für
xor eax, eax
ist kürzer als der fürmov eax, 0
, daher verwendet man es immer noch. Ich würde sogar sagen dass es so üblich ist, dass es den einen oder anderen geübten Assembler-Leser ein klein bisschen verwirren könntemov eax, 0
zu lesen.BTW:
xor eax, eax
ist auch kürzer alsxor rax, rax
, weswegen manxor eax, eax
auch findet wenn das ganzerax
auf 0 gesetzt werden soll. Was sich wieder die Tatsache zu nutze macht dass bei 32 Bit Befehlen im 64 Bit Mode die oberen 32 Bit des Ziel-Registers auf 0 gesetzt werden.Und Speicher soll wohl auch ein ziemlicher Flaschenhals geworden sein, da dessen Geschwindigkeit nicht so schnell gewachsen ist wie bei den CPUs, also wird es heute wohl weniger LUTs geben als früher, wenn die Berechnung weniger kostet als ein Speicherzugriff.
Was günstiger ist kommt auf viele Faktoren an. Wenn man viele Werte in Folge aus der LUT braucht, die LUT klein genug ist und die Befehle um den Wert direkt zu berechnen teuer genug, dann zahlt sich das schon noch aus. Für eine Multiplikation macht man es aber eher nicht mehr. Für eine Division meist auch nicht, aber für viele in Folge... vielleicht doch.
-
Hier kommt immer eine Gleitkomma-Ausnahme und ich weiß auch nicht, wieso ich
rax
nur relativ adressieren kann:.text .globl getx .type getx, @function getx: .LFB0: push %rax push %rbx push %rcx push %rdx # mov %r8, %rax # mov %r9, %rbx # main.c:7: float w2 = w * (M_PI / 180.0); # main.c:8: x = (int) (r * cos(w2)); # main.c:9: return x; mov $0x4048f5c3, %rax mov $0x43340000, %rbx div %rbx mul %r8 movsd 0(%rax), %xmm0 call cos@PLT movsd %xmm0, 0(%rax) mul %r9 mov 0(%rax), %eax # pop %rax # pop %rbx # pop %rcx # pop %rdx leave ret
Aufruf:
# main.c:24: x = getx(25, i); mov %rbp, %r8 # i, tmp87 mov $25, %r9 #, call getx # movl %eax, -16(%rbp) # tmp88, x
-
Die Ausnahme kommt vermutlich gerade weil du rax "relativ addressierst" - das heisst ja du lädst den Wert für xmm0 aus dem Speicher, und zwar von der Adresse die in rax steht. Und da an der Adresse - wenig überraschend - nix gemappt ist, gibt's aua.
Und warum es nicht "direkt" geht: rax ist ein 64 Bit Skalar-Register, xmm0 ist ein 128 Bit Vektorregister. Wie soll das also gehen? Angenommen es gibt einen Register<->Register mov Befehl der das kann (keinen Ahnung ob es den gibt), dann müsstest du zumindest irgendwo mit angeben was genau er jetzt mit den 64 Bit machen soll. Also wie er die 64 Bit aus rax auf xmm0 "aufteilen" soll.
Bei "indirekt" dagegen nimmt der Compiler an dass xmm0 in seinem normalen Format (also 128 Bit) im Speicher liegt. Und das geht natürlich immer, dabei ist ja keine Konvertierung/Abbildung von Bits nötig.
-
Auch damit geht es nicht (gleiche Fehlermeldung)
getx: .LFB0: push %rax push %rbx push %rcx push %rdx # mov %r8, %rax # mov %r9, %rbx # main.c:7: float w2 = w * (M_PI / 180.0); # main.c:8: x = (int) (r * cos(w2)); # main.c:9: return x; mov $0x4048f5c3, %rax mov $0x43340000, %rbx div %rbx mul %r8 movd %rax, %xmm0 call cos@PLT movd %xmm0, %rax mul %r9 movd %rax, %xmm0 cvttsd2si %xmm0, %eax # pop %rax # pop %rbx # pop %rcx # pop %rdx leave ret