-1

I was given a task to convert a certain code from C++ to ASM based on AT&T syntax, so I started with simple examples and encountered the first problem.

code from which I started the exercise

void func() {
  int num = 1;
  std::cout << "before: " << num << std::endl;
  num = num << 3;
  std::cout << "after: " << num << std::endl;
}

which gives a result:

before: 1
after: 8

my translation variable num is first local variable so it should be in address -4(%ebp)

void func() {
  int num = 1;
  std::cout << "before: " << num << std::endl;
  asm (
    "mov -4(%ebp), %eax         \n"
    "sall $3, %eax              \n"
    "mov %eax, -4(%ebp)         \n"
  );
  std::cout << "after: " << num << std::endl;
}

which gives a result:

before: 1
after: 1

why this code has no effect on num var ?

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
ira1denu
  • 1
  • 2
  • I've rolled-back your edit. You should not edit a second question into an existing question, especially if the first question already has (correct) answers. If you have a new problem, open a new question. – JHBonarius Dec 17 '20 at 19:36

1 Answers1

2

The code you are writing is very implementation specific. In your case the code likely doesn't work because you are using ebp which is a 32-bit addressing register, while you are running on a 64-bit machine, which uses rbp.

HOWEVER you are approaching this incorrectly. Either you write pure assembly, or you use the correct (extended) C inline assembly, that correctly interfaces with local variables. Else the code will break as soon as you change something, as you have experienced yourself.

according to this answer the inline asm should look like:

asm ( "assembly code"
    : output operands                  /* optional */
    : input operands                   /* optional */
    : list of clobbered registers      /* optional */
);

so your code could look like

asm (
    "mov %0, %%eax;"
    "sal $3, %%eax;"
    "mov %%eax, %1;"
    :"=r" (num)
    :"r" (num)
    :"%eax"
);

However, you don't need to specify and use eax, thus the code could be simplified (and clarified) to

asm (
    "sal $3, %[num];"
    :[num] "+r" (num)
);
JHBonarius
  • 10,824
  • 3
  • 22
  • 41
  • The conversion of ebp to rbp helped me solve my first problem thank you. – ira1denu Dec 17 '20 at 19:33
  • 1
    This might happen to work given the surrounding code that the OP's compiler happened to generate, but is *completely* unsafe. Modifying registers without telling the compiler about it is effectively undefined behaviour. Never use GNU C Basic asm (no out / in / clobber constraints) inside a non-naked function, only at global scope. See https://gcc.gnu.org/wiki/ConvertBasicAsmToExtended. This is bad answer because it endorses using inline asm completely wrong, without even mentioning the fundamental flaw in how the OP is trying to do this. https://stackoverflow.com/tags/inline-assembly/info – Peter Cordes Dec 18 '20 at 05:36
  • 1
    That's why this breaks when the surrounding code changes. (Or even if you compile with optimization enabled.) It depends on massively amounts of assumptions in GCC's code-gen, including debug-mode everything-is-volatile and that registers are "dead" between statements. And about where GCC put the variable on the stack. – Peter Cordes Dec 18 '20 at 05:39
  • @PeterCordes ok, better like this? (I'm also learning here) – JHBonarius Dec 18 '20 at 08:24
  • 1
    Better; your first version is safe but uses EAX for no reason after forcing the compiler to give you the value in a reg (instead of its choice of reg/mem/immediate with "g"). Your final version fails to read from %1, instead blindly assuming that the compiler will pick the same register for %1 as for %0, even though you gave if the choice of any register with `"r"`. To force the same reg, use `"0"` (where 0 is the operand-number to match). Or more simply, just use a single read-write operand (which behind the scenes invents a matching input constraint). `asm("sal $3, %0" : "+r"(num));` – Peter Cordes Dec 18 '20 at 08:43
  • 1
    The compiler might choose to use your asm statement as a copy-and-operate and pick different regs e.g. if it was a function body with a register arg in EDI and wanting the return value in EAX. Or if it still wanted a copy of the old value, e.g. because of `int foo = num` before the asm statement. So it would be easy to construct a case where that breaks, once you know what weak spot to poke at. – Peter Cordes Dec 18 '20 at 08:46
  • 1
    Timothy's edit made me look again at the middle code block. In that case (if you're going to force 2 `mov` instructions instead of `sal $3, %0` like the syntax is designed for), you *should* be using 2 separate input and output operands, letting the compiler use the asm statement as a copy-and-shift. Generally only force in-place if that saves instructions. But there's no reason to write it with 2 `mov` instructions inside the asm template; let the compiler emit one mov if necessary to preserve the input. So just showing that as a non-optimized baby-step from orig to good is ok. – Peter Cordes Dec 22 '20 at 00:40
  • @peter should I then also use %0 and %1 or does %0 alone work? – JHBonarius Dec 22 '20 at 06:30
  • 1
    You should of course use `%0` when you mean that operand and `%1` when you mean the other operand. Unless you force the compiler to pick the same register for both `"=r"` output and `"r"` input, they *can* be different. My entire point was to *let* them be difference if the compiler chooses. It can of course choose to make them the same register (if you don't use an `"=&r"` early-clobber output, which you shouldn't because you read all the inputs for the last time before writing that output for the first time.) And often will, especially in debug builds; testing doesn't prove correctness. – Peter Cordes Dec 22 '20 at 06:36