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));
没有声明新变量。
我想使用 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));
没有声明新变量。