【一】.在 __asm block中使用汇编语言
1.关键字__asm调用内联汇编语句
有三种方式可用
(1)__asm block 形式
例子:
// asm_overview.cpp // processor: x86 void __declspec(naked) main() { // Naked functions must provide their own prolog... __asm { push ebp mov ebp, esp sub esp, __LOCAL_SIZE } // ... and epilog __asm { pop ebp ret } }
(2)将__asm放在每句汇编指令的开头
例子:
__asm push ebp
__asm mov ebp, esp
__asm sub esp, __LOCAL_SIZE
(3)应为__asm 也是一个语句分隔符,所以可以将汇编指令放在同一行上
例子:
__asm push ebp __asm mov ebp, esp __asm sub esp, __LOCAL_SIZE
2.内联汇编指令集
VC++编译器支持Pentium 4 和 AMD Athlon的所有指令,额外的被其他目标处理器支持的指令
能够被创造用_emit 伪指令。
附:_emit 伪指令说明
_emit伪指令的MASM的DB指令相似,你能够使用_emit在代码段(text segment)的当前位置
去定义一个字节的立即数。_emit 一次只能定义一个字节,并且仅仅能够再代码段(text segment)
内定义。
例子:
#define randasm __asm _emit 0x4A __asm _emit 0x43 __asm _emit 0x4B . . . __asm { randasm }
3.再内联汇编中的MASM表达式
内联汇编能够使用任何的MASM的表达式,能够使任何操作数和操作码的组合。
4.内联汇编中的数据指令和操作
尽管__asm block能够引用C/C++的数据类型和对象(object),但是他不能定义数据对象用MASM的指令和操作,尤其,不能使用
DB,DW,DD,DQ,DT,DF 或者DUP,THIS。MASM中结构体和记录类型也是不可用的,内联汇编不接受STRUC,RECORD,WIDTH,MASK操作.
5.EVEN 和 ALIGN 指令
尽管内联汇编不支持大多数MASM的指令,但是支持EVEN 和 ALIGN 指令,这两个指令填充NOP在汇编代码中去对其数据和指定的边界,这样能够
CUP的数据访问更加高效.
6.内联汇编中的MASM宏指令
内联汇编不支持MAsm中的宏指令(MACRO, REPT, IRC, IRP, ENDM)或者宏操作符(<>, !, &, %, .TYPE)。
7.内联汇编中的段引用
再内联汇编中指定一个段只能通过寄存器,而不能通过名字(例如,段名_TEXT是不可用的),段超越必须显式的使用寄存器,如ES:[BX].
8.内联汇编中的类型和变量尺寸问题
LENGTH, SIZE 和 TYPE 操作符有一个限定的意义再内联汇编中,他们不能被使用和DUP一起(因为再内联汇编中不能使用DUP命令),但是能够使
用他们去得到C/C++变量的尺寸和类型.
*LENGTH 操作符返回一个数组的元素数目,非数组变量返回1.
*Size 操作符返回C/C++变量的尺寸,一个变量的尺寸是LENGTH与TYPE相乘的结果.
*TYPE 操作符返回C/C++类型或变量的尺寸,如果是一个数组变量返回数组中单个元素的TYPE.
例子:
int arr[8]; __asm C Size LENGTH arr sizeof(arr)/sizeof(arr[0]) 8 SIZE arr sizeof(arr) 32 TYPE arr sizeof(arr[0]) 4
9.内联汇编的注释问题
再__asm block 中可以使用汇编语言的注释
例子:
__asm mov ax, offset buff ; Load address of buff
【二】.再__asm block 中使用C/C++
概述:应为内联汇编能够与C/C++语句混合使用,他梦能够使用C/C++的变量通过名字,还有C/C++语言的其他元素.
*符号,包括标号,变量,函数名.
*常量,包括符号常量和枚举(enum)
*宏,预处理命令
*注释(包括 /**/和//)
*类型名称
*typedef名称,一般都和PTR和TYPE一起使用或者去指定结构体或联合成员
1.在 __asm block 中使用操作符
再 __asm block 中不能使用 C/C++特有的操作符,例如<<。C/C++与汇编共用的操作符,如*,是被解释为汇编操作符。
举个例子来说,[]操作符在C语言里被解释为数组的下标, C能够自动的转换数组元素的尺寸,解释为首地址+单个元素的长度*方括号内的值.
但是再__asm block中,他被看做 MASM索引操作符(index operator),解释为首地址+方括号中的值.
下面的实例显示了他们的不同。
int array[10]; __asm mov array[6], bx ; Store BX at array+6 (not scaled) array[6] = 0; /* Store 0 at array+24 (scaled) */
能够使用TYPE操作符去达到和C同样的效果
__asm mov array[6 * TYPE int], 0 ; Store 0 at array + 24 array[6] = 0; /* Store 0 at array + 24 */
2.使用C/C++符号在__asm block 中
__asm块能够引用 C/C++在作用域中的符号(包括变量名,函数名,标号,不能调用C++的成员函数)
在使用C/C++符号时有一些限制:
*每条汇编语句仅仅能够包含一个C/C++的符号。在LENGTH, TYPE, 和 SIZE表达式中则可以使用多个C/C++符号。
*在__asm block中函数引用必须先声明。否则编译器不能区别在__asm block 中的标号与函数名.
*不能使用与MASM保留字相同的符号名称(无论大小写)。
*结构体和联合类型不能别识别在__asm block中.
3.访问C/C++数据在__asm block中
在内联汇编中通过名称访问C/C++变量是十分方便的。在__asm block中能访问任何在作用域中符号。
例如,在其作用域中有一个C变量 var, __asm MOV EAX,var 存储var的值在EAX中。
如果一个类,结构体或者联合结构的成员是唯一的,在__asm block中引用他仅仅使用成员变量名,
而不用使用变量名或者typedef名在.操作符之前。如果成员名不是唯一的,无论如何,必须放置变量名或者typedef名在.操作符之前。
例子:
// InlineAssembler_Accessing_C_asm_Blocks.cpp // processor: x86 #include <stdio.h> struct first_type { char *weasel; int same_name; }; struct second_type { int wonton; long same_name; }; int main() { struct first_type hal; struct second_type oat; __asm { lea ebx, hal mov ecx, [ebx]hal.same_name ; Must use 'hal' mov esi, [ebx].weasel ; Can omit 'hal' } return 0; }
在 __asm block中能够访问C++ 的数据成员而不用去遵守访问限制,但是不能调用C++的成员函数.
4.使用内联汇编写函数
略。没啥好讲的,直接看例子
int power2( int num, int power ) { __asm { mov eax, num ; Get first argument mov ecx, power ; Get second argument shl eax, cl ; EAX = EAX * ( 2 to the power of CL ) } // Return with result in EAX }
5.使用和保存寄存器在内联汇编中
一般来说,不应该假设寄存器将会有一个指定的值在__asm blok块开始时,寄存器的值不保证在离开了一个__asm block后被保存,如果你离开
了一个asm块并开始了另一个asm块,不应该应用在上一个块中保存寄存器的值。An __asm block inherits whatever register values result
from the normal flow of control.
如果使用__fastcall调用约定,编译器传递参数使用寄存器而不是堆栈,这可能产生一个问题在应用了__asm block的函数中,因为函数无法知
道那个参数是在寄存器中。如果一个函数接受参数在EAX中,但是过后别立刻用来存储其他的值,那摩这个原始的参数就丢失了。并且,在
__fastcall约定中,必须保存ECX寄存器的值。
去避免如此的寄存器冲突,不要使用__fastcall调用约定为那些包含__asm block的函数,如果使用/Gr编译器选项指定了全局的__fastcall约定
,那摩定义每个包含__asm block的函数用_stdcall或__cdecl。
当使用__asm去写汇编语句在C/C++中,不需要去保存EAX,EBX,ECX,EDX,ESI,EDI。在使用EBX,ESI,EDI时,你强迫编译器去保存并回复这些寄存
器的值在函数的序言与结尾处.
也应该保存使用的其他寄存器(如DS,SS,SP,BP,EFLAGS)对于这个__asm block的作用域.
也应该保存ESP和EBP除非你有其他的改变他们的原因。(例如,堆栈转换)
下面这段不太好翻译,自己看吧:
Some SSE types require eight-byte stack alignment, forcing the compiler to emit dynamic stack-alignment code. To be able to
access both the local variables and the function parameters after the alignment, the compiler maintains two frame pointers.
If the compiler performs frame pointer omission (FPO), it will use EBP and ESP. If the compiler does not perform FPO, it will
use EBX and EBP. To ensure code runs correctly, do not modify EBX in asm code if the function requires dynamic stack
alignment as it could modify the frame pointer. Either move the eight-byte aligned types out of the function, or avoid using
EBX.
注意:如果在__asm block中改变了方向标志,通过STD,CLD,那摩就要保存这些标志的原始值.
6.在内联汇编中跳转到指定标号
像一般的 C/C++标号,在__asm block有函数作用域(在整个函数中可见,而不仅仅是在定义的__asm block中),汇编指令与goto语句都能跳到
标号处.
定义在__asm block中的标号不是大小写敏感的,goto语句与汇编指令能够引用整个标号而不用考虑大小写。但是C/C++代码中的标号是大小写
敏感的当使用goto语句时,使用汇编语句不用考虑大小写问题.
例子:
void func( void ) { goto C_Dest; /* Legal: correct case */ goto c_dest; /* Error: incorrect case */ goto A_Dest; /* Legal: correct case */ goto a_dest; /* Legal: incorrect case */ __asm { jmp C_Dest ; Legal: correct case jmp c_dest ; Legal: incorrect case jmp A_Dest ; Legal: correct case jmp a_dest ; Legal: incorrect case a_dest: ; __asm label } C_Dest: /* C label */ return; } int main() { }
在__asm block中不要使用C库的函数名作为标号名称。
BAD TECHNIQUE: using library function name as label
jne exit
.
.
.
exit:
; More __asm code follows
在MASM中($)符号作为当前的地址计数(current location counter)。他是当前正在被编译的指令的标号.在__asm block 中他的主要作用
是去作为一个长的条件跳转.
jne $+5 ; next instruction is 5 bytes long
jmp farlabel
; $+5
.
.
.
farlabel:
【三】.在内联汇编中调用C函数
一个__asm block能够调用C函数,包括C库函数。下面是调用printf的例子:
// InlineAssembler_Calling_C_Functions_in_Inline_Assembly.cpp // processor: x86 #include <stdio.h> char format[] = "%s %s\n"; char hello[] = "Hello"; char world[] = "world"; int main( void ) { __asm { mov eax, offset world push eax mov eax, offset hello push eax mov eax, offset format push eax call printf //clean up the stack so that main can exit cleanly //use the unused register ebx to do the cleanup pop ebx pop ebx pop ebx } }
【四】.定义__asm block作为宏
C语言的宏提供了一个简便的方式去插汇编代码进入源代码。但是那需要额外的小心因为宏被扩展到一个单独的逻辑行上(a single logical
line),去创建无错误的宏,应遵守下列规则:
*用{}包围__asm block
*放__asm 关键字在每条汇编指令的开头
*使用老式的注释(/**/)代替汇编中的注释(;)和单行注释(//).
例子:
#define PORTIO __asm \ /* Port output */ \ { \ __asm mov al, 2 \ __asm mov dx, 0xD007 \ __asm out dx, al \ }
一个__asm block写的宏能够带参数,但是不能返回值,因此不要使用这样的宏在C/C++表达式中.
【五】.内联汇编的优化问题
__asm block的存在会对优化产生一些影响。首先,编译器不会尝试去优化__asm block中的指令。第二,__asm block会对寄存器变量的存储产
生影响,编译器会避免去登记穿越__asm block的那些寄存器会被修改的变量.