Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
195 views
in Technique[技术] by (71.8m points)

c++ - How can one protect a register from being overwritten during a function call?

I am trying to write assembly code for the following function:

#include <iostream>
void f(int x) {
    if (x > 0) {
        std::cout << x << std::endl;
        f(x-1);
        std::cout << x << std::endl;
    }
}
int main() {
    f(1);
}

The output of this function script is 1 1. I try to write the assembly code for the so-called "low-cost computer" assembler, a computer invented by Anthony Dos Reis for his book "C and C++ under the hood". The assembly code I wrote is:

startup   jsr main
          halt             ; back to operating system
;==============================================================
                           ; #include <stdio.h>
greater   dout
          nl
          sub r1, r0, 1
          push lr
          push fp
          mov fp, sp
          push r1
          jsr f
          add sp, sp, 1
          dout
          nl
          mov sp, fp
          pop fp
          pop lr
          ret
;==============================================================
f         push lr          ; int f()
          push fp          ; {
          mov fp, sp
          ldr r0, fp, 2
          cmp r0, 0
          brgt greater
          mov sp, fp
          pop fp
          pop lr
          ret
;==============================================================
main      push lr
          push fp
          mov fp, sp
          mov r0, 1
          push r0
          jsr f
          add sp, sp, 1
          mov sp, fp
          pop fp
          pop lr
          ret

The code prints 1 0 to stdout, and is obviously false. The reason for the false output lies in the fact that the register r0 contains 1 before it jumps to the function f during evaluation of the branch greater, and then the function f modifies the register and sets r0 to 0 when doing the comparison cmp. This let me wonder how I the assembler can keep the registers invariant during function calls. I thought of the following:

  1. By simply pushing the entire register to the stack and loading it again afterwards
  2. From somehow gleaning what the function call thus and then "protecting" the registers if it needs to.

I thought the solution 1 is very defensive and likely costly, whilst solution 2 is probably complicated and assumes a lot of knowledge. Most likely, I simply made a mistake in writing the assembly, but I still don't understand how the assembler can keep its registers invariant when it needs to in general cases, or how one can address such a problem as outlined above. Does somebody know what to do in this case? I am grateful for any help or suggestions!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Commonly, a computing platform includes an Application Binary Interface (ABI) that specifies, among other things, protocols for function calls. The ABI specifies that certain processor registers are used for passing arguments or return results, certain registers may be freely used by the called routine (scratch or volatile registers), certain registers may be used by the called routine but must be restored to their original values (preserved or non-volatile registers), and/or certain registers must not be altered by the called routine. Rules about calling routines may also be called a “calling convention.”

If your routine uses one of the registers that called functions are free to use, and it wants the value of that register to be retained across a function call, it must save it before the function call and restore it afterward.

Generally, the ABI and functions seek a balance in which registers they use. If the ABI said all registers had to be saved and restored, then a called function would have to save and restore each register it used. Conversely, if the ABI said no registers have to be saved and restored, then a calling function would have to save and restore each register it needed. With a mix, a calling routine may often have some of its data in registers that are preserved (so it does not have to save them before the call and restore them afterward), and a called routine will not use all of the registers it must preserve (so it does not have to save and restore them; it uses the scratch registers instead), so the overall number of register saves and restores that are performed is reduced.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...