安全中国首页 > 文章中心 > 编释语言
 
安全中国网友投稿专用上传FTP空间:
Ftp服务器:download.anqn.com
Ftp端口:21
用户名:anqn
密 码:anqn.com
 

《加密解密 技术内幕》 在Visual C++中使用内联汇编

更新时间:2008-4-2 0:30:11
责任编辑:果果龙
热 点:

   如果C/C++中的类、结构或者枚举成员具有唯一的名称,如果在"."操作符之前不指定变量或者typedef名称,则__asm块中只能引用成员名称。然而,如果成员不是唯一的,你必须在"."操作符之前加上变量名或typedef名称。例如,下面的两个结构都具有same_name这个成员变量: 

       struct first_type 
       { 
           
char *weasel; 
           
int same_name; 
       }; 

       struct second_type 
       { 
           
int wonton; 
           
long same_name; 
       }; 

   如果按下面声明变量: 

       struct first_type hal; 
       
struct second_type oat; 

   那么,所有引用same_name成员的地方都必须使用变量名,因为same_name不是唯一的。另外,上面的weasel变量具有唯一的名称,你可以仅仅使用它的成员名称来引用它: 

       __asm 
       { 
           
MOV EBX, OFFSET hal 
           
MOV ECX, [EBX]hal.same_name ; 必须使用 'hal'
           
MOV ESI, [EBX].weasel       ; 可以省略 'hal'
       } 

   注意,省略了变量名仅仅是为了写代码的方便,生成的汇编指令的还是一样的。 

   可以不受限制地访问C++成员变量,但是不能调用C++的成员函数。


五、寄存器使用

   一般来说,在__asm块开始的时候,寄存器是空的,不能在两个__asm之间保存寄存器的值。(这是MSDN上说的,我在实际使用时发现,好像并不是这样。不过它是说"一般",我是特殊:)) 

   如果一个函数被声明成了__fastcall,则其参数将放在寄存器中,这将给寄存器的管理带来问题。所以,如果要将一个函数声明成__fastcall,必须保存ECX寄存器。为了避免以上的冲突,在声明为__fastcall的函数中不要有__asm块。如果用了/Gr编译选项(它全局的变成__fastcall),将每个函数声明成__cdecl或者__stdcall,这个属性告诉编译器用传统的C方法。 

   如果使用EAX、EBX、ECX、EDX、ESI和EDI寄存器,你不需要保存它;但如果你用到了DS、 SS、SP、BP和标志寄存器,那就应该PUSH保存这些寄存器。 

   如果程序中改变了用于STD和CLD的方向标志,你必须将其恢复到原来的值。


六、转跳

   可以在C里面使用goto调到__asm块中的标号处,也可以在__asm块中转跳到__asm块里面和外面的标号处。__asm块内的标号是不区分大小写的(指令、指示符等也是不区分大小写的)。例: 

       void func() 
       { 
               
goto C_Dest;    /* 合法 */ 
               
goto c_dest;    /* 错误 */ 

               goto A_Dest;    /* 合法 */ 
               
goto a_dest;    /* 合法 */ 

               __asm 
               { 
                       
JMP C_Dest  ; 合法
                       
JMP c_dest  ; MSDN上说合法,但是我在VS.NET中编译,认为这样不合法

                       JMP A_Dest  ; 合法
                       
JMP a_dest  ; 合法

       a_dest:     ; __asm 标号 
               } 

       C_Dest:     /* C的标号 */ 
       
return
       } 


   不要使用函数名称当作标号,否则将使其跳到函数执行而不是标号处。如下所示: 

       ; 错误: 使用函数名作为标号 
       
JNE exit 
       . 
       . 
       . 
       exit: 
       
; 下面是更多的ASM代码 


   美元符号$用于指定当前位置,如下所用,常用于条件跳转: 

       JNE $Content$5 ; 下面这条指令的长度是5个字节 
       
JMP farlabel 
       
;$Content$5,跳到了这里 
       . 
       . 
       . 
       farlabel: 


七、调用函数

   内联汇编调用C/C++函数必须自己清除堆栈,下面是一个调用C/C++函数例子: 

       #include <stdio.h> 

       char szformat[] = "%s %s\n"
       
char szHello[] = "Hello"
       
char szWorld[] = " world"
       
void main() 
       { 
           
__asm 
           { 
               
MOV     EAX, OFFSET szWorld 
               
PUSH    EAX 
               
MOV     EAX, OFFSET szHello 
               
PUSH    EAX 
               
MOV     EAX, OFFSET szformat 
               
PUSH    EAX 
               
CALL    printf 

               //内联汇编调用C函数必须自己清除堆栈 
               
//用不使用的EBX寄存器清除堆栈,或ADD ESP, 12 
               
POP     EBX 
               
POP     EBX 
               
POP     EBX 
           } 
       } 

   注意:函数参数是从右向左压栈。 

   不能够访问C++中的类成员函数,但是可以访问extern "C"函数。 

   如果调用Windows API函数,则不需要自己清除堆栈,因为API的返回指令是RET n,会自动清除堆栈 

   比如下面的例子: 

       #include <windows.h> 

       char szAppName[] = "API Test"

       void main() 
       { 
           
char szHello[] = "Hello, world!"

           __asm 
           { 
               
PUSH    MB_OK OR MB_ICONINformATION 
               
PUSH    OFFSET szAppName    ; 全局变量用OFFSET
               
LEA     EAX, szHello        ; 局部变量用LEA
               
PUSH    EAX 
               
PUSH    0 
               
CALL    DWORD PTR [MessageBoxA]     ; 注意这里,我费了好大周折才发现不是CALL MessageBoxA
           } 
       } 

   一般来说,在Visual C++中使用内联汇编是为了提高速度,因此这些函数调用尽可能用C/C++写。


八、一个例子

   下面的例子是在VS.NET(即VC7)中C语言写的。先建一个工程,将下列代码放到工程中的.c文件中编译,无需作特别的设置,即可编译通过。

上一页 1 2 3 下一页

 
相关文章
一日一文章
 
一日一软件
一日一动画