2015-01-27 9 views
10

Ich mache mehrere Experimente mit x86 asm versuchen zu sehen, wie gängige Sprachkonstrukte in Assembly mappen. In meinem aktuellen Experiment versuche ich speziell zu sehen, wie C-Sprachzeiger der registerindirekten Adressierung zugeordnet werden. Ich habe eine ziemlich hallo-Welt wie Zeiger Programm geschrieben:In x86, warum habe ich die gleiche Anweisung zweimal, mit umgekehrten Operanden?

#include <stdio.h> 

int 
main (void) 
{ 
    int value = 5; 
    int *int_val = &value; 

    printf ("The value we have is %d\n", *int_val); 
    return 0; 
} 

und an folgenden asm zusammengestellt werden: gcc -o pointer.s -fno-asynchronous-unwind-tables pointer.c: [1] [2]

 .file "pointer.c" 
     .section  .rodata 
.LC0: 
     .string "The value we have is %d\n" 
     .text 
     .globl main 
     .type main, @function 
main: 
;------- function prologue 
     pushq %rbp 
     movq %rsp, %rbp 
;--------------------------------- 
     subq $32, %rsp 
     movq %fs:40, %rax 
     movq %rax, -8(%rbp) 
     xorl %eax, %eax 
;---------------------------------- 
     movl $5, -20(%rbp) ; This is where the value 5 is stored in `value` (automatic allocation) 
;---------------------------------- 
     leaq -20(%rbp), %rax ;; (GUESS) If I have understood correctly, this is where the address of `value` is 
           ;; extracted, and stored into %rax 
;---------------------------------- 
     movq %rax, -16(%rbp) ;; 
     movq -16(%rbp), %rax ;; Why do I have two times the same instructions, with reversed operands??? 
;---------------------------------- 
     movl (%rax), %eax 
     movl %eax, %esi 
     movl $.LC0, %edi 
     movl $0, %eax 
     call printf 
;---------------------------------- 
     movl $0, %eax 
     movq -8(%rbp), %rdx 
     xorq %fs:40, %rdx 
     je  .L3 
     call __stack_chk_fail 
.L3: 
     leave 
     ret 
     .size main, .-main 
     .ident "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1" 
     .section  .note.GNU-stack,"",@progbits 

Mein Problem ist, dass ich don verstehe nicht, warum es zweimal den Befehl movq enthält, mit umgekehrten Operanden. Kann mir jemand das erklären?

[1]: Ich möchte vermeiden, dass mein asm-Code mit cfi-Anweisungen durchsetzt ist, wenn ich sie überhaupt nicht brauche.

[2]: Meine Umgebung ist Ubuntu 14.10, gcc 4.9.1 (von ubuntu geändert) und Gnu assembler (GNU Binutils for Ubuntu) 2.24.90.20141014, konfiguriert x86_64-linux-gnu

+5

Wenn Sie gcc nicht zur Optimierung auffordern, wird es einen * extrem * dummen Code erzeugen. Wie Sie gesehen haben, speichert es alle lokalen Variablen auf dem Stack, selbst wenn es leicht in einem Register gespeichert werden könnte. Um vernünftigen Code zu erhalten, sagen Sie gcc zur Optimierung mit '-O3'. – EOF

+0

Ich kann nur den EOF-Kommentar wiedergeben. Ich finde, dass "-O" für mich am lesbarsten ist: "-O3" kann Transformationen anwenden, die die Rückverfolgbarkeit des Quellcodes in der Assembly erschweren. Aus dem gleichen Grund, in dem Sie '-fno-asynchronous-unwind-tables' verwendet haben, mögen Sie auch' -fomit-frame-pointer' (für einfache Funktionen ist die resultierende Assembly tatsächlich leichter und einfacher zu folgen). Lesen Sie nicht den Code, der mit '-O0' generiert wurde, es sei denn, Sie haben Zeit zu verschwenden. –

+0

@EOF Ja, ich habe gesehen, was es mit '-O3' macht, und während es sauberer ist, hat es meine Zeiger (und weit mehr) optimiert, und auf diese Weise kann ich verlieren, was ich ursprünglich sehen wollte: Wie Zeigern zuordnen, um indirekte Adressierung zu registrieren. – NlightNFotis

Antwort

9

Vielleicht wird es klarer sein Ziel, wenn Sie Ihre Blöcke reorganisieren:

;---------------------------------- 
    leaq -20(%rbp), %rax  ; &value 
    movq %rax, -16(%rbp)  ; int_val 
;---------------------------------- 
    movq -16(%rbp), %rax  ; int_val 
    movl (%rax), %eax  ; *int_val 
    movl %eax, %esi   ; printf-argument 
    movl $.LC0, %edi   ; printf-argument (format-string) 
    movl $0, %eax   ; no floating-point numbers 
    call printf 
;---------------------------------- 

Die Der erste Block führt int *int_val = &value; aus, der zweite Block führt printf ... aus. Ohne Optimierung sind die Blöcke unabhängig.

4

Da Sie keine Optimierung vornehmen, erstellt gcc sehr einfachen Code, der jede Anweisung im Programm nacheinander ausführt, ohne auf eine andere Anweisung zu schauen. In Ihrem Beispiel speichert es also einen Wert in der Variablen int_val, und dann liest die nächste Anweisung diese Variable erneut als Teil der nächsten Anweisung. In beiden Fällen wird %rax als temporärer Wert verwendet, da dies das erste Register ist, das normalerweise für Dinge verwendet wird.