ZZ: GCC embedded compilation

xiaoxiao2021-03-06  15

In the inline assembly, the C language expression can be specified as the operand of the assembly instruction, and does not use which register for reading the value of the C language expression, and how to write the calculation result back to C variables, you just tell The correspondence between the C language expression and the assembly instruction operand can be automatically inserted into the code completed the necessary operations.

1. Simple embedded compilation

example:

__ASM___volatile __ ("hlt"); "__ASM__" indicates that the following code is embedded, "ASM" is an alias of "__asm__". "__volatile__" indicates that the compiler does not optimize the code, and the following instructions remain, "volatile" is its alias. The brackets are compilation instructions.

2, embedded assembly example

Using embedded assembly, you must first write the assembly instruction template and then associate C language expressions with the operands of the instructions and tell GCC which restrictions on these operations. For example, in the following assembly statement:

__ASM____violate__ ("MOVL% 1,% 0": "= r": "INPUT);

"MOVL% 1,% 0" is an instruction template; "% 0" and "% 1" representative instructions, called placeholders, and embedded compilation depends on them corresponding to the command operands. The instruction template is enclosed in a small bracket in a small bracket. There are only two: "result" and "INPUT" in this example, and they correspond to the order "% 0", "% 1", respectively, according to the order of appearance Pay attention to the corresponding order: The first C expression corresponds to "% 0"; the second expression corresponds to "% 1", push it according to the secondary, and the number of operands are at most 10, respectively with "% 0", "% 1". ... "% 9" means. In front of each operand, there is a string enclosed with quotation marks, and the content of the string is limit or requirements for the operand. The restriction string in front of "Result" is "= R", where "=" indicates that "Result" is the output operand, "R" means that "Result" needs to be associated with a general register, first will limit the value of the operand Read the register, then use the corresponding register in the instead of "result" itself, of course, you need to save the value "result" in the register after execution, which is read from the surface to "Result". Operation, actually GCC has implicitly processed so that we can write some instructions. "R" in "INPUT" indicates that the expression needs to be placed in a register and then use the register to participate in the operation in the instruction. C Expression or Variables The relationship between the register is automatically processed by GCC, and we only need to use restriction string to guide GCC how to handle it. Restriction characters must match the requirements of the operating number, otherwise the assembly code generated will be wrong, the reader can change the two "R" in the above example to "M" (M represents the operand in memory) Instead of the register), the results obtained after compilation are: MOVL INPUT, and Result is obvious that this is an illegal directive, so the restriction string must match the requirements of the operating number of the command. For example, the command MOVL allows the register to the register, immediately to the register, etc., but does not allow memory to memory operation, so the two operands cannot be used simultaneously as a qualified character.

The embedded compilation syntax is as follows: __ASM __ (Compilation Specile Template: Output section: Enter the section: Destroying section) Total four parts: Compilation Specifier Templates, Output Sections, Input Sections, Description Sections, Each Part ":" Genet, Compilation language template is essential, other three parts can be selected, if used in the back part, and the front part is empty, it is necessary to use ":", and the corresponding part is empty. For example: __ASM____volatile __ ("CLI":: "Memory") 1. Compilation Stage Template Compilation File Template consists of composite statement sequences, and ";", "/ n" or "/ n / t" is separated. The number of operands in the command can use the placeholder reference C language variable, the operand placeholder is up to 10, the name is as follows:% 0,% 1, ...,% 9. Use the operand represented in the instruction, always treated as a long type (4 bytes), but the operation applied to it can be a word or byte according to the instruction, when the number of operands or bytes are used When the default is low or low byte. The byte operation can be explicitly indicated whether the low byte is or the secondary byte. The method is to insert a letter between% and serial numbers, "B" represents the low byte, "H" represents high bytes, such as% H1. 2, the output part output section describes the output operand, and the different operand descriptors are used in a comma, each operand descriptor consists of a defined string and a C language variable. The limit string of each output operator must contain "=" to indicate that he is an output operator. Example: __ASM____volatile __ ("Pushfl; Popl% 0; CLI": "= g") Descriptor string represents the limit condition for the variable, so that GCC can determine how to assign registers based on these conditions, how to generate The code processing instruction operands are connected between C expressions or C variables. 3. Input section input section describes the input operand, and the different operand descriptors are used to open, each operand descriptor consists of a defined string and a C language expression or C language variable. Example 1: __ASM____Volatile__ ("LIDT% 0":: "M" (REAL_MODE_IDT)); Example 2 (Bitops.h): static __inline__void __set_bit (int NR, Volatile Void * Addr) {__ASM __ ("BTSL% 1, % 0 ":" = m "(AddR):" IR "(NR));}

