Introduction
In the development of embedded software systemsCLanguage ApplicationProgramDevelopment to improve development efficiency. At the same time, the system often contains some key modules that determine the overall system performance. In order to achieve the best performance, they are often written in assembly language, or in some special circumstances, such as operating hardware, you must also use the assembly language.
Function isCAn important concept in language, which is often used in assembly languages.(Subroutine or procedure)This article uses the glossary to express the same concept. This article first introducesArmThis paper introduces a general method for designing assembly language subroutines, and proposes a new design method based on Stack frames.CLanguage interaction technology.
1 General Method
InArmGenerallyBL (branch and link)Command to call a subroutine,BLThe command first saves the return address in the Link registerR14 (Also knownLr)And jump to the target address. After the sub-routine is executedR14Copy contentPC.
...
BL subr;CallSubr
...;Return here
Subr
...;Child routine body
MoV PC, LR;SlaveSubrReturn
This method is sufficient for leaf routines (that is, routines that do not call other child routines), but it cannot process nested or recursive calls. HypothesisSubrInternal useBLIf another subroutine is calledLRWill be rewritten by the return address of the next call, resulting in an endless loopSubr. To solve this problem,SubrIt must be saved before the second subroutine is called.LR. Furthermore, to enable the subroutine to call another subroutine in any depth, some method must be taken to save any number of return addresses. The most common method is to save the return address in the stack, as shown in the following example:
Subr
Stmfd SP !, {R4-R12, LR};Save all working registers and return addresses, and update the stack pointer.
...;Child routine body
Ldmfd SP! R4-R12, PC};Recover all working registers and load them with the saved return addressPC,
;Update Stack pointer
You can setSubrAny operation registers andLRSave it to the stack and bring them up at the exit point. This allows you to safely call the child routines without worrying about the failure to return the normal data from the child routine due to the rewrite of the return address. Note that the returned address is directly used for loading at the exit point.PCIt is equivalent to the following two commands:
Ldmfd SP! {R4-R12, LR}
MoV PC, LR
2 Stack frame-based child routines
The child routine design method described above can meet the design requirements, but is familiar X86 Programmers in assembly languages are not quite comfortable with it. As we all know, X86 The assembly language subroutine has a standard stack structure, 1 . One of its notable features is EBP Registers are used as reference points to reference parameters and local variables. For example, the first parameter is at the address [EBP + 8]. The advantage of stack frame is that it unifies the programming style of the Assembly subroutine. parameters, return addresses, work registers, or local variables have fixed positions, which not only improvesCodeReadability is also conducive to code maintenance. Based on the above considerations, the stack frame concept is introduced Arm The following example shows the design of the assembly language subroutine. For simplicity, assume that Subr The prototype is Int subr (int A, int B, int C, int D, int e, int F ); Obviously, according APCs (ARM Procedure Call Standard ) , Parameter A-d Register R0-R3 And the remaining two parameters are passed. E And F Pass through the stack. Final stack frame structure 2As shown in the following figure: 1 In X86 The only difference between the frame structure and the local variable is the opposite location of the working register. The reason for this difference is to make full use Arm Multi-register Load-store Command advantages.
Caller
...;Parameter omittedA-dTransfer code
MoV R4, #2
STR R4, [Sp, #-4]!; 1)Set the parameter FPush to stack
MoV R4, #1
STR R4, [Sp, #-4]!;Set parametersEPush to stack
BLSubr; 2)Call a subroutineSubr
Add SP, SP, #8; 8)Balance the stack.SubrThe returned value is saved inR0Medium
Subr
StmfdSP !, {R4-R7, FP, LR}; 3)Save the working register,FpAndLR
Add FP, SP, #16; 4)Calculate frame pointer
SubSP, SP, #8; 5)Allocate space for local variables
LDR R4, [FP, #8];Load parametersE
LDR R5, [FP, #12];Load parametersF
...; SubrChild routine body
AddSP, SP, #8; 6)Release local variable space
Ldmfd SP !, {R4-R7, FP, PC}; 7)Recover registers and return
Figure1x86Stack frame structure
Figure2 armStack frame structure in
The following details how to build a stack frame step by step. The sequence number corresponds to the sequence number in the sample code comment:
1)GenerallyStr rn, [Sp, #-4]!The command pushes the parameters required by the subroutine into the stack. Note thatAPCsFirst, consider using registersR0-R3PASS Parameters. The remaining parameters are pushed to the stack in reverse order. If you use a registerR0-R3All parameters can be passed, so this step can be omitted.
2)BLThe command pushes the return address to the stack and jumps to the specified subroutine to continue execution. Since then, all stack modification work has been transferred to the child routine.
3)If the subroutine needs to useR4-R11Working registers, which must be pushed into the stack, and the old frame pointer registersFpAnd link registerLRPush into the stack, which can be efficiently completed in a single instruction.
4)Adjust frame pointerFpTo reference stack parameters and variables. In this example, you can useLDR r0, [FP, #8]Reference parametersE,LDR r0, [FP, #-20]Reference the first local variable.
5)Allocate8The local variables of the sub-routine. However, if you do not need to use local variables, skip this step. AndCISCArchitectureX86Different processors,ProteusArchitectureArmThe processor has a large number of General registers, suchR0-R7,LRSo in most cases, you do not need to allocate stack space for local variables.
6)If you previously allocated stack space for local variables, you need to release them to maintain a stack balance.
7)Recover the registers stored in the stack in step 3.PCRegisters are returned from the subroutine.
8)SubroutineSubrReturn here after the execution is complete. This step is very important becauseCallerBefore callingSubrParametersEAndFPushed Into the stack, so fromSubrAfter ReturnCallerThe two parameters must pop up the stack to maintain the balance of the stack. Of courseCThe compiler is responsible for completing the stack balancing when a subroutine is called in a language.
3 Assembly Language and C Language Interaction
After compiling the Assembly subroutine, the next question is howCLanguage. Essentially, no matter which language is used for code writing, the routines that call other modules in each other must follow a common parameter and result transfer convention. ForArmThis Convention is calledArmThe process call standard, which defines:
LUsage of General registers
LType of stack used
LTransmission of parameters and results
LArmShared Library Mechanism support
Because the code generated by the compiler always strictly followsAPCsTherefore, you only need to ensure that the compiled Assembly Code conformsAPCsYou can. The following example shows howCThe language calls the child routines written in assembly language to implement the memory copy function. The development environment isRealview mdk3.22a.
;Define and exportMymemcpyOfMymemcpy. sFile
; R0Destination Address,R1Point to source address,R2Copy Length
Area demo, code, readonly
Export mymemcpy
Mymemcpy
Stmfd SP !, {R4, LR}
MoV R3, R0;Retrieve Destination Address
MoV R12, R1;Retrieve Source Address
Copy
CMP R2, #0;If the length is less than or equal0Exit
Ble exit
Sub R2, R2, #0x1
Beq exit
Ldrb LR, [R12], #0x1
Strb LR, [R3], #0x1
B copy
Exit
Ldmfd R13 !, {R4, PC}
End
// Main. cTest procedure
Extern void * mymemcpy (void * DST, const void * SRC, size_t size );
Int main (INT argc, char ** argv)
{
Const char * src = "first string-source ";
Char DST [] = "second string-destination ";
Mymemcpy (DST, SRC, strlen (SRC) + 1 );
Return (0 );
}
Call from assembly languageCThe key of a function is howCThe prototype of the function correctly transmits parameters. The following example shows how to callCLibrary functionsStrcmpThe prototype isInt strcmp (const char * S1, const char * S2 );, It only has two pointer type parameters, soR0AndR1Point to the first and second strings respectively. Note thatCLibrary Function, select the project option dialog box,TargetTabUse microlib.
Area |. Text |, code, readonly
Export main;ExportMain
Import _ main
Import strcmp;ImportStrcmpFunction
Main
Stmfd SP !, {R4, LR };SaveLR
ADR r0, big;PassR0PASS Parameters1
ADR R1, small;PassR1PASS Parameters2
BL strcmp;CallStrcmpLibrary functions
Ldmfd SP !, {R4, PC}
Big
DCB "big", 0
Small
DCB "small", 0
End
Summary
This article starts from the author's practice and talks aboutArmDesign method of assembly subroutine and itsCYour experience in language interaction should be corrected.
References
1.Andrew N. sloss, Dominic Symes, Chris WrightBy.Translated by Shen Jianhua. ArmEmbedded System Development-Software Design and Optimization.Beijing University of Aeronautics and Astronautics Press.
2.David seal. Arm Architecture Reference Manual, second edition, Addison-Wesley.
3.RealviewCompilation tool2.0Version-Developer Guide, Arm Limited.