0

I want to access a specific position on memory using Assembly and C.

I've create the following struct:

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

Later, I created a object of that type.

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

Now, on Assembly, I want to change the value of y.

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

foo:

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

I want %r12 to have the address of ptr->y. I imagined that it would get the correct address, because ptr.x would be first in memory and it weights 8*5 = 40 bytes, however that's not the case.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • @harold it's missing the 'ret' at the end. I guess the correct would be movq $42, 0(%r12), right? –  Jun 09 '21 at 20:05
  • 1
    I suggest to fill the structure with data that is easy to recognize before calling `foo()`. Run your program in a debugger step-by-step and check the register and memory that holds the structure.. – Bodo Jun 09 '21 at 20:07
  • Instead of a global variable it might be better design to pass the pointer as paramter to `foo`. – Aconcagua Jun 09 '21 at 20:47

1 Answers1

5

First, choose a different register from %r12. In the x86-64 ABI used on Linux and most other Unix-like systems (which I assume you are using, since that's the main target for the GNU assembler), %r12 is a "callee-saved" register, which means that when the compiler calls your function foo, it expects %r12 to keep its value. You could push and pop %r12 at the start and end of your function, but it's simpler just to choose a different register which is "caller-saved", for which the compiler assumes its value could change. I'll use %rax below.

You need a load from memory to get the value of ptr into a register. Your leaq won't do that; it ends up with the address of pointer, plus 40, in %r12, and does not read from memory. You instead need a mov with a memory source operand.

Then, you need to do a store to actually write to the address that ptr points to (plus 40). Your current movq only puts the number 42 into the %r12 register and doesn't modify memory at all.

Try

    .text
    .globl  foo
    .extern ptr

foo:

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

If trying to build a position-independent executable (default for 64-bit Linux), you need rip-relative addressing to access a global variable. In that case, replace movq ptr, %rax by movq ptr(%rip), %rax.


You have a separate problem in that you declare a local variable named ptr in main, which shadows the global variable with the same name. The result of malloc is assigned to the local variable, while the global one stays equal to NULL. The function foo will access the global one, so it'll dereference NULL and crash. You should change

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

to simply

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

which does not declare a new variable.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Thanks for answering! I didn't notice that I declared struct node on main again. Nice catch! I wanted to put the address of ptr->y on %r12. Is it possible? –  Jun 09 '21 at 20:49
  • @LuizSantos: You can, but it will take an extra instruction and be less efficient: `movq ptr, %r12 ; addq $40, %r12 ; movq $42, (%r12)`. There isn't any instruction that will load a value from memory, do arithmetic on it, and keep it in a register. – Nate Eldredge Jun 09 '21 at 21:13
  • @LuizSantos: BTW, you should *always* use RIP-relative addressing for static variables in 64-bit code, just like a compiler, because that's more efficient. The only reason not to is when indexing a global array with another register(s), or [when putting a symbol address into a register](https://stackoverflow.com/questions/57212012/how-to-load-address-of-function-or-label-into-register). `movq ptr, %r12` will work in a non-PIE executable on Linux, though. (R12 is call-preserved, so the compiler-generated code that calls this will expect R12 to be unmodified, though.) – Peter Cordes Jun 10 '21 at 06:31
  • In GAS, `.extern ptr` does nothing; that's already the default for undeclared symbols. I guess it doesn't hurt as documentation, though. – Peter Cordes Jun 10 '21 at 06:32
  • @PeterCordes: Good spot with r12 being call preserved. I updated my code to fix that. – Nate Eldredge Jun 10 '21 at 15:15