Http://blog.csdn.net/menuconfig/archive/2007/08/23/1756082.aspx
This chapter will show you how to read and write the assembly code under the MIPs system. The MIPs assembly code looks very different from the actual code because of the following reasons:
1. MIPS assembler compiler provides a large number of predefined macro commands (extra macro-instruction ). Therefore, the instruction set of the compiler is much larger than the instruction set actually provided by the CPU.
2. There are many pseudo operators in the MIPs assembly code, which are placed at the beginning and end of the Code. They are used to predefine common data, control the order of commands, and control code optimization. They are usually called "ctictives" or "pseudo Ps ".
3. In practical applications, assembly code is often submitted to the compiler for compilation only after being processed by the C language Preprocessor. The C language Preprocessor replaces the Macros in the assembly code with their own header file definitions. This makes compilation code easier to write.
Before you proceed, it is best to go back and review the content of chapter-2, including the structure, data type, and addressing mode of the Low-layer machine code. ($: The pipeline knowledge is worth reviewing, mainly looking at the damn delay points delay-slot .)
9.1 a simple example
We still use the example we have seen in chapter-8: The C library function strcmp (1 ). This demonstration focuses on the symbols necessary for the Assembly syntax, as well as some hand-scheduled code.
Int
Strcmp (char * a0, char * A1)
{
Char t0, t1;
While (1 ){
T0 = a0 [0];
A0 + = 1;
T1 = a1 [0];
A1 + = 1;
If (t0 = 0)
Break;
If (t0! = T1)
Break;
}
Return (t0-t1 );
}
The running speed of this Code is relatively low for the following reasons:
1. Each loop goes through two conditional branch and two extraction commands (load), and we do not have branch delay-slot) and put enough commands ($: equivalent to the nop action of the cpu at the delay-slot, thus affecting the efficiency, see 1.5.5 programmer-visible pipeline effects ).
2. Each loop only compares one byte, making the loop too frequent and inefficient ($: Because the branch (B *) and redirect (j *) commands will refresh the pipeline, ).
Let's modify this code: first, expand the loop, and compare two bytes in each loop. Then, adjust a load command to the end of the loop-this is just a small trick, in this way, we can try to put valid commands at each branch delay-slot and load delay-slot.
Int
Strcmp (char * a0, char * a1)
{
Char t0, t1, t2;
/* Because the first load is adjusted to the end of the loop, the value must be obtained first */
T0 = a0 [0];
While (1 ){
T1 = A1 [0];/* first byte */
If (T0 = 0)
Break;
A0 + = 2;/* $: branch delay-slot */
If (T0! = T1)
Break;
/* 2nd bytes. above, we have added A0 to 2, so here is [-1] */
T2 = a0 [-1];/* $: branch delay-slot */
T1 = a1 [1];/* do not add a1 to 2 first, and leave it to the delay-slot below */
If (t2 = 0)
Return t2-t1;/* the flag in the assembly code below. t21 */
A1 + = 2;/* $: branch delay-slot */
If (t1! = T2)
Return t2-t1;/* the flag in the assembly code below. t21 */
T0 = a0 [0];/* $: branch delay-slot */
}
/* Mark. t01 in the assembly code below */
Return (t0-t1 );
}
OK. Now let's turn this code into a compilation.
# I nclude <mips/asm. h>
# I nclude <mips/regdef. h>
LEAF (strcmp)
. Set nowarn
. Set noreorder
Lbu t1, 0 (a1 );
1:
Beq t0, zero,. t01 # load delay-slot
Addu A0, A0, 2 # branch delay-Slot
BNE T0, T1,. t01
Lbu T2,-1 (A0) # branch delay-Slot
Lbu t1, 1 (a1) # load delay-slot
Beq t2, zero,. t21
Addu a1, a1, 2 # branch delay-slot
Beq t2, t1, 1b
Lbu t0, 0 (a0) # branch delay-slot
. T21:
J ra
Subu v0, t2, t1 # branch delay-slot
. T01:
J ra
Subu v0, T0, T1 # branch delay-Slot
. Set reorder
End (strcmp)
Even without all the scheduling. There are a lot of interesting things. Let's take a look.
# I nclude
This is a good idea: the C language Preprocessor CPP defines constants and introduces some predefined text macros ($: Text-subsitution macro, is the above leaf, end and so on ). The above Assembly file does this. Here, we use CPP to embed the two header files into the assembly code file before submitting the code to the aggreger. MIPs/ASM. h defines the macro leaf and macro end (see below), MIPS/regdef. h defines the common registers (conventional name), such as T0 and A1 (Section 2.2.1 ).
Macro
Here we use two macro definitions: leaf and end. Their definitions in MIPS/ASM. h are as follows:
# Define leaf (name )/
. Text ;/
. Globl name ;/
. Ent name ;/
Name:
Leaf is used to define a simple subfunction (simple subroutine). If a function does not call other functions in the body, it is relative to the entire call tree, this function is called a piece of "leaf" on the tree, so it is named "leaf ". Relatively, a function that needs to call other functions is called "nonleaf". The nonleaf function must do a lot of troublesome things, such as saving registers and returning addresses, however, it is seldom necessary to write a nonleaf assembly code by yourself ($: This is usually written in C ). Note the following:
. Text indicates that the code written in assembly should be placed in the ". Text" section, and ". Text" is the code segment of the C language program.
. Globl declares "name" as a global variable. It exists as a globally unique symbol in the symbol table of the module ($: The global variable is unique throughout the program; local variables are unique in the function body and static variables are unique in the file ).
. Ent has no practical significance for the program. It just tells the handler to mark this as the starting point of the "name" function and provides debugging information.
. Name: name the address where it is located as "name" as the output of assmbler. Function calls with the name "name" start from this address.
END defines the information required by two reducers, neither of which is required.
# Define END (name )/
. Size name,.-name ;/
. End name
. Size indicates that the size (number of bytes) of the "name" function body is listed together with the "name" symbol in the symbol table.
. End indicates the end of the function. Debugging information.
The. set pseudo operator (directive) is used to tell assembler how to compile.
In this example ,. noreorder indicates that re-sorting of code is prohibited, so that the Code strictly maintains the order of writing. Otherwise, MIPS Explorer will try to re-sort the code-fill the delay-slot for better running efficiency. Nowarn asked the programmer not to bother pointing out the places that should be reordered. I believe the programmer has handled these issues well. This is usually not a good idea-unless you are sure you are correct. Basically, this is an unnecessary ve.
Labels: "1:" is a digital label, which is treated as a ** local ** label by most reducers. A label like "1:" can be used as much as you want in the program: You can use "1f" to reference the next "1:" In the reference :"; use "1b" to reference the previous "1 :". This is very common.
Instructions: the order of some commands has unexpected problems. You must pay attention to them .. Set noreorder this ctive ve makes the delay-slot issue very sensitive and prone to problems. We must ensure that the load data is not immediately used by the next command. For example:
Bne t0, t1,. t01
Lbu t2,-1 (a0)
...............
. T01:
J ra
Subu v0, t0, t1
Here, t0 cannot be used in the lbu t2,-1 (a0) statement, because t0 is used in the next command subu v0, t0, t1.
Well, I have read an example. Let's look at some syntax.
9.2 Syntax Overview
In appendix B, you can find the syntax list of the MIPS assembler. Most compilers of other vendors also follow the rules of this list. Of course, the specific meanings of a few direve ve may be slightly different. If you have used javaser on unix-like systems before, you should be familiar with this list.
9.2.1 Layout, Delimiters, and Identifiers
First, you must be familiar with the C language. If you are familiar with C, note that there are some differences between assembly code and C code.
Assembly Code separates behavior, with a line feed (end-of_line) representing the end of a directive or pseudo operator direve ve. You can also write multiple commands or pseudo operators in a row, as long as they are separated.
The line starting with "#" is a comment, and the comment er ignores it. But ** do not place "#" on the leftmost side of the line **: This will activate the C preprocessor cpp (C preprocessor), and sometimes you may use it. If you are sure that your code will be preprocessed by the C Preprocessor, you can use the C-style annotation method in your Assembly Code: "/*… */", Can span multiple lines, as long as you are willing.
The identifiers of variables and labels can be arbitrary-as long as they are legal in the C language, they can even contain "$" and "."
In the code, you can use 0 ~ The number between 99 is regarded as a label, which is considered as a temporary symbol, so you can reuse the same number as a label in the code. In a branch instruction, "1f" points to the next "1:", while "1b" refers to the forward "1 :", in this way, you don't have to worry about the jump and loop names that you can write at will. You can save these names to name those subprograms and those key jumps.
MIPS/SGI proceser provides the conventional name ($: zero, t0 ,~, (Ra), so you must use C preprocessor to pre-process your assembly code. Therefore, you need to include the header file mips/regdef. h In the code. Although the standard aggreger can usually recognize these registers, it is better not to press the Register on it for the sake of code universality.
The assembler locating counter points to the address of the current instruction being compiled. You can reference the value of the locating counter of the assembler in the compilation code. Identifier "." The value of the current position counter of the operator. You can even perform limited operations on it. In the context, the label (or other symbol relocatable symbol) will be replaced with its address.
($: Similar to adds r0, pc, and symbol address-(in arm -(. + 8 .)
Fixed characters and strings are defined in the same way as C.
9.3 command Rules General Rules for Instructions
Mips Explorer allows some simple instructions. Sometimes, the operand you provide is less than what the machine code requires, or the machine code requires a register while you use a constant. In some cases, the operator will also allow this writing, and automatically adjust. You will find that this situation is very frequent in real assembly code. We will discuss this issue in this section.
9.3.1 operation commands between registers
The Mips Operation Command has three operands. Arithmetic arithmetical or logical commands have two inputs and one output. For example, Rd = rs + rt is written as addu rd, rs, and rt.
The three registers can be repeated (such as addu rd, rd, rd ). In the cpu (for example, intel386) command of CISC-style, there are only two operands. Mips Explorer also supports this style of writing. The destination register can also be used as a source operand: for example, addu rd and rs, which are the same as addu rd, rd, and rs, will be automatically converted to the latter by the receiver.
There are some pseudo commands unary operation, such as Neg and not, in the instruction set provided by Mips operator. These pseudo commands are actually a combination of one or more machine commands. For these commands, the aggreger accepts a maximum of two operands. Negu rd and rs are actually converted to subu rd, zero, rs, and not rd will be converted to or rd, zero, rs.
The most common inter-register operation of register-register operation is move rd and rs. This command is actually or rd, zero, rs.
9.3.2: Operation Command with immediate count
In the assembler and machine language, constants embedded in commands are called the immediate number of immediate values. Many Mips arithmetic and logical commands have another form in which the rt register is replaced by a 16bit immediate number. During the internal operation of the cpu, the immediate number will be extended to 32bit, which may be the symbol extension sign-extend ($: Use the leftmost bit (bit15) fill up the extended 16 bits), or zero-extended zero-extend ($: Fill the extended 16 bits with 0)-depending on the specific instructions. Generally, arithmetic commands perform sign-extended sign-extend, while logical commands perform zero-extended zero-extend.
In terms of the concept of machine commands, even if the same operation is executed, whether the operand contains the difference of the immediate number will lead to two different commands (such as add and addi ). However, for programmers, there is still no need to specifically distinguish the instructions containing the immediate number. The aggreger will find them and convert them. For example:
Addu $2, $4, 64 --------> addiu $2, $4, 64
If the number is too large and exceeds the range expressed by 16 bits, the machine code will not be able to accommodate it. At this time, the handler will help us again: it will automatically load the instant count to "compile the temporary register into ER temporary register" at/$1, and then perform the following operations:
Add $4, 0x12345 ---------> li at, 0x12345
Add $4, $4,
Note that the "li" (load immediate) command here cannot be found in the machine instruction set provided by the cpu. This is a commonly used macro command used to load a 32-bit integer into the register, instead of worrying about how to implement this action:
When the 32bit integer is between-32k ~ + Between 32 KB, the consumer er uses the addiu command with the zero register to implement "li ";
When 16-31bit is 0, the ori command is used to implement "li ";
When 0-15 bit is 0, the lui command is used to implement "li". ($: The operation commands (ori, addiu) are faster than the access commands (sw, lui)
If none of the above conditions is true, you have to use lui/ori commands to implement "li:
Li $3,-5 ------> addiu $3, $0,-5
Li $4, 08000 ------> ori $4, $
Li $5, 120000 ------> lui $5, 0x12
Li $6, 0x12345 ------> lui $6, 0x1
Ori $6, $6, 0x2345
9.3.3 about 32/64-bit commands
We mentioned earlier (2.7.3) that we can extend the machine code of the 32-bit command to 64-bit to ensure that the 32-bit program (misii) can run normally on the old machine.
9.4 address mode
As mentioned above, only one address mode is supported on the mips cpu Hardware: Register base address + immediate number offset base_reg + offset, the offset must be between-32768 and + 32767 (16-bit signed integer can represent the range ). However, the volume er supports the following address modes:
Direct: The data label or external variable name provided by you.
Direct + index: an offset, plus the label address pointed by the register.
Constant: A number processed as a 32-Bit absolute address.
Register indirect: a special form of Register plus offset: the offset is 0.
9.5 assembler ctictives
All the commands in MIPS are inserted in the 32bit space, leading to an obvious problem: access a definite/Embedded instruction ????? (Compiled-in location) the memory address usually takes at least two commands. For example:
Lw $2, addr ------------- à lui at, % hi (addr)
Lw $2, % lo (addr) ()
In a large number of programs that use global or static variables, this defect often leads to bloated and inefficient compiled code.
The early MIPS compiler introduced a technology to make up for the above defects. This technology has been used by the later MIPS compilation toolchain, it is usually called "relative addressing of global volume Pointers" gp-relative address. this technology requires that compiler, javaser, linker, and runtime startup code combine to aggregate the 'Min' variables and constants in the program into an independent memory interval; then set register $28 (usually known as global pointer or gp) to point to the center of the range (linker generates a special symbol _ gp whose address is the center of the range, the activation code loads the _ gp address to the gp register, which is completed before the first load/store command is run ). as long as the global variables, static compilation volumes, and constants do not occupy a 64 k space, the point of the data relative to this range cannot exceed plus or minus 32 k (the offset is 15bit + the symbol bit is 1bit, see mips machine code format), so we can complete the load/store operations on them in a command:
Lw $2, addr ------ à lw $2, addr-_ gp ()
One problem is that when compiling modules that are independent from each other, compiler and aggreger determine which variables need to be addressed through gp. The common practice is to keep all variables smaller than a specific length (usually 8 bytes) objects are placed in this range, which can be controlled by the "-G n" option of compiler/assembler. in particular, "-G 0" will cancel the gp-relative addressing mode.
The above mentioned gp-relative addressing is a very useful technique, but there are some "traps" worth noting in use. when declaring the global volume in assembly code, you 'd better be careful:
Writable, initialized small data must be explicitly declared in. sdata segment. (In the word "small object", "small" means that the length of the "mentioned above" is less than 8 bytes ")
The length of a global object must be specified.
. Comm. smallobj, 4
. Comm. bigobj and 100
When declaring a small external variable, you must also specify its length.
Extern smallext, 4
Most schedulers do not perform auxiliary processing on object declarations (for example, specify the object length ).
The global variables in C code must be declared in all modules that use it. For external queues, You can explicitly point out its length, such:
Int cmnarray [narry];
You can also leave it unspecified:
Extern int exarray [];
Sometimes, the program running method (Environment) determines that the GP-relative addressing method cannot be used. for example, in some real-time operating systems, there are many programs (PROM monitor) in the Curing Environment that use a separate link code to implement kernel, applications directly use sub-functions (rather than common system calls) to call them to the kernel. in this case, an appropriate method cannot be found to make the GP register in two small data segments of the kernel and application. switch back and forth in sdata, so the "-G 0" option must be used to compile one of the kernel and the application (no need to do both.
When you use the "-G 0" option to compile the module, the library that needs to be connected to the module should also use the "-G 0" option to compile. whether the information should be placed in. on the sdata issue, the module and library declarations should be consistent with each other. If there is a conflict, linker will not be able to determine whether the data should be placed in a small data segment or a common data segment, at this time, linker will give a strange and worthless error message.
9.5 assembler ctictives
We mentioned "ctictive" in the beginning. You can also find its list in appendix B, but it is not detailed.
9.5.1
The names of common data segments and code segments and their support may be different in different compilation tool chains. I hope most of them will support at least General MIPS generic segments, as shown in Figure 9.1.
In the Assembly Code, select the segment as follows:
. Text,. rdata, and. data
Simply put the appropriate segment name before data and instructions, as shown below:
. Rdata
Msg: asciiz "hello world! /N"
. Data
Table:
. Word 1
. Word 2
. Word 3
. Text
Func: sub sp, 64
..................
. Lit4 and. lit8: Implicit floating point constant segment floating-point implict constants
You cannot write these segments like directives. these are read-only data segments implicitly created by the consumer, used to place li. s and li. the floating point constant type parameter in the d macro command. some reducers and linker merge the same constants to save space.
. Bss,. comm .., and. lcomm data
This segment name does not need to be used as direve VE. It is used to collect all uninitialized data declared in the C code. A feature of C is that different modules can have definitions of the same name. as long as no more than one file is initialized .. the BSS segment is used to collect data that has not been initialized in all modules. FORTRAN programmers can think of this as in FORTRAN. common section, although the names are different.
You must declare the length (in bytes) of each data item. When the program is linked, it can get enough space (the maximum value in all declarations ). if any module declares it in the initialized data segment, these lengths will be used and the following declaration will be used:
. Comm. dbgflag, 4 # global common variable, 4 bytes
. Lcomm. Sum, 4 # local common variable, 8 bytes
. Lcomm. array, 100 # local common variable, 100 bytes
The "uninitialized" statement is not accurate: although these segments are not part of the compiled target file, before executing your program, run-Time startup code or the operating system will. BSS segment erasing --------- many C Programs depend on this feature.
. Sdata, small data, and. sbss
These segments are compiled with toolchains for separate placement of small data objects. data and. BSS segment. the MIPs compilation tool chain is used to perform efficient load/store operations on a compact small data object segment. The principle is to save a data pointer in the GP register, for more information, see Chapter 9.4.1.
Note that ,. sbss is not a legal ve; put in. the information in the sbss segment meets two conditions: 1. Use. comm or. lcomm Declaration; 2. the length is smaller than the length specified by the "g N" Compilation option (8 bytes by default ).
. Section
Start a segment with any name and provide a flag (which can be provided in code or toolkit? Which are Object Code specify and probably toolkit specific). view your toolkit instruction manual ,????????????????????
The structure shown in 9.1 may be suitable as a curing program Rom program running on bare-metal bare CPU. Read-Only segments tend to be placed away from the memory location in the lower erasable range.
Heap and stack are not really isolated by the hacker or linker. Generally, they are run by the operating system ??????? Run-Time System Initialization and maintenance. the stack is defined by setting the SP register to the maximum address (8 byte alignment) available memory of the program. The stack is defined by a global pointer variable similar to the malloc function, it is usually initialized as the end symbol, which is assigned by linker as the highest position of the declared variable.
Special symbols
Figure 9.1 shows some symbols automatically declared by linker so that the program can locate the start/end position of each segment. this was initially just a habit, and later developed on Unix systems, some of which were unique in the MIPs environment. some or all of them may be defined in your toolkit Manual. The following @ symbol will definitely be defined:
Symbol standard value
_ Ftext code segment start point
Etext @ code snippet end point
_ Fdata Data Segment start point
Edata @ Data Segment end point
_ Fbss uninitialized segment start point
End @ end point of uninitialized segments
(End is usually the end point of the program image)
9.5.3 data definition and alignment
After selecting the correct section, you need to use the direve ve mentioned below to define the data object itself.
. Byte,. Half,. Word, and. DWORD
These direve ve generate Integers of 1, 2, 4, 8 byte length (some tool chains ----- even 64-bit ------- are not provided. DWORD direve VE ). the list of values can be separated by commas (,). You can add a colon to the list of values and follow a repeated count to indicate several identical values in a row, as follows (WORD = 4 byte ):
. Byte 3 #1 byte: 3
. Half 1, 2, 3 #3 halfwords: 1 2 3
. Byte 3 #5 words: 5 5 5 6 7
Note that the data location (relative to the start of the segment) is automatically aligned to the appropriate boundary before the data is output. if you really need to output Non-Aligned data, you must use what you want to talk about in the West. align directive.
. Float and. double
These directive output single-precision/double-precision floating point values:
. Float 1.4142175 #1 single-precision floating point number
. Double 1e + 10, 3.1415 #1 double-precision floating point number
Similar to integer processing, you can use a colon to indicate repetition.
. Ascii and. asciiz
These directive output ASCII strings with/without the end mark. The following two lines of code output the same string:
. Ascii "Hello/0"
. Asciiz "Hello"
. Align
This directive allows you to specify an alignment boundary greater than the normal requirements for the next data item ?????? Alignment. The alignment is the N power of 2.
. Align 4 # align to the 16-byte boundary (2 ^ 4)
Var:
. Word 0
If the sign (var in the preceding example) is followed by. align, the sign can still be correctly aligned. For example, the following example works the same way as the preceding example:
Var:
. Align 4 # align to the 16-byte boundary (2 ^ 4)
. Word 0
Required compact structure ???? Packed data structure. This directive allows you to cancel. half ,. the automatic alignment function of word, you can specify it as 0 alignment, it will continue to function until the next segment starts .??????
. Half 3 # correctly aligned half word
. Align 0 # Turn off the automatic alignment Function
. Word 100 # Words aligned by half words.
. Comm and. lcomm
Declare a common or uninitialized data object by specifying the object name and length.
Use. objects declared by comm are valid for all modules that have declared it. Their space is allocated by linker and the maximum value in all declarations is used. however, if any module declares it in. data ,. sdata ,. rdata, then all the length declarations will expire, and will be replaced by the initialization definition.
??????. Comm is used to avoid the following situation: a data object must be used in many files, but it has no special connection with each file, but we have to declare it in a file, which leads to asymmetry. but it does exist, because fortran defines it with such semantics, and we want to compile the fortran program through the Assembly Language (such as viewing the compiled assembly code of the fortran Program ).
Objects declared with. lcomm are local objects, which are allocated space by the consumer in the. bss or. sbss segments, but not visible outside the modules.
. Space directive increases the space count of the current segment, for example:
Struc:. word 3
. Space 120 # space of 120byte
. Word-1
For common data/code segments, the empty space is filled with 0. If the hacker allows you to declare that the content is not defined in the object file (such. bss), this space only affects the offset between consecutive symbols/variables.
9.5.4 symbol binding attribute symbol-binding attributes
Symbol (a flag in a data segment or code segment) can be adjusted to visible ?????, It can also be used by linker to compile several separated modules into a complete program.
The symbol has three levels of visibility:
Partial:
Except for the module that declares it, it is invisible to the outside and won't be used by linker. You don't have to worry about whether the same symbol is applied to other modules.
Global:
These are public symbols for linker to use. Use the. extern keyword. You can reference the global symbol in other modules without defining local space for it.
Weak global:
This obscure concept uses keywords in some tool chains. weakext implementation. it allows you to define a symbol. If a global object with the same name exists, connect it to the global object with the same name. If no global object with the same name exists, as a local object. if. if the comm segment exists, you should not use the 'weak global' concept .????
. Globl
In the C language environment, unless declared using the static keyword, the module-level data and function entries are global by default. unlike C, it is generally used in an assembly language unless it is used. globl directive is explicitly declared. Otherwise, the label is a local attribute by default. for. objects declared by comm do not need to be used again. globl, because they already have global attributes.
. Data
. Globl status # global variable
Status:. word 0
. Text
. Globl set_status # global function entry
Set_status:
Subu sp, 24
....................
. Extern
If the undefined flag in the current module is referenced, then the operator assumes that it is a global object defined in other modules (external variables ). in some cases, if the aggreger can know the length of the referenced object, it can generate more optimized code (see section 9.4.1 ). length of the external variable. extern directive to specify:
. Extern index, 4
. Externarray, 100
Lw $3, index # extract an external variable with a 4-byte (1-word) Length
Lw $2, array ($3) # extract part of the 100-byte length external variable
Sw $2, value # load an external variable with an unknown length
. Weakext
Some reducers and tool chains support the concept of weak global, which allows you to specify a temporary binding (binding, a concept used for connection, for a symbol, the correspondence between the symbol and its memory address ?????), If a normal global (strong global) object definition exists, it overwrites the previous weak global binding. For example:
. Data
. Weakext errno
Errno:. word 0
. Text
Lw $2, errno # may use local definitions or external definitions.
If no other module uses. globl to define errno, then this module ------ there are other modules ------ will use the partial definition of errno in the code above.
Another possible use is to declare a local variable with a name, and declare another weak global identity with another name.
. Data
Myerrno:. word 0
. Weakext errno, myerrno
. Text
Lw $2, myerrno # Always use the above partial definition
Lw $2, errno # may use local definitions or other
# (External definition)
9.5.5 function directive
This article from the CSDN blog, reproduced please indicate the source: http://blog.csdn.net/menuconfig/archive/2007/08/23/1756082.aspx