0

I'm trying to put the value of %rbp into the %rax register using __asm__ so that I can get a reference to the stack frame that won't be changed when I create an external variable.

I'm using the following code to do this:

int some_variadic_function(int n, ...)
{
    __asm__("movl %rax, %rbp");
    return 0;   
}


int main() {
    some_variadic_function(3, 1, 2, 3);
    return 0;
}

However, when I try and compile this with clang I get the following error:

using_rbp.c:4:13: error: invalid operand for instruction
    __asm__("movl %rax, %rbp");
            ^
<inline asm>:1:7: note: instantiated into assembly here
        movl %rax, %rbp
             ^~~~~
1 error generated.

This seems to be implying that I can't put %rbp directly into %rax. Is that's what happening, or is there another issue with this code?

For reference, I'm on Mac with x86-64 hardware.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Connor
  • 867
  • 7
  • 18
  • 10
    Size mismatch. `movl` is 32-bit, so needs `%eax`, and `%ebp` as operands. `movq` will take the 64-bit registers. – Erik Eidt Apr 20 '23 at 18:12
  • Oh weird! When I looked it up it said `movl` was for 64bit. Am I completely wrong, or does it differ? – Connor Apr 20 '23 at 22:15
  • 2
    You are completely wrong. AT&T size suffixes are unambiguous for integer operands, and `l` means 32-bit. (For x87 FP, unfortunately `flds` is 32-bit single, `fldl` is 64-bit double.) For `movl`, the `l` definitely means 32-bit operand-size. When a register implies the size, just omit the size override like Intel syntax does, it's a waste of typing. (Except maybe with inline asm where you're using `%0` or `%[var]` operands that the compiler will expand, then it's a good check that your operands have the type-width you intended.) – Peter Cordes Apr 21 '23 at 01:48

1 Answers1

3

You have made a whole bunch of mistakes. Let's take them one by one:

  1. In AT&T assembly syntax for x86, if you provide a size suffix on the instruction, it must match the sizes inferred from the operands. Size suffixes are almost always unnecessary for x86 (the only case I can think of, offhand, where they're needed is when the source operand is an immediate value and the destination is memory): I recommend leaving them out unless the assembler complains about their absence.

  2. In AT&T assembly syntax, the destination comes second; mov %rax, %rbp copies RAX into RBP, not the other way around.

  3. In GNU-style inline assembly, % begins an escape sequence (like in printf); to write a literal register name you have to double the percent sign. This is why you are getting an error from clang, rather than from the assembler.

  4. GNU-style inline assembly must be annotated with "operand constraints" so the compiler understands what you are doing. You cannot simply move a value into RAX and expect that to become the return value. See https://gcc.gnu.org/onlinedocs/gcc-11.3.0/gcc/Using-Assembly-Language-with-C.html -- read all of this manual section, not just the summary page I linked to.

  5. In the 64-bit System V ABI for x86, %rbp is an ordinary general-purpose register, not a "frame pointer".

The correct way to do what you were trying to do is

long some_variadic_function(int n, ...)
{
    long rv;
    __asm__("mov %%rbp, %0" : "=r" (rv));
    return rv;
}

However, this must be compiled with -fno-omit-frame-pointer or the return value will be garbage.

You are probably suffering from the infamous XY problem. Ask a new question about the thing you were originally trying to do, not about the details of your attempted solution to the problem, and we can probably be more helpful.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • I had a different error before that using `movl` fixed, so I assumed that wasn't the issue! Thank you, changing to `mov` let it compile. You're right, this is an XY, problem, I've posted a question about it previously here: https://stackoverflow.com/questions/76028121/can-i-get-rsp-with-inline-assembly. Basically, I want to access stack arguments without using `varg` and any `built_ins`. It's an exercise on codewars that supposed to teach you about stack frames. – Connor Apr 20 '23 at 22:22
  • 1
    I think my answer to the other question is "write the entire function by hand in assembly language in a `.s` file, the compiler is only getting in your way here". And that would also mean you could use Intel syntax if you want. – zwol Apr 20 '23 at 23:42
  • 3
    *I recommend leaving them out unless the assembler complains about their absence.* - Beware that until the last couple years, GAS would assemble `add $1, (%rdi)` without complaint as `addl`. Now it still just warns, not errors, unless you use Intel-syntax mode. Only for `mov $1, (%rdi)` does it error on the ambiguous operand-size, any other operation uses 32-bit as a default operand size. I think I read once that historical Unix assemblers had this default, so I guess that's why GAS only errors on ambiguity in Intel-syntax mode. (Clang is better, erroring on ambiguity even in AT&T mode.) – Peter Cordes Apr 21 '23 at 01:57
  • @zwol The problem is, it has to be in C, because it's being sent to an external server for compilation in C. I'm not allowed to add source files, it's a single file! From the discussions I've had, this is a silly thing to do, however, I am learning a lot... so perhaps it's not so silly! – Connor Apr 21 '23 at 08:13
  • 2
    @Connor: You can write whole functions in asm by putting an `asm("...")` statement *at global scope*, outside any function. The "template string" in that case should define a label like `asm("foo: lea (%rdi, %rsi, 2), %eax; ret");`, and the C should declare a prototype for the function like `int foo(int, int);`. (No need to use `.globl foo` since it only needs to be visible within this one file.) – Peter Cordes May 05 '23 at 00:00