The post-exemplary function is to set the NR bit of (* AddR) to 1. The first placeholder% 0 corresponds to the C language variable ADDR, and the second placeholder% 1 corresponds to the C language variable NR. Therefore, the above assembly statement code is equivalent to the following pseudo code equivalents: BTSL NR, ADDR, the two operands of the instruction cannot be all memory variables, so designate NR's qualified string to "IR", and the NR " Or the register is associated, so only addr is a memory variable in the two operands.

4, restriction character

4.1, restriction character list

There are many species that limit characters, and some are related to a particular architecture, which lists only some commonly used qualifiers that are commonly used in I386. Their role is to indicate how the compiler handles the relationship between the subsequent C language variables and the instruction operand. Classification qualifier description

Universal Register "A" puts the input variable into EAX

Here is a problem: assuming that EAX has been used, what should I do?

Actually, it is very simple: because GCC knows that EAX has been used, it is assembled in this section.

Insert a statement pushl% EAX in the beginning, save the EAX content to the stack,

After the end of this code, add a statement POPL% EAX to restore EAX content.

"b" put the input variable into EBX

"c" puts the input variable into ECX

"D" puts the input variable into the EDX

"S" puts the input variable into the ESI

"d" puts the input variable into the EDI

"q" puts the input variable into the EAX, EBX, ECX, EDX

"R" places the input variable into the universal register, which is Eax, EBX, ECX,

One of EDX, ESI, EDI

"A" synthesize EAX and EDX a 64-bit register (use long longs)

Memory "M" memory variable

The "O" operand is a memory variable, but its addressing method is an offset type.

It is also a base address, or a base address address address.

The "V" operand is a memory variable, but the addressing method is not an offset type.

"" The operand is a memory variable, but the addressing method is automatic increment.

"P" operand is a legal memory address (pointer)

The register or memory "G" puts the input variable into the EAX, EBX, ECX, EDX.

Or as a memory variable

The "X" operand can be any type

Quick number

The immediate number of "I" 0-31 (for 32-bit shift instructions)

The immediate number of "J" 0-63 (used for 64-bit shift instructions)

The immediate number of "n" 0-255 (for OUT command)

"i" immediately

"n" is immediately, some systems do not support the immediate number other than the word,

These systems should use "N" instead of "i"

Match "0", indicating that the number of operands that is limited is matched to a specified operand,

"1" ... that is, this operand is the specified number of operands, such as "0"

"9" describes the "% 1" operand, then "% 1" is actually "% 0" operand, pay attention to 0-9 of the qualifier letter

The difference between "% 0" - "% 9" in the instruction, the former describes the operand,

The latter represents the operand.

& This output operand cannot be used and the same register is the same.

Operand type "=" operands are only written in the command (output operand)

" " Operands are read and write types in the command (input and output operand)

Floating point number "F" floating point register

"T" first floating point register

"U" second floating point register

"G" standard 80387 floating point constant

% This operand can be exchanged and the next operand

For example, the two operands of AddL can exchange order

(Of course, the two operands can not be an immediate number)

# Some comments, all letters between the comma from this character are ignored

* Indicates that if the register is selected, the following letters are ignored.

5, destroying the description

