When reading the Linux kernel source code or performing performance optimizations on the codes, there is often a need to embed a piece of assembler code in the C language, which is called inline assembly in CS terminology. The notes in this article attempt to illustrate the basic grammatical rules and usages of inline assembly (students with strong English reading skills are advised to read the technical articles recommended in this reference directly ^_^).
Note: Since GCC uses the T-style assembler syntax (as opposed to the Intel syntax, the difference between the two is here), the assembly code involved in this article is subject to the-t syntax.
1. Basic grammar Rules
The basic syntax template for inline assembly (or embedded assembly) is simpler, as shown below (to make the structure clearer, there is a special line break here, in fact it can all be written to a single line):
[CPP]View Plaincopy
- ASM [ volatile] (
- Assembler Template
- [: Output OPERANDS] / * Optional * /
- [: Input operands] / * Optional * /
- [: List of clobbered registers] / * Optional * /
- );
Note: This article adheres to the unified style of the Linux system, with [] to indicate that its corresponding content is optional.
As you can see from the code template, the Basic grammar rules are made up of 5 parts , each of which is described below.
1) keyword ASM and volatile
ASM is the GCC keyword, which means that the assembly code will be embedded next. To prevent keyword ASM from naming conflicts with other parts of the program, GCC also supports the __ASM__ keyword, which is equivalent to the role of ASM.
Volatile is an optional keyword that does not require GCC to do any optimizations for the following assembly code. Also, for reasons of avoiding naming conflicts, __volatile__ is also a volatile equivalent keyword supported by GCC.
Volatile keywords are often used in BTW:C languages to modify variables (see here for unfamiliar classmates)
2) Assembler template
This is part of the assembly command that we want to embed, because we are assembling the code inline in the C language, so we need to enclose the command in double quotation marks "" so that GCC can pass these commands to assembler as in string form. For example, it can be written like this: "Movl%eax,%EBX"
Sometimes, there may be multiple assembly commands, usually divided into multiple lines, each line of commands are enclosed in double quotation marks, the command immediately followed by the "\n\t" such as the delimiter (of course, you can only use 1 pairs of double quotation marks to enclose the multi-line command, in terms of syntax, both are valid, we can decide which format to write). The sample code looks like this:
[CPP]View Plaincopy
- __asm__ __volatile__ ( "Movl%eax,%ebx\n\t"
- "Movl%ecx, 2 (%edx,%EBX, $8) \n\t"
- "Movb%ah, (%EBX)"
- );
Sometimes, depending on the program context, there may be some operands in the embedded assembly code similar to magic number, such as the following code:
[CPP]View Plaincopy
- int a=10, B;
- ASM ("Movl%1,%%eax; /* NOTICE: The following explains why the%%eax reference register EAX here
- MOVL%%eax,% 0; "
- : "=r" (b)///output The syntax for this field is described later in detail, which can be ignored here, the same as */
- :"R" (a)/ * input * /
- :"%eax"/ * clobbered Register * /
- );
We see that in the operand of the movl instruction (operand), there is a% 1,%0, which often makes the novice feel confused.in fact, as long as you know the following rules will not create doubts:
In inline assembly, operands are usually referenced by numbers, with the number sequence: If the command involves n operands altogether, the 1th output operand (the first operand) is numbered 0, the 2nd operand is numbered 1, and so on, The last 1 input operands (the end input operand) are numbered n-1.
Specifically to the above example code, according to the context, involving 2 operand variables A, B, the function of this assembly code is to assign a value to B, visible, A is the input operand, and B is the output operand, then according to the rules of the operand reference, not difficult to launch, a should use% As a reference, B should be quoted as%.
It is also necessary to note that:When both registers and operands referenced by%num are present in the command, the registers are referenced in%%reg (as in the previous example,%%eax) to help GCC to differentiate between registers and the operands provided by the C language.
3) Output operands
This field is optional to indicate the output operand, typically in the following format:
: "=a" (Out_var)
where "=a" specifies the constraint (constraint) that the output operand should adhere to, Out_var is the variable that holds the result of the instruction, usually a C language variable. In this example, "=" is a constraint that is specific to the output operand field, indicating that the operand is write-only (write-only); "A" indicates that the command execution result is first output to%eax, and then the Out_var in memory is updated by the register%eax. For common constraint rules, a description is given later in this article.
If there are multiple outputs, the typical format example is as follows:
[CPP]View Plaincopy
- ASM ( "CPUID"
- : "=a" (out_var1), "=b" (out_var2), "=c" (OUT_VAR3)
- : "A" (OP)
- );
Visible, we can specify its constraints for each output operand.
4) Input operands
This field is optional to indicate the number of input operands, in a typical format:
: "Constraints" (In_var)
Among them, constraints can be a variety of constraints supported by GCC, in_var the input variables typically provided by the C language.
Similar to output operands, when there is more than one input, the typical format is:
: "Constraints1" (in_var1), "Constraints2" (IN_VAR2), "Constraints3" (IN_VAR3), ...
Of course, the total number of input operands + output operands is usually limited, and the operand limit here is not difficult to understand, given that the maximum number of operands supported by each instruction set architecture is usually limited. The specific upper limit here is Max (max_in_instruction), where max_in_instruction is the number of operands that the instruction with the most operands in ISA contains.
It should be clear that, in the case of the input operands, even if the instruction does not produce an output operands, it: it needs to be given. For example ASM ("Sidt%0\n":: "M" (Loc)); The directive will also be: Write full, even if there is no specific output operands, because there is followed: input operands field.
5) List of clobbered registers
This field is optional and is used to list those registers that are involved in the directive that do not appear in the Output operands field and the input operands field. If the register is included in the clobber-list, it is equivalent to telling GCC that these registers may be rewritten by the inline assembly command. As a result, these registers will not be used by GCC for other processes or commands during inline assembly execution.
2. Common constraints (commonly used constraints)
In the process of describing the output operands and input operands fields, we already know that these operands often need to indicate their own constraints in order to accomplish the functionality we expect more clearly (imagine, If the constraint is not explicitly specified and is determined by GCC itself, debugging will become difficult once the code execution results are not expected.
Here are some common constraint items to start with.
1) Register operand constraint (register operand constraint, R)
When the operand is specified as such, the operand is stored in the specified universal register (general Purpose registers, GPR) when the assembly instruction is executed. For example:
ASM ("Movl%%eax,%0\n": "=R"(Out_val));
The purpose of this directive is to return the value of%eax to the C-language variable referenced by the% 0 out_val, according to the "=R" constraint that the specific operation flow is: first copy the%eax value to any GPR, and ultimately by the register to write the value of 0 in the variable represented by. The "R" constraint indicates that GCC can first deposit the%EAX value into any available register, which is then responsible for updating the memory variables.
It is also generally possible to explicitly designate a register as a "transit", and the corresponding relationship between the constraint parameter and the Register is:
A:%eax,%ax,%al
B:%ebx,%BX,%BL
C:%ecx,%CX,%CL
D:%edx,%DX,%DL
S:%esi,%si
D:%edi,%di
For example, if you want to specify a%EBX as the transit register, the command is ASM ("Movl%%eax,%0\n": "=b" (Out_val));
2) memory operand constraint (operand constraint, m)
When we do not want to pass through the register, but directly manipulate the memory, you can use "M" to constrain. For example:
ASM volatile ("lock; Decl%0 ":" =m"(counter):" M "(counter));
This instruction implements an atomic subtraction operation, and the input and output operands are directly from memory (and because of this, the atomicity of the operation can be guaranteed).
3) association constraints (matching constraint)
In some cases, if the input and output of the command are the same variable, you can specify the matching constraint to allocate registers in the inline assembly, at which point the input operand and output operand share the same "transit" register. For example:
ASM ("Incl%0": "=a" (Var): "0"(Var));
This instruction performs a incl operation on the variable Var, because the input and output are the same variable, so "0" can be used to specify that the%EAX as the transit register. Note that the "0" constraint modifies the input operands.
4) Other constraints
In addition to the 3 commonly used constraints described above, there are some other constraint parameters (such as "O", "V", "I", "G", etc.), interested students can refer to here.
3. Example Anatomy
There are a number of theoretical rules that have been introduced here, and the understanding of inline assembly is deepened by analyzing an example.
The following code is the SYSCALL0 definition in the Linux kernel i386 version:
[CPP]View Plaincopy
- #define _SYSCALL0 (type, name) \
- Type name (void) \
- { \
- long __res; \
- __asm__ volatile ( "int $0x80" \
- : "=a" (__res) \
- : "0" (__nr_# #name)); \
- __syscall_return (type, __res); \
- }
For system call Fork, the above macro expands to:
[CPP]View Plaincopy
- pid_t fork (void)
- {
- long __res;
- __asm__ volatile ( "int $0x80"
- : "=a" (__res)
- : "0" (__nr_fork));
- __syscall_return (pid_t, __res);
- }
It is not difficult to understand the meaning of this code, based on the previous statement of the inline assembly syntax and how it is used. This inline assembler translates a more readable pseudo-code in the form of:
[CPP]View Plaincopy
- pid_t fork (void)
- {
- long __res;
- %eax = __nr_fork/ * __nr_fork The call number assigned to the system call fork for the kernel * /
- int $0x80/ * Trigger Interrupt, the kernel is based on the value of%eax, the interrupt is the fork system call * /
- __res =%EAX/ * Interrupt return value remains in%eax * /
- __syscall_return (pid_t, __res);
- }
Resources
1. Gcc-inline-assembly-howto
2. Inline assembly for x86 in Linux
3. Programmer self-cultivation-link, load and library, 12th chapter
4. Using Assembly Language in Linux
=============== EOF ================
Article Source: http://blog.csdn.net/slvher/article/details/8864996
[Reproduced] "Linux learning note" in Linux C inline assembly syntax format and how to use (inline Assembly in Linux c)