c 和 Assembly 的指针运算

Pointer arithmetic with c and Assembly

我想使用 Assembly 和 C 访问内存中的特定位置。

我创建了以下结构:

struct node{
    uint64_t x[5];
    uint64_t y;
    struct node * next;
};

后来,我创建了一个该类型的对象。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

struct node{
    uint64_t x[5];
    uint64_t y;
    struct node * next;
};

void foo();

struct node * ptr;

int main(){
    
    struct node* ptr = (struct node *) malloc(sizeof(struct node));
    ptr->next = NULL;

    foo();

    printf("%lu\n", ptr->y);

    return 0;
}

现在,在程序集上,我想更改 y 的值。

    .section .data
    
    .text
    .globl  foo
    .extern ptr

foo:

    //access ptr->y
    leaq (ptr + 40), %r12
    movq , %r12
    
    ret

我希望 %r12 具有 ptr->y 的地址。我想象它会得到正确的地址,因为 ptr.x 将在内存中排在第一位,并且它的权重为 8*5 = 40 字节,但事实并非如此。

首先,选择与 %r12 不同的寄存器。在 Linux 和大多数其他类 Unix 系统上使用的 x86-64 ABI(我假设您正在使用,因为这是 GNU 汇编器的主要目标),%r12 is a "callee-saved" register,这意味着当编译器调用您的函数 foo,它希望 %r12 保持其值。您可以在函数的开头和结尾压入和弹出 %r12,但更简单的方法是选择一个不同的寄存器,它是“调用者保存的”,编译器假定它的值可以改变。我将在下面使用 %rax

您需要从内存加载才能将 ptr 的值存入寄存器。你的 leaq 不会那样做;它以指针的 地址 加上 %r12 中的 40 结束,并且不从内存中读取。您需要一个带有内存源操作数的 mov

然后,你需要做一个存储来实际写入 ptr 指向的地址(加上 40)。您当前的 movq 仅将数字 42 放入 %r12 寄存器并且根本不修改内存。

尝试

    .text
    .globl  foo
    .extern ptr

foo:

    movq ptr, %rax  # no $ means this is a load from memory
    movq , 40(%rax)
    ret

如果尝试构建与位置无关的可执行文件(默认为 64 位 Linux),您需要 rip-relative 寻址来访问全局变量。在这种情况下,将 movq ptr, %rax 替换为 movq ptr(%rip), %rax


您有一个单独的问题,您在 main 中声明了一个名为 ptr 的局部变量,它隐藏了具有相同名称的全局变量。 malloc 的结果赋值给局部变量,而全局变量保持等于 NULL。函数 foo 将访问全局函数,因此它将取消引用 NULL 并崩溃。你应该改变

    struct node* ptr = (struct node *) malloc(sizeof(struct node));

简单地

    ptr = (struct node *) malloc(sizeof(struct node));

没有声明新变量。