A Free Trial That Lets You Build Big!
Start building with 50+ products and up to 12 months usage for Elastic Compute Service
Introduction to GCC Embedded Assembly
In Embedded Assembly, the C language expression can be specified as the operand of the assembly instruction, and you do not have to worry about how to read the value of the C language expression into which register, and how to write the calculation result back to the C variable, you only need to tell the program the ing between the C language expression and the assembly instruction operand, GCC will automatically Insert the code to complete the necessary operations.
1. Simple Embedded Assembly
_ ASM _ volatile _ ("hlt"); "_ ASM _" indicates that the subsequent code is embedded assembly, "ASM" is the alias of "_ ASM. "_ Volatile _" indicates that the compiler should not optimize the code, and the subsequent commands will remain unchanged. "volatile" is its alias. The brackets contain Assembly commands.
2. Example of Embedded Assembly
To use embedded assembly, you must first compile the assembly instruction template, then associate the C language expression with the instruction operand, And tell GCC What restrictions are imposed on these operations. For example, in the following Assembly statement:
_ ASM _ violate _ ("movl % 1, % 0": "= r" (result): "M" (input ));
"Movl % 1, % 0" is the instruction template; "% 0" and "% 1" represent the instruction operands, which are called placeholders, embedded Assembly relies on them to correspond the C language expression to the instruction operand. The instruction template is enclosed in parentheses in C language expressions. In this example, there are only two: "result" and "input". They are displayed in the order of occurrence and the instruction operand "% 0", respectively ", "% 1" corresponds to; Note the corresponding order: the first c expression corresponds to "% 0"; the second expression corresponds to "% 1", and so on. The number of operands has up to 10, use "% 0", "% 1 ".... "% 9" indicates. There is a string enclosed in quotation marks before each operand. The content of the string is a limitation or requirement on the operand. "Result" indicates that the restricted string is "= r", where "=" indicates that "result" is the output operand, "R" indicates that you need to associate "result" with a general register. First, read the value of the operand into the Register, and then use the corresponding register in the instruction, instead of the result itself, of course, after executing the command, you need to store the value in the Register into the variable "result". On the surface, it seems that the command directly performs operations on "result". In fact, GCC implements implicit processing, in this way, we can write less commands. "R" in front of "input" indicates that the expression needs to be put into a register first, and then use this register in the instruction for calculation.
The relationship between a c expression or variable and a register is automatically handled by GCC. We only need to use a restricted string to guide GCC in how to handle it. The limit character must match the instruction's requirements on the operand. Otherwise, the resulting assembly code may be wrong. You can refer to the two "R" in the above example ", change to "M" (M indicates that the operands are stored in the memory, rather than in the register). After compilation, the result is:
Movl input, result
Obviously this is an invalid instruction, so the character string must match the instruction's requirements on the operand. For example, the instruction movl allows the Register to the Register and the register to be counted immediately, but the memory to the memory operation is not allowed. Therefore, the two operands cannot use "M" as the delimiter at the same time.
The embedded assembly syntax is as follows:
_ ASM _ (Assembly statement template: Output part: input part: Destroy description part)
There are four parts in total: The Assembly statement template, the output part, the input part, and the destruction description part. Each part uses the ":" lattice. The Assembly statement template is required. The other three parts are optional, if the latter part is used, and the former part is empty, you must use the ":" format. The content of the corresponding part is empty. For example:
_ ASM _ volatile _ ("CLI": "Memory ")
1. Assembly statement Template
An assembly statement template consists of a sequence of Assembly statements, separated by ";", "/N", or "/n/t. The operands in the command can use placeholders to reference C language variables. A maximum of 10 operands can be placeholders named as follows: % 0, % 1,..., % 9. Operations expressed by placeholders in a command are always considered as long (4 bytes). However, operations on the command can be a word or byte, when the operand is used as a word or byte, the default value is low or low. The Byte operation can explicitly specify whether it is a low byte or a secondary byte. Insert a letter between % and serial number. "B" indicates low bytes, and "H" indicates high bytes, for example, % H1.
2. Output part
The output part describes the output operands. Different operands are separated by commas. each operand descriptor consists of a limited string and a C-language variable. The limited string of each output operand must contain "=", indicating that it is an output operand.
_ ASM _ volatile _ ("pushfl; popl % 0; CLI": "= G" (x ))
The descriptor string represents the constraints on the variable, so that GCC can determine how to allocate registers based on these conditions, and how to generate the connection between the necessary code Processing Instruction operands and C expressions or C variables.
The input part describes the input operands. Different operands are separated by commas (,). Each operand descriptor consists of a limited string, a C expression, or a C variable.
_ 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 following example sets 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 Assembly statement code above is equivalent to the pseudo code below: btsl NR, ADDR. The two operands of this command cannot be all memory variables. Therefore, the qualified string of NR is specified as "ir ", associate NR with the immediate number or register, so that only ADDR is the memory variable in the two operands.
4. Restricted characters
4.1. Restricted Character List
There are many types of restricted characters, some of which are related to the specific architecture. Here, only the commonly used qualified characters and some common qualifiers that may be used in i386 are listed. They are used to indicate how the compiler processes the relationship between the subsequent C language variables and the instruction operands.
CATEGORY qualifier description
General Register "A" puts input variables into eax
Here is a question: what if eax has been used?
In fact, it is very simple: because GCC knows that eax has been used, it assembles the code in this section
Insert a statement pushl % eax at the beginning of, save the eax content to the stack
Then add a statement popl % eax at the end of the Code to restore the content of eax.
"B" puts input variables in EBX
"C" puts input variables into ECx
"D" put the input variable in edX
"S" puts the input variable into ESI
"D" put input variables into EDI
"Q": Put the input variables in one of eax, EBX, ECx, and EDX.
"R" puts the input variables into the General registers, that is, eax, EBX, ECx,
One of edX, ESI, and EDI
"A" combines eax and EDX into a 64-bit register (use long longs)
Memory "M" memory variable
The "O" operand is a memory variable, but its addressing method is offset type,
That is, base address addressing, or base address plus address change addressing
The "v" operand is a memory variable, but the addressing mode is not an offset type.
"" The operand is a memory variable, but the addressing mode is auto increment.
The "p" operand is a valid memory address (pointer)
Registers or memory "G" to put the input variables into one of eax, EBX, ECx, and EDX.
Or as a memory variable
The "X" operand can be of any type.
"I" 0-31 immediate count (for 32 shift bit instructions)
"J" immediate number between 0 and 63 (for 64-bit displacement instructions)
Immediate number between "N" and 0-(used for the out command)
"I" immediate count
"N": immediate number. Some systems do not support immediate numbers except words,
These systems should use "N" instead of "I"
Matches "0", indicating that the operands restricted by it match a specified operand,
"1"... that is, the specified operand, for example, "0"
"9" to describe "% 1" operations, then "% 1" references actually
Is the operand of "% 0". Note that the 0-9 and
The difference between "% 0"-"% 9" in the command. The former describes the operands,
The latter represents the operands.
& The output operations cannot use registers that are the same as the input operations.
Operand type "=" the operand is only written in the instruction (output operand)
"+" Operations are read/write type in the instruction (input/output operations)
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 switched to the next operand
For example, the two operands of addl can exchange order.
(Of course, neither of the two operands can be an immediate number)
# Partial comments. All letters from this character to the comma after it are ignored
* Indicates that if a register is selected, the subsequent letters are ignored.
5. Damage description
The destroy descriptor is used to notify the Compiler which registers or memory are used. It is composed of comma-separated strings. Each string describes a situation, generally the register name; in addition to registers, there is also "Memory ". For example, "% eax", "% EBX", and "Memory.
"Memory" is special and may be the most difficult part of Embedded Assembly. To explain it clearly, first introduce the compiler optimization knowledge, and then look at the C keyword volatile. Finally, let's look at the descriptor.
1. Introduction to Compiler Optimization
The memory access speed is far less than the CPU processing speed. To improve the overall performance of the machine, hardware high-speed cache is introduced on the hardware to accelerate memory access. In addition, commands in the modern CPU are not necessarily executed in strict order. commands without relevance can be executed in disorder to make full use of the CPU command line and improve the execution speed. The above is the hardware-level optimization. Let's look at optimization at the software level: one is optimized by the programmer when writing the code, and the other is optimized by the compiler. Common Methods for Compiler optimization are: caching memory variables to registers; adjusting the command order to make full use of the CPU command line; common is re-sorting read/write commands. When optimizing regular memory, these optimizations are transparent and efficient. The solution to the problem caused by Compiler Optimization or hardware re-sorting is from the hardware (or other processors) the memory barrier (memory barrier) must be set between operations executed in a specific sequence. Linux provides a macro to solve the execution sequence problem of the compiler.
Void barrier (void)
This function notifies the compiler to insert a memory barrier, but it does not work for the hardware. The compiled code will store all the modified values in the current CPU Register into the memory, read the data from the memory again when necessary.
2. C language keyword volatile
The C-language keyword volatile (note that it is used to modify the variable rather than the _ volatile _ mentioned above) indicates that the value of a variable may be changed externally, therefore, the access to these variables cannot be cached in registers and must be re-accessed each time. This keyword is often used in a multi-threaded environment, because the same variable may be modified by multiple threads when a multi-threaded program is compiled, and the program synchronizes various threads through this variable. For example:
DWORD _ stdcall threadfunc (lpvoid signal)
Int * intsignal = reinterpret_cast (signal );
* Intsignal = 2;
While (* intsignal! = 1)
Sleep (1000 );
Set intsignal to 2 when the thread starts, and then wait until it exits when intsignal is 1. Obviously, the intsignal value must be changed externally; otherwise, the thread will not exit. However, the thread does not exit during actual operation. Even if the value of the thread is changed to 1 in the external department, you can see the corresponding pseudo assembly code:
MoV ax, signal
If (ax! = 1)
For the C compiler, it does not know that this value will be modified by other threads. Naturally, it is cached in the register. Remember, the C compiler has no thread concept! Volatile is required. Volatile indicates that the value may be changed outside the current thread. That is to say, we need to add the volatile keyword before intsignal in threadfunc. At this time, the compiler knows that the value of this variable will change externally, so it will read the variable again every time it is accessed, the following pseudo code shows how the loop is made:
MoV ax, signal
If (ax! = 1)
With the above knowledge, it is not difficult to understand the memory modification descriptor. The memory descriptor tells GCC:
1) do not re-sort the assembly instructions embedded in this section with the preceding commands. That is, before the embedded assembly code is executed, all the preceding commands are executed.
2) Do not cache variables in registers, because this Code may use memory variables which may change in unpredictable ways, therefore, GCC inserts necessary code to first write the variable values cached in the register back to the memory. If you access these variables later, you need to re-access the memory.
If the Assembly command modifies the memory, but GCC itself does not notice it, because there is no description in the output part, you need to add "Memory" in the description section to tell GCC that the memory has been modified, after GCC learns this information, it will insert the necessary commands before this command to write the variable values in the cache to the register back to the memory, if you want to use these variables again later, read them again.
This can also be achieved by using "volatile", but it is better to use "Memory" to add this keyword before each variable.
Start building with 50+ products and up to 12 months usage for Elastic Compute Service