从机器的角度理解汇编程序中的指针

C0D*_*3IL 1 c assembly stack gcc pointers

这是我在 godbolt 编译器上编写的一个基本程序,它很简单:

#include<stdio.h>

void main()
{
    int a = 10;
    int *p = &a;
    printf("%d", *p);
}
Run Code Online (Sandbox Code Playgroud)

编译后我得到的结果:

.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-12], 10
        lea     rax, [rbp-12]
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        nop
        leave
        ret
Run Code Online (Sandbox Code Playgroud)

问题:推送 rbp,通过创建 16 字节块来创建堆栈帧,如何从寄存器将值移动到堆栈位置,反之亦然,LEA 的工作是如何计算出地址,我得到了这部分。

问题:

            lea     rax, [rbp-12]
            mov     QWORD PTR [rbp-8], rax
            mov     rax, QWORD PTR [rbp-8]
            mov     eax, DWORD PTR [rax]
Run Code Online (Sandbox Code Playgroud)

Lea -> 将 rbp-12 的地址放入 rax,然后将 rbp-12 的地址值移入 rax,但下一行又说,移至 rax,即 rbp-8 的值。这似乎含糊不清。然后再次将 rax 的值移至 eax。我不明白这里的工作量。为什么我做不到

        lea     rax, [rbp-12]
        mov     QWORD PTR [rbp-8], rax
        mov     eax, QWORD PTR [rbp-8]
Run Code Online (Sandbox Code Playgroud)

就这样结束了吗?因为在原始行中,rbp-12 的地址存储到 rax,然后 rax 存储到 rbp-8。然后rbp-8再次存储到rax中,然后rax再次存储到eax中?我们不能直接将 rbp-8 复制到 eax 吗?我猜不会。但我的问题是为什么?

我知道指针中存在取消引用,所以我了解 LEA 如何帮助抓取 rbp-12 的地址,但在接下来的部分中,它何时从我完全丢失的地址中抓取值。而且,在那之后,我不明白任何汇编代码。

Fre*_*son 5

您会看到非常未优化的代码。这是我逐行解释:

.LC0:
        .string "%d"                     ; Format string for printf
main:
        push    rbp                      ; Save original base pointer
        mov     rbp, rsp                 ; Set base pointer to beginning of stack frame
        sub     rsp, 16                  ; Allocate space for stack frame
        mov     DWORD PTR [rbp-12], 10   ; Initialize variable 'a'
        lea     rax, [rbp-12]            ; Load effective address of 'a'
        mov     QWORD PTR [rbp-8], rax   ; Store address of 'a' in 'p'
        mov     rax, QWORD PTR [rbp-8]   ; Load 'p' into rax (even though it's already there - heh!)
        mov     eax, DWORD PTR [rax]     ; Load 32-bit value of '*p' into eax
        mov     esi, eax                 ; Load value to print into esi
        mov     edi, OFFSET FLAT:.LC0    ; Load format string address into edi
        mov     eax, 0                   ; Zero out eax (not sure why -- likely printf call protocol)
        call    printf                   ; Make the printf call
        nop                              ; No-op (not sure why)
        leave                            ; Remove the stack frame
        ret                              ; Return
Run Code Online (Sandbox Code Playgroud)

编译器在不优化时,会在解析您提供的代码时生成这样的代码。它做了很多不必要的事情,但生成速度更快,并且使调试器的使用更容易。

将其与优化代码 (-O2) 进行比较:

.LC0:
        .string "%d"                   ; Format string for printf
main:
        mov     esi, 10                ; Don't need those variables -- just a 10 to pass to printf!
        mov     edi, OFFSET FLAT:.LC0  ; Load format string address into edi
        xor     eax, eax               ; It's a few cycles faster to xor a register with itself than to load an immediate 0
        jmp     printf                 ; Just jmp to printf -- it will handle the return
Run Code Online (Sandbox Code Playgroud)

优化器发现这些变量不是必需的,因此不会创建堆栈帧。除了电话,什么都没有留下printf!这是作为 a 完成的jmp,因为完成后这里不需要做任何其他事情printf