Frage zu Assembler und der Syntax



  • Erhard, bist Du jetzt AT&T Syntax Fan?

    Ehrlich gesagt finde ich beides, sowohl Intel als auch AT&T Syntax, nicht eindeutig und eingängig genug. Daher bleibe ich einfach zwischen beiden unentschieden und versuche beides zu verstehen und zu nutzen. Mir geht es momentan darum, den Übergang von C nach Assembler und das Wechselspiel zwischen beiden wirklich zu verstehen. Denn dies ist für eigene Betriebssystementwicklung incl. Bootloader - ob man will oder nicht - notwendig.

    Ich habe deinen Tipp aufgenommen und ausprobiert, allerdings mit version 4.4.1 (in code::blocks 10.05), sehr interessant:

    .file	"main.c"
     # GNU C (TDM-2 mingw32) version 4.4.1 (mingw32)
     #	compiled by GNU C version 4.4.1, GMP version 4.3.0, MPFR version 2.4.1.
     # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
     # options passed:  -iprefix
     # c:\programme\codeblocks\mingw\bin\../lib/gcc/mingw32/4.4.1/
     # C:\temp\step1\main.c -mtune=i386 -auxbase-strip obj\Debug\main.o -g -g0
     # -O3 -Wall -fverbose-asm -fomit-frame-pointer
     # options enabled:  -falign-loops -fargument-alias -fauto-inc-dec
     # -fbranch-count-reg -fcaller-saves -fcommon -fcprop-registers
     # -fcrossjumping -fcse-follow-jumps -fdefer-pop
     # -fdelete-null-pointer-checks -fearly-inlining
     # -feliminate-unused-debug-types -fexpensive-optimizations
     # -fforward-propagate -ffunction-cse -fgcse -fgcse-after-reload -fgcse-lm
     # -fguess-branch-probability -fident -fif-conversion -fif-conversion2
     # -findirect-inlining -finline -finline-functions
     # -finline-functions-called-once -finline-small-functions -fipa-cp
     # -fipa-cp-clone -fipa-pure-const -fipa-reference -fira-share-save-slots
     # -fira-share-spill-slots -fivopts -fkeep-static-consts
     # -fleading-underscore -fmath-errno -fmerge-constants
     # -fmerge-debug-strings -fmove-loop-invariants -fomit-frame-pointer
     # -foptimize-register-move -foptimize-sibling-calls -fpeephole -fpeephole2
     # -fpredictive-commoning -freg-struct-return -fregmove -freorder-blocks
     # -freorder-functions -frerun-cse-after-loop -fsched-interblock
     # -fsched-spec -fsched-stalled-insns-dep -fsigned-zeros
     # -fsplit-ivs-in-unroller -fsplit-wide-types -fstrict-aliasing
     # -fstrict-overflow -fthread-jumps -ftoplevel-reorder -ftrapping-math
     # -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-copy-prop
     # -ftree-copyrename -ftree-cselim -ftree-dce -ftree-dominator-opts
     # -ftree-dse -ftree-fre -ftree-loop-im -ftree-loop-ivcanon
     # -ftree-loop-optimize -ftree-parallelize-loops= -ftree-pre -ftree-reassoc
     # -ftree-scev-cprop -ftree-sink -ftree-sra -ftree-switch-conversion
     # -ftree-ter -ftree-vect-loop-version -ftree-vectorize -ftree-vrp
     # -funit-at-a-time -funswitch-loops -fvect-cost-model -fverbose-asm
     # -fzero-initialized-in-bss -m32 -m80387 -m96bit-long-double
     # -maccumulate-outgoing-args -malign-double -malign-stringops
     # -mfancy-math-387 -mfp-ret-in-387 -mfused-madd -mieee-fp -mno-red-zone
     # -mno-sse4 -mpush-args -msahf -mstack-arg-probe
    
     # Compiler executable checksum: cf55188e7d2f640bec71afc62bf8d59d
    
    	.text
    	.p2align 2,,3
    .globl _doSomething
    	.def	_doSomething;	.scl	2;	.type	32;	.endef
    _doSomething:
    /APP
     # 4 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
     # 6 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
    /NO_APP
    	movl	4(%esp), %eax	 # val1, val1
    	addl	8(%esp), %eax	 # val2, tmp63
    	addl	12(%esp), %eax	 # val3, tmp62
    	ret
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.p2align 2,,3
    .globl _main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	pushl	%ebp	 #
    	movl	%esp, %ebp	 #,
    	andl	$-16, %esp	 #,
    	call	___main	 #
    /APP
     # 4 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
     # 6 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
    /NO_APP
    	xorl	%eax, %eax	 #
    	leave
    	ret
    

    Hier ohne -O3: mingw32-gcc -S tmp.c -g0 -fverbose-asm -fomit-frame-pointer

    .file	"main.c"
     # GNU C (TDM-2 mingw32) version 4.4.1 (mingw32)
     #	compiled by GNU C version 4.4.1, GMP version 4.3.0, MPFR version 2.4.1.
     # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
     # options passed:  -iprefix
     # c:\programme\codeblocks\mingw\bin\../lib/gcc/mingw32/4.4.1/
     # C:\temp\step1\main.c -mtune=i386 -auxbase-strip obj\Debug\main.o -g -g0
     # -Wall -fverbose-asm -fomit-frame-pointer
     # options enabled:  -falign-loops -fargument-alias -fauto-inc-dec
     # -fbranch-count-reg -fcommon -fearly-inlining
     # -feliminate-unused-debug-types -ffunction-cse -fgcse-lm -fident
     # -finline-functions-called-once -fira-share-save-slots
     # -fira-share-spill-slots -fivopts -fkeep-static-consts
     # -fleading-underscore -fmath-errno -fmerge-debug-strings
     # -fmove-loop-invariants -fomit-frame-pointer -fpeephole
     # -freg-struct-return -fsched-interblock -fsched-spec
     # -fsched-stalled-insns-dep -fsigned-zeros -fsplit-ivs-in-unroller
     # -ftrapping-math -ftree-cselim -ftree-loop-im -ftree-loop-ivcanon
     # -ftree-loop-optimize -ftree-parallelize-loops= -ftree-reassoc
     # -ftree-scev-cprop -ftree-switch-conversion -ftree-vect-loop-version
     # -funit-at-a-time -fvect-cost-model -fverbose-asm
     # -fzero-initialized-in-bss -m32 -m80387 -m96bit-long-double
     # -maccumulate-outgoing-args -malign-double -malign-stringops
     # -mfancy-math-387 -mfp-ret-in-387 -mfused-madd -mieee-fp -mno-red-zone
     # -mno-sse4 -mpush-args -msahf -mstack-arg-probe
    
     # Compiler executable checksum: cf55188e7d2f640bec71afc62bf8d59d
    
    	.text
    .globl _doSomething
    	.def	_doSomething;	.scl	2;	.type	32;	.endef
    _doSomething:
    	subl	$16, %esp	 #,
    	movl	$0, 12(%esp)	 #, value
    /APP
     # 4 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
    /NO_APP
    	movl	24(%esp), %eax	 # val2, tmp61
    	movl	20(%esp), %edx	 # val1, tmp62
    	leal	(%edx,%eax), %eax	 #, D.1240
    	addl	28(%esp), %eax	 # val3, tmp63
    	movl	%eax, 12(%esp)	 # tmp63, value
    /APP
     # 6 "C:\temp\step1\main.c" 1
    	nop
     # 0 "" 2
    /NO_APP
    	movl	12(%esp), %eax	 # value, D.1241
    	addl	$16, %esp	 #,
    	ret
    	.def	___main;	.scl	2;	.type	32;	.endef
    .globl _main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	pushl	%ebp	 #
    	movl	%esp, %ebp	 #,
    	andl	$-16, %esp	 #,
    	subl	$16, %esp	 #,
    	call	___main	 #
    	movl	$-14, 8(%esp)	 #,
    	movl	$-12, 4(%esp)	 #,
    	movl	$42, (%esp)	 #,
    	call	_doSomething	 #
    	movl	$0, %eax	 #, D.1244
    	leave
    	ret
    

    Kannst Du die Version ohne Aufruf der Funktion bitte erläutern? Wo werden dort die Parameter addiert?



  • Die Funktion ist ja bereits vom Compiler kommentiert 🙂 Kommentare beginnen mit dem #-Zeichen:

    .text 
        .p2align 2,,3 
    .globl _doSomething 
        .def    _doSomething;    .scl    2;    .type    32;    .endef 
    _doSomething: 
    /APP 
     # 4 "C:\temp\step1\main.c" 1 
        nop 
     # 0 "" 2 
     # 6 "C:\temp\step1\main.c" 1 
        nop 
     # 0 "" 2 
    /NO_APP 
        movl    4(%esp), %eax     # val1, val1 
        addl    8(%esp), %eax     # val2, tmp63 
        addl    12(%esp), %eax     # val3, tmp62 
        ret
    

    Ausschnitt der Stelle, wo die Argumente aufaddiert werden:

    movl    4(%esp), %eax     # val1, val1 
        addl    8(%esp), %eax     # val2, tmp63 
        addl    12(%esp), %eax     # val3, tmp62
    

    movl steht für "move long", long ist 32-Bit
    addl steht für "add long"
    val1, val2 und val3 (in den Kommentaren) stammen direkt aus der main.c:

    int doSomething(int val1, int val2, int val3)
    

    tmp63 und tmp62 sind Zwischenergebnisse, vom Compiler automatisch generiert:
    tmp63 = val1 + val2
    tmp62 = tmp63 + val3

    Erhard Henkes schrieb:

    Ehrlich gesagt finde ich beides, sowohl Intel als auch AT&T Syntax, nicht eindeutig und eingängig genug. Daher bleibe ich einfach zwischen beiden unentschieden und versuche beides zu verstehen und zu nutzen.

    Ich denke, die Überzeugung und Zuneigung zur AT&T Syntax und GNU Assembler kommt noch 🙂
    Ich denke, wenn man einen Assembler als Funktion betrachtet, die eingegebene Befehle in die Maschinenbefehle übersetzt/transformiert/abbildet/was auch immer, dann gilt folgendes:
    GNU Assembler ist bijektiv 👍
    Rest ist surjektiv, hm, tja, bleibt nichts anderes übrig: 👎
    🙂



  • doSomething wird gar nicht aufgerufen

    Meine Frage war: wie wird denn addiert, wenn doSomething bei dir nirgends aufgerufen wird? Da stimmt doch was nicht.



  • Es wurde wegoptimiert, weil das Ergebnis der Funktion niergends im Programm verwendet wird... ausser der NOPs, weil man diese mit asm("") eingefügt hat und Code in asm("") wird vom Compiler ohne weiteres übernommen, weil es wird optimistisch davon ausgegangen, dass der Programmierer weiss, was er da mit asm("") macht 🙂



  • abc.w schrieb:

    ...
    Ausschnitt der Stelle, wo die Argumente aufaddiert werden:

    movl    4(%esp), %eax     # val1, val1 
        addl    8(%esp), %eax     # val2, tmp63 
        addl    12(%esp), %eax     # val3, tmp62
    

    movl steht für "move long", long ist 32-Bit
    addl steht für "add long"
    ...

    Mir kommt das Grausen, die Syntax (links Displacement und Quelle, rechts Ziel) brrr...

    Ich bleib bei der Intel-Syntax: mov Ziel, Quelle

    mov    eax, [esp+4]     ; eax ist 32-Bit also braucht man nicht  
                                    ; mov eax, dword ptr [esp+4] schreiben. 
            add    eax, [esp+8]
            add    eax, [esp+12]
    

    😉



  • Birne schrieb:

    Mir kommt das Grausen, die Syntax (links Displacement und Quelle, rechts Ziel) brrr...

    Ich frage mich, warum 😕 Man lernt seit dem ersten Schuljahr, von links nach rechts zu lesen und plötzlich kommt einem das Grausen, weil es richtig da steht und man doch von recht nach links lesen möchte 😕



  • Die Reihenfolge opcode source, destination ist sicher intuitiver als der umgekehrte Weg. Das spricht für AT&T. Mich stören die unverständlichen % bzw. %% und auch so etwas: 4(%esp)

    Wir können es aber nicht ändern, also sollten wir uns an beides gewöhnen. NASM verwendet standardisiert Intel syntax, und GCC liebt AT&T. Also bleibt nur volle Akzeptanz beider Methoden.



  • Mit dem %-Zeichen wollte man wahrscheinlich zwischen den Registern und Variablen unterscheiden. Man kann z.B. eine Variable eax anlegen und das Register %eax in der Variable eax speichern:

    .section .bss
    eax: .int 0    # Variable eax
    
    .section .text
        movl %eax, eax    # Register %eax speichern in der Variable eax
    

    Sprich, Register sind was besonderes und verdienen das %-Zeichen 🙂
    Die Kombination %% muss man nur im Inline-Assembler von gcc verwenden, warum auch immer. Weiss es jemand, warum?
    Vielleicht, weil die Inline-Assembler Strings so was wie printf Formatstrings sind... Bei printf muss man ja auch %d, %u, %X usw. verwenden und speziell bei % muss man %% eingeben:

    printf("abc.w hat 100%% Recht!\n");
    


  • abc.w schrieb:

    Man lernt seit dem ersten Schuljahr, von links nach rechts zu lesen ...

    Genau!

    eax = 5           ->  mov eax, 5
    ebp = esp         ->  mov ebp, esp
    eax = eax - ebx   ->  sub eax, ebx
    eax = ebx * 1234  ->  imul eax, ebx, 1234
    

    ⚠ 🙂



  • @merker: das hat auch was 😉

    Also wie gesagt, schluss mit dem nutzlosen intel vs. at&t.



  • Möchte nur zum Schluss eine Anmerkung machen, dass es diese Art der Syntax (mit Zuweisungen usw.) in der Praxis wirklich gibt. Bei Blackfin DSPs. Siehe z.B. hier: http://docs.blackfin.uclinux.org/doku.php?id=simple_hello_world_application_example_asm

    🙂


Anmelden zum Antworten