ElBlo

Inline Assembly to force a read from a pointer

How many times have you been surprised by a compiler optimization removing your dead reads? My coworker Roland told me of a neat and portable way of making sure the compiler won’t optimize a way a memory read, without writing any assembly code.

To read a value from a pointer, you can do:

__asm__ volatile("" : "=r" (x) : "0"(*p));

This is equivalent to x = *p;

Explanation:

  • "=r" is the constraint on the output operand (operand number 0), saying that it must be a register.
  • "0" is the constraint on the input operand (operand number 1). It is a matching constraint that says that it must be the same operand as operand number 0.

These constrains tell the compiler that before executing any assembly code, it must provide a register %0 that has the contents of *p in it.

Note that this might not work if p can be the NULL pointer, as the compiler could assume it is undefined behavior. To do that, you can “launder” the pointer beforehand:

    __asm__("" : "=r"(p) : "0"(p));

Here is some sample code and the corresponding generated code:

int force_read(int* p) {
    int x = 3;
    __asm__ volatile("" : "=r" (x) : "0"(*p));
    return x;
}

int force_read_null() {
    int* p = NULL;
    int x = 3;
    __asm__("" : "=r"(p) : "0"(p));
    __asm__ volatile("" : "=r" (x) : "0"(*p));
    return x;
}
force_read:
        mov     eax, DWORD PTR [rdi]
        ret
force_read_null:
        xor     eax, eax
        mov     eax, DWORD PTR [rax]
        ret

© Marco Vanotti 2024

Powered by Hugo & new.css.