10
votes

I am doing several experiments with x86 asm trying to see how common language constructs map into assembly. In my current experiment, I am trying to see specifically how C language pointers map to register-indirect addressing. I have written a fairly hello-world like pointer program:

#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;
}

and compiled it to the following asm using: 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

My issue is that I don't understand why it contains the instruction movq two times, with reversed operands. Could someone explain it to me?

[1]: I want to avoid having my asm code interspersed with cfi directives when I don't need them at all.

[2]: My environment is Ubuntu 14.10, gcc 4.9.1 (modified by ubuntu), and Gnu assembler (GNU Binutils for Ubuntu) 2.24.90.20141014, configured to target x86_64-linux-gnu

2
If you don't tell gcc to optimize, it will produce extremely stupid code. As you've seen, it stores all local variables on the stack, even if it could easily be kept in a register. To get sensible code, tell gcc to optimize with -O3.EOF
I can only echo EOF's comment. I find that -O is the most readable for me: -O3 can apply transforms that impede traceability of source code in the assembly. For the same reason you used -fno-asynchronous-unwind-tables, you may also like -fomit-frame-pointer (for simple functions, the resulting assembly is in fact lighter and easier to follow). Don't read code generated with -O0 unless you have time to waste.Pascal Cuoq
@EOF yeah, I've seen what it does with -O3, and while it's cleaner, it has optimized out my pointers (and far more), and this way I get to lose what I wanted to see originaly: How pointers map to register indirect addressing.NlightNFotis
if you want variables to not be optimized out, you can also use -Ogphuclv

2 Answers

9
votes

Maybe it will be clearer if you reorganize your blocks:

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

The first block performs int *int_val = &value;, the second block performs printf .... Without optimization, the blocks are independent.

4
votes

Since you're not doing any optimization, gcc creates very simple-minded code that does each statement in the program one at a time without looking at any other statement. So in your example, it stores a value into the variable int_val, and then the very next instruction reads that variable again as part of the next statement. In both cases, it is using %rax as the temporary to hold value, as that's the first register generally used for things.