APCS Introduction (ARM procedure Call standard) |
· Introduced
· Register naming
· Design key
· Consistency
· Stack
· Backtracking structure
· Actual parameters
· function exit
· Build a stack backtracking structure
· APCS Standard
· Something that's useful for coding
Introduced
The Apcs,arm procedure invocation standard (ARM Procedure call standards) provides a mechanism for a compact authoring routine that can be interwoven with other routines. The most significant point is that there is no clear limit to where these routines come from. They can be compiled from C, Pascal, or it can be written in assembly language.
The APCS defines:
· Restrictions on the use of registers.
· conventions for using stacks.
· Pass/return parameters between function calls.
· The format of a stack-based structure that can be ' backtracking ' to provide a list of functions (and given parameters) from the point of failure to the entry of the program.
APCS is not a single given standard, but a series of standards that are similar but differ in specific conditions. For example, Apcs-r (for RISC OS) specifies that flags set when a function enters must be reset when the function exits. Under the 32-bit standard, you don't always know the entry flags (no USR_CPSR), so you don't need to restore them. As you might expect, there is no compatibility between the different versions. The code that wants to recover flags may behave erratically when they are not restored ...
If you develop an ARM-based system, you are not required to implement APCS. But it is recommended that you implement it, because it is not difficult to achieve, and can make you gain various benefits. However, if you want to write assembly code that is used to connect to the compiled C, you must use APCS. The compiler expects specific conditions that must be met in your join (add-in) code. A good example is APCS definition A1 to A4 can be destroyed, while V1 to V6 must be protected. Now I'm sure you're scratching your heads and talking to yourself, "What's a?" What is V? ". So first introduce the APCS-R register definition ...
Register naming
APCS a different name for the registers we normally call R0 to R14. With the assembler preprocessor feature, you can define names like R0, but it's a good idea to learn to use APCS names when you're modifying code written by someone else.
Register name |
Reg # |
APCS |
Significance |
R0 |
A1 |
Work Register |
R1 |
A2 |
" |
R2 |
A3 |
" |
R3 |
A4 |
" |
R4 |
V1 |
Must protect |
R5 |
V2 |
" |
R6 |
V3 |
" |
R7 |
V4 |
" |
R8 |
V5 |
" |
R9 |
V6 |
" |
R10 |
Sl |
Stack limit |
R11 |
Fp |
Frame pointer |
R12 |
Ip |
R13 |
Sp |
Stack pointer |
R14 |
Lr |
Connection Register |
R15 |
Pc |
Program counter |
IP is shorthand for instruction pointers.
These names are not defined by the standard Acorn objasm (version 2.00), but later versions of Objasm, and other assemblers (such as Nick Robert's ASM) define them. To define a register name, typically, you use the RN Macro Directive (Directive) at the beginning of the program:
A1 RN 0
A2 RN 1
A3 RN 2
... etc...
R13 RN 13
SP RN 13
R14 RN 14
LR RN R14
PC RN 15
This example shows some important things:
1. Registers can define multiple names-you can define both ' R13 ' and ' SP '.
2. Registers can be defined from the register previously defined-' LR ' is defined by a register called ' R14 '.
(For Objasm is correct, other assembler may not be the case)
Design key
· function calls should be fast, small, and easy (by the compiler) to optimize.
· Functions should be able to handle multiple stacks properly.
· Functions should be easy to write reentrant and relocatable code, mainly by separating the writable data from the code.
· But above all, it should be simple. This way assembler programmers can easily use its facilities, while debuggers can easily track the program.
Consistency
The part of the program that follows APCS is called "consistent" when calling an external function. Following APCS (typically, compiler-generated programs) at all times during program execution is called "strict consistency." The agreement states that if you follow the correct entry and exit parameters, you can do whatever you need within your own function and remain consistent. This is necessary in some cases, such as when writing SWI camouflage (veneers), using many registers for actual SWI calls.
Stack
Stacks are a list of linked ' frames ' that are linked by a thing called ' backtracking structure '. This structure is stored at the high end of each frame. Each chunk of the stack is allocated in descending order of address. The register SP always points to the lowest used address in the most current frame. This is in line with the traditional full descending stack. In Apcs-r, the Register SL holds a stack limit, and you decrement the SP to not lower it. Between the current stack pointer and the current stack, there should be no other APCS function to rely on, and when called, the function can set itself a stack.
There can be more than one stack area (chunk). They can be located at any address in memory and no specification is provided here. Typically, this will be used to provide multiple stacks for the same code when executed in reentrant mode, and an analogy is FileCore, which provides a simple set of ' state ' information and calls the same part of the code as required, to the currently available FileCore file system (ADFS, Ramfs, Idefs, scsifs, etc.) to provide services.
Backtracking structure
The register FP (frame pointer) should be 0 or the last structure in the list of stack backtracking structures, providing a way to trace the function of the call in reverse.
The backtracking structure is:
Address High end
Save code Pointer [FP] FP point here
return LR value [FP, #-4]
return SP value [FP, #-8]
Returns the FP value [FP, #-12] points to the next structure
[Saved SL]
[Saved V6]
[Saved V5]
[Saved V4]
[Saved V3]
[Saved v2]
[Saved V1]
[Saved A4]
[Saved A3]
[Saved A2]
[Saved A1]
[Saved F7] three characters
[Saved F6] three characters
[Saved F5] three characters
[Saved F4] three characters
Address Low End
This structure contains 4 to 27 characters, which are optional values in square brackets. If they exist, they must exist in the order given (for example, A3 saved in memory can be saved F4, but a2-f5 cannot exist). Floating-point values are stored in ' internal format ' and occupy three characters (12 bytes).
The FP register points to the stack backtracking structure of the currently executing function. The return FP value should be zero, or a pointer to the stack backtracking structure established by the function that called the current function. The return FP value in this structure is a pointer to the stack backtracking structure that invokes the function that called the current function, and so on until the first function.
When the function exits, the return value of the connection, the return SP value, and the return FP value are loaded into the PC, SP, and FP.
#include <stdio.h>
void one (void);
void (void);
void zero (void);
int main (void)
{
One ();
return 0;
}
void One (void)
{
Zero ();
Both ();
Return
}
void (void)
{
printf ("main...one...two\n");
Return
}
void zero (void)
{
Return
}
When it outputs a message on the screen,
The APCS backtracking structure will be:
FP----> Two_structure
Return link
Return SP
return FP----> one_structure
... return link
Return SP
return FP----> main_structure
... return link
Return SP
return FP----> 0
...
So, we can examine the FP and see the structure of the function ' one ', which points to the structure of the function ' one ', which points to the structure of ' main ', which points to the zero end. In this way, we can reverse-trace the entire program and determine how we reached the current crash point. It is worth pointing out the ' zero ' function, because it has been executed and exited, and we are doing the printing behind it, so it was in the back of the structure, but it's not there anymore. It is also worth pointing out that it is unlikely that a APCS structure like the above will always be generated for a given code. The reason is that a function that does not call any other function does not require a full APCS header.
For a more detailed understanding, the following code is Norcroft C v4.00 generated for the above code ...
Area | C $ $code |, code, READONLY
IMPORT |__main|
|x$codeseg|
B |__main|
DCB &6d,&61,&69,&6e
DCB &00,&00,&00,&00
DCD &ff000008
IMPORT |x$stack_overflow|
EXPORT One
EXPORT Main
Main
MOV IP, SP
Stmfd sp!, {fp,ip,lr,pc}
SUB FP, IP, #4
CMPS SP, SL
Bllt |x$stack_overflow|
BL One
MOV A1, #0
Ldmea FP, {fp,sp,pc}^
DCB &6f,&6e,&65,&00
DCD &ff000004
EXPORT Zero
EXPORT
One
MOV IP, SP
Stmfd sp!, {fp,ip,lr,pc}
SUB FP, IP, #4
CMPS SP, SL
Bllt |x$stack_overflow|
BL Zero
Ldmea FP, {FP,SP,LR}
B
IMPORT |_printf|
Both
ADD A1, PC, #L000060-.-8
B |_printf|
L000060
DCB &6d,&61,&69,&6e
DCB &2e,&2e,&2e,&6f
DCB &6e,&65,&2e,&2e
DCB &2e,&74,&77,&6f
DCB &0a,&00,&00,&00
Zero
MOVS PC, LR
Area | C $ $data |
|x$dataseg|
END
This example does not conform to the 32 system. The APCS-32 rule simply indicates that the logo does not need to be saved. So delete the LDM ' ^ ' suffix and delete the MOVS ' S ' suffix in function zero. The code is the same as the compiler that complied with 32-bit.
The Save Code pointer contains the instruction to set the backtracking structure (STMFD ...). Add 12 bytes to the address. Remember, for the 26-bit code, you need to remove the PSR to get the actual code address.
Now let's look at the first entry to the function:
· The PC always contains the location of the next instruction to be executed.
· LR (always) contains the value to be loaded into the PC when exiting. It also contains PSR in the 26-bit bit code.
· The SP points to the current stack block (chunk) limit, or to the top of it. This is where the temporary data, registers, and similar things are copied. Under RISC OS, you have the option of at least 256 bytes to extend it.
· The FP is either zero or points to the most current part of the backtracking structure.
· The function arguments are laid out as described (below).
Actual parameters
APCS does not define records, arrays, and similar patterns. Such languages are free to define how these activities are carried out. However, if your own implementation does not actually conform to the spirit of APCS, then the code from your compiler will not be allowed to connect with code from other compilers. Typical, the use of C language conventions.
· The first 4 integer arguments (or less!) are loaded into the A1-A4.
· Top 4 floating point arguments (or less!) are loaded into the f0-f3.
· Any other arguments, if any, are stored in memory and are pointed to by the word that precedes the value of the SP as it enters the function. In other words, the remaining parameters are pressed into the top of the stack. So to be simple. It is a good idea to define a function that accepts 4 or fewer parameters.
function exit
Exits the function by passing the value of the return connection to the program counter, and:
· If the function returns a value less than or equal to one word size, the value is placed in the A1.
· If the function returns a floating-point value, put it in F0.
· SP, FP, SL, V1-V6, and F4-f7 should be restored (if modified) to contain the values it holds when entering the function.
I tested intentional destruction registers, and the result was (often in completely different parts of the program) an unwanted and singular failure.
· These arguments for IP, LR, a2-a4, F1-F3, and incoming stacks can be destroyed.
In 32-bit mode, the PSR flag is not required to be protected from cross function calls. This must be done in 26-bit mode and secretly restored by transmitting LR to the PC (MOVS, or Ldmfd xxx^). It is not sufficient to re-load N, Z, C, and V from LR, and to protect these flags across functions.
Build a stack backtracking structure
For a simple function (a fixed number of arguments, not reentrant), you can create a stack backtracking structure with the following instructions:
Function_name_label
MOV IP, SP
Stmfd sp!, {fp,ip,lr,pc}
SUB FP, IP, #4
This fragment (from the compiled program above) is the most basic form. If you want to destroy other non-destructive registers, you should include them in this STMFD directive.
The next task is to check the stack space. If you don't need a lot of space (less than 256 bytes) You can use:
CMPS SP, SL
Bllt |x$stack_overflow|
This is the way C version 4.00 handles overflow. In a later release, you will call |__rt_stkovf_split_small|.
And then do your own thing ...
Complete the exit with the following command:
Ldmea FP, {fp,sp,pc}^
Also, if you have other registers on the stack, reload them here as well. The reason for choosing this simple LDM exit mechanism is that it is easier and more reasonable than branching to a special function to exit the processor (handler).
An extension to this protocol used in backtracking is to embed the function name in the code. immediately preceding the function (and MOV IP, SP) should be:
DCD &ff0000xx
The ' xx ' here is the length of the function name string (including padding and Terminator). This string is a word-aligned, trailing-filled, and should be placed directly on the DCD &ff .... The front.
So a complete stack backtracking code should be:
DCB "My_function_name", 0, 0, 0, 0
DCD &ff000010
My_function_name
&n