Destruction Descriptor For Notification Compiler Which registers or memory is used, consisting of a comma-opened string, each string describes a situation, typically a register name; except for the register, there is "Memory". For example: "% EAX", "% EBX", "Memory", etc.

"Memory" is special, which may be the most difficult part in embedding assembly. In order to explain it, let's introduce the optimization knowledge of the compiler, and then look at the C keyword volatile. Finally, look at this descriptor.

1, compiler optimization introduction

The memory access speed is far from the CPU processing speed, in order to improve the overall performance of the machine, introduce the hardware cache Cache on the hardware to accelerate access to memory. In addition, the execution of instructions in modern CPUs is not necessarily performed in order, without correlation instructions can be performed, to take advantage of the CPU's instruction pipeline, improve the execution speed. The above is the optimization of the hardware level. Look at the optimization of the software level: one is optimized by programmers when writing code, and the other is optimized by the compiler. The compiler optimized method is: cache memory variables to the register; adjust the command order to take advantage of the CPU instruction pipeline, commonly to reorder read and write instructions. These optimizations are transparent when optimizing conventional memory, and it is very efficient. The solution to the problem caused by compiler or hardware reordering is to set a memory barrier between the operations executed from the hardware (or other processor), and Linux provides a macro solution. Performing order of compiler.

Void Barrier (Void)

This function notifies the compiler inserts a memory barrier, but the hardware is invalid, the compiled code will store all modified values ​​in the current CPU register into memory. When these data is required to read it again.

2, C language Keyword volatile

C Language Keyword Volatile (Note that it is __volatile__) used to modify the variable instead of the above, indicating that the value of a variable may be changed externally, so access to these variables cannot be cached to the register, and each time you use Renew. This keyword is often used in multi-threaded environments, because when writing multithreaded programs, the same variable may be modified by multiple threads, and the program synchronizes each thread through this variable, for example: DWORD __STDCALL ThreadFunc (LPVOID SIGNAL)

{

INT * INTSIGNAL = Reinterpret_cast (signal);

* INTSIGNAL = 2;

While (* INTSIGNAL! = 1)

Sleep (1000);

Return 0;

}

When the thread starts, set the INTSIGNAL to 2, then the loop waits until IntSignal exits when INTSignal is 1. Obviously the value of INTSignal must be changed outside, otherwise the thread will not exit. But when actually running, the thread will not quit, even if it is changed to 1 in the outside, look at the corresponding pseudococh code:

MOV AX, Signal

Label:

IF (AX! = 1)

Goto label

For the C compiler, it doesn't know that this value will be modified by other threads. Naturally put it Cache inside the register. Remember, the C compiler is no thread concept! This time you need to use Volatile. The original meaning of Volatile means that this value may be changed outside the current thread. That is, we have to add a Volatile keyword in front of the IntSignal in Threadfunc. At this time, the compiler knows the value of the variable to change in the external change, so each access the variable, the loop is changed to As shown in the following pseudo code:

Label:

MOV AX, Signal

IF (AX! = 1)

Goto label

3, Memory

With the top knowledge, it is not difficult to understand the Memory modification descriptor, the Memory descriptor informs GCC:

1) Do not reordbound this paragraph inline assembly instructions and the previous instructions; that is, before execution of the assembly code, its previous instructions are completed

2) Do not cache variables to registers because this code may use memory variables, and these memory variables will change in unpredictable ways, so GCC is inserted into the necessary code first writes the variable value of the register. If you have access to these variables later, you need to re-access memory.

If the assembled instruction modifies the memory, the GCC itself is not perceived because there is no description in the output section, and it is necessary to add "Memory" in the modification description. Tell the GCC memory has been modified. After the GCC learned this information, Before this instruction, insert the necessary instructions to write back the memory before optimizing the variable values ​​in the Cache to the register, if you want to use these variables again later.

Use "volatile" to achieve this, but we add this keyword before each variable, it is better to use "Memory"

转载请注明原文地址:https://www.9cbs.com/read-46222.html

New Post(0)