企业即时通讯 - Enterprise Instant Messenger

局域网聊天工具,文字讯息、文件发送、语音通讯、高清视频通讯、远程桌面控制。

用户操作
[即时聊天] [发私信] [加为好友]
FreeEIM StudioID:i_like_cpp
972409次访问,排名32,好友4人,关注者7人。
i_like_cpp的文章
原创 888 篇
翻译 4 篇
转载 69 篇
评论 1148 篇
FreeEIM Studio的公告
最近评论
ScanerKi:#include <stdio.h>

int asm(int s)
{
int t=0;
__asm
{
mov eax, DWORD PTR [ebp+8] ;把s的值传给eax
mov t, eax ;把eax的值传给t
}
printf("- %d……
li_delong:谢谢
li_delong:谢谢
li_delong:谢谢
XUETUJIAN:
文章分类
收藏
相册
EIM 截图
相关软件图片
PI的最精确值
FreeEIM 标签
FreeEIM华军下载
XP SP2 SDK
下载FreeEIM
盛天龙
飞鸽传书
不错的网站
CPPBLOG
局域网聊天
泡妞专家
用VC写Assembly代码(RSS)
百度的Blog(RSS)
存档
软件项目交易
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

转载 用VC写Assembly代码(7)--在Visual C++中使用内联汇编收藏

新一篇: Why Learning Assembly Language is Still a Good Idea | 旧一篇: 用VC写Assembly代码

 在Visual C++中使用内联汇编

    一、 优点

    使用内联汇编可以在 C/C++ 代码中嵌入汇编语言指令,而且不需要额外的汇编和连接步骤。在 Visual C++ 中,内联汇编是内置的编译器,因此不需要配置诸如 MASM 一类的独立汇编工具。这里,我们就以 Visual Studio .NET 2003 为背景,介绍在 Visual C++ 中使用内联汇的相关知识(如果是早期的版本,可能会有些许出入)。

    内联汇编代码可以使用 C/C++ 变量和函数,因此它能非常容易地整合到 C/C++ 代码中。它能做一些对于单独使用 C/C++ 来说非常笨重或不可能完成的任务。

    内联汇编的用途包括:

    * 使用汇编语言编写特定的函数;
    * 编写对速度要求非常较高的代码;
    * 在设备驱动程序中直接访问硬件;
    * 编写 naked 函数的初始化和结束代码。

    二、 关键字

    使用内联汇编要用到 __asm 关键字,它可以出现在任何允许 C/C++ 语句出现的地方。我们来看一些例子:

    * 简单的 __asm 块:

        __asm
        {
            MOV AL, 2
            MOV DX, 0xD007
            OUT AL, DX
        }

    * 在每条汇编指令之前加 __asm 关键字:

        __asm MOV AL, 2
        __asm MOV DX, 0xD007
        __asm OUT AL, DX

    * 因为 __asm 关键字是语句分隔符,所以可以把多条汇编指令放在同一行:

        __asm MOV AL, 2  __asm MOV DX, 0XD007  __asm OUT AL, DX

    显然,第一种方法与 C/C++ 的风格很一致,并且把汇编代码和 C/C++ 代码清楚地分开,还避免了重复输入 __asm 关键字,因此推荐使用第一种方法。

    不像在 C/C++ 中的“{}”,__asm 块的“{}”不会影响 C/C++ 变量的作用范围。同时,__asm 块可以嵌套,而且嵌套也不会影响变量的作用范围。

    为了与低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意义。另外,Visual C++ 支持标准 C++ 的 asm 关键字,但是它不会生成任何指令,它的作用仅限于使编译器不会出现编译错误。要使用内联汇编,必须使用 __asm 而不是 asm 关键字。

    三、 汇编语言

1. 指令集

    内联汇编支持 Intel Pentium 4 和 AMD Athlon 的所有指令。更多其它处理器的指令可以通过 _EMIT 伪指令来创建(_EMIT 伪指令说明见下文)。

2. MASM 表达式

    在内联汇编代码中,可以使用所有的 MASM 表达式(MASM 表达式是指用来计算一个数值或一个地址的操作符和操作数的组合)。

3. 数据指示符和操作符

    虽然 __asm 块中允许使用 C/C++ 的数据类型和对象,但它不能使用 MASM 指示符和操作符来定义数据对象。这里特别指出,__asm 块中不允许 MASM 中的定义指示符(DB、DW、DD、DQ、DT 和 DF),也不允许使用 DUP 和 THIS 操作符。MASM 中的结构和记录也不再有效,内联汇编不接受 STRUC、RECORD、WIDTH 或者 MASK。

4. EVEN 和 ALIGN 指示符

    尽管内联汇编不支持大多数 MASM 指示符,但它支持 EVEN 和 ALIGN。当需要的时候,这些指示符在汇编代码里面加入 NOP 指令(空操作)使标号对齐到特定边界。这样可以使某些处理器取指令时具有更高的效率。

5. MASM 宏指示符

    内联汇编不是宏汇编,不能使用 MASM 宏指示符(MACRO、REPT、IRC、IRP 和 ENDM)和宏操作符(<>、!、&、% 和 .TYPE)。

6. 段

    必须使用寄存器而不是名称来指明段(段名称“_TEXT”是无效的)。并且,段跨越必须显式地说明,如 ES:[EBX]。

7. 类型和变量大小

    在内联汇编中,可以用 LENGTH、SIZE 和 TYPE 来获取 C/C++ 变量和类型的大? ?

    * LENGTH 操作符用来取得 C/C++ 中数组的元素个数(如果不是一个数组,则结果为 1)。
    * SIZE 操作符可以获取 C/C++ 变量的大小(一个变量的大小是 LENGTH 和 TYPE 的乘积)。
    * TYPE 操作符可以返回 C/C++ 类型和变量的大小(如果变量是一个数组,它得到的是数组中单个元素的大小)。

    例如,程序中定义了一个 8 维的整数型变量:

        int iArray[8];

    下面是 C 和汇编表达式中得到的 iArray 及其元素的相关值:

        __asm                   C                                   Size

        LENGTH iArray           sizeof(iArray)/sizeof(iArray[0])    8
        SIZE iArray             sizeof(iArray)                      32
        TYPE iArray             sizeof(iArray[0])                   4

8. 注释

    内联汇编中可以使用汇编语言的注释,即“;”。例如:

        __asm MOV EAX, OFFSET pbBuff  ; Load address of pbBuff

    因为 C/C++ 宏将会展开到一个逻辑行中,为了避免在宏中使用汇编语言注释带来的混乱,内联汇编也允许使用 C/C++ 风格的注释。

9. _EMIT 伪指令

    _EMIT 伪指令相当于 MASM 中的 DB,但是 _EMIT 一次只能在当前代码段(.text 段)中定义一个字节。例如:

        __asm
        {
            JMP _CodeLabel

            _EMIT 0x00        ; 定义混合在代码段的数据
            _EMIT 0x01

        _CodeLabel:           ; 这里是代码
            _EMIT 0x90        ; NOP指令
        }

10. 寄存器使用

    一般来说,不能假定某个寄存器在 __asm 块开始的时候有已知的值。寄存器的值将不能保证会从 __asm 块保留到另外一个 __asm 块中。

    如果一个函数声明为 __fastcall 调用方式,则其参数将通过寄存器而不是堆栈来传递。这将会使 __asm 块产生问题,因为函数无法被告知哪个参数在哪个寄存器中。如果函数接收了 EAX 中的参数并立即储存一个值到 EAX 中的话,原来的参数将丢失掉。另外,在所有声明为 __fastcall 的函数中,ECX 寄存器是必须一直保留的。为了避免以上的冲突,包含 __asm 块的函数不要声明为 __fastcall 调用方式。

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

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


四、 使用 C/C++ 元素

1. 可用的 C/C++ 元素

    C/C++ 与汇编语言可以混合使用,在内联汇编中可以使用 C/C++ 变量以及很多其它的 C/C++ 元素,包括:

    * 符号,包括标号、变量和函数名;
    * 常量,包括符号常量和枚举型成员;
    * 宏定义和预处理指示符;
    * 注释,包括“/**/”和“//”;
    * 类型名,包括所有 MASM 中合法的类型;
    * typedef 名称,通常使用 PTR 和 TYPE 操作符,或者使用指定的的结构或枚举成员。

    在内联汇编中,可以使用 C/C++ 或汇编语言的基数计数法。例如,0x100 和 100H 是相等的。

2. 操作符使用

    内联汇编中不能使用诸如“<<”一类的 C/C++ 操作符。但是,C/C++ 和 MASM 共有的操作符(比如“*”和“[]”操作符),都被认为是汇编语言的操作符,是可以使用的。举个例子:

        int iArray[10];

        __asm MOV iArray[6], BX             ; Store BX at iArray + 6 (Not scaled)
        iArray[6] = 0;                      // Store 0 at iArray+12 (Scaled)

    * 提示:在内联汇编中,可以使用 TYPE 操作符使其与 C/C++ 一致。比如,下面两条语句是一样的:

        __asm MOV iArray[6 * TYPE int], 0   ; Store 0 at iArray + 12
        iArray[6] = 0;                      // Store 0 at iArray + 12

3. C/C++ 符号使用

    在 __asm 块中可以引用所有在作用范围内的 C/C++ 符号,包括变量名称、函数名称和标号。但是不能访问 C++ 类的成员函数。

    下面是在内联汇编中使用 C/C++ 符号的一些限制:

    * 每条汇编语句只能包含一个 C/C++ 符号。在一条汇编指令中,多个符号只能出现在 LENGTH、TYPE 或 SIZE 表达式中。
    * 在 __asm 块中引用函数必须先声明。否则,编译器将不能区别 __asm 块中的函数名和标号。
    * 在 __asm 块中不能使用对于 MASM 来说是保留字的 C/C++ 符号(不区分大小写)。MASM 保留字包含指令名称(如 PUSH)和寄存器名称(如 ESI)等。
    * 在 __asm 块中不能识别结构和联合标签。

4. 访问 C/C++ 中的数据

    内联汇编的一个非常大的方便之处是它可以使用名称来引用 C/C++ 变量。例如,如果 C/C++ 变量 iVar 在作用范围内:

        __asm MOV EAX, iVar  ; Stores the value of iVar in EAX

    如果 C/C++ 中的类、结构或者枚举成员具有唯一的名称,则在 __asm 块中可以只通过成员名称来访问(省略“.”操作符之前的变量名或 typedef 名称)。然而,如果成员不是唯一的,你必须在“.”操作符之前加上变量名或 typedef 名称。例如,下面的两个结构都具有 SameName 这个成员变量:

        struct FIRST_TYPE
        {
            char *pszWeasel;
            int SameName;
        };

        struct SECOND_TYPE
        {
            int iWonton;
            long SameName;
        };

    如果按下面方式声明变量:

        struct FIRST_TYPE ftTest;
        struct SECOND_TYPE stTemp;

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

        __asm
        {
            MOV EBX, OFFSET ftTest
            MOV ECX, [EBX]ftTest.SameName       ; 必须使用“ftTest”
            MOV ESI, [EBX]. pszWeasel           ; 可以省略“ftTest”
        }

    * 提示:省略变量名仅仅是为了书写代码方便,生成的汇编指令还是一样的。

5. 用内联汇编写函数

    如果用内联汇编写函数的话,要传递参数和返回一个值都是非常容易的。看下面的例子,比较一下用独立汇编和内联汇编写的函数:

        ; PowerAsm.asm
        ; Compute the power of an integer

        PUBLIC      GetPowerAsm
        _TEXT       SEGMENT WORD PUBLIC 'CODE'
        GetPowerAsm PROC
            PUSH    EBP             ; Save EBP
            MOV     EBP, ESP        ; Move ESP into EBP so we can refer
                                    ; to arguments on the stack
            MOV     EAX, [EBP+4]    ; Get first argument
            MOV     ECX, [EBP+6]    ; Get second argument
            SHL     EAX, CL         ; EAX = EAX * (2 ^ CL)
            POP     EBP             ; Restore EBP
            RET                     ; Return with sum in EAX
        GetPowerAsm ENDP
        _TEXT       ENDS
                    END

    C/C++ 函数一般用堆栈来传递参数,所以上面的函数中需要通过堆栈位置来访问它的参数(在 MASM 或其它一些汇编工具中,也允许通过名称来访问堆栈参数和局部堆栈变量)。

    下面的程序是使用内联汇编写的:

        // PowerC.c

        #include <Stdio.h>

        int GetPowerC(int iNum, int iPower);

        int main()
        {
            printf("3 times 2 to the power of 5 is %d\n", GetPowerC( 3, 5));
        }

        int GetPowerC(int iNum, int iPower)
        {
            __asm
            {
                MOV EAX, iNum   ; Get first argument
                MOV ECX, iPower ; Get second argument
                SHL EAX, CL     ; EAX = EAX * (2 to the power of CL)
            }
            // Return with result in EAX
        }

    使用内联汇编写的 GetPowerC 函数可以通过参数名称来引用它的参数。由于 GetPowerC 函数没有执行 C 的 return 语句,所以编译器会给出一个警告信息,我们可以通过 #pragma warning 禁止生成这个警告。

    内联汇编的其中一个用途是编写 naked 函数的初始化和结束代码。对于一般的函数,编译器会自动帮我们生成函数的初始化(构建参数指针和分配局部变量等)和结束代码(平衡堆栈和返回一个值等)。使用内联汇编,我们可以自己编写干干净净的函数。当然,此时我们必须自己动手做一些有关函数初始化和扫尾的工作。例如:

        void __declspec(naked) MyNakedFunction()
        {
            // Naked functions must provide their own prolog.
            __asm
            {
                PUSH EBP
                MOV ESP, EBP
                SUB ESP, __LOCAL_SIZE
            }

            .
            .
            .

            // And we must provide epilog.
            __asm
            {
                POP EBP
                RET
            }
        }

6. 调用 C/C++ 函数

    内联汇编中调用声明为 __cdecl 方式(默认)的 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

                // 压入了 3 个参数在堆栈中,调用函数之后要调整堆栈
                ADD     ESP, 12
            }
        }

    * 提示:参数是按从右往左的顺序压入堆栈的。

    如果调用 __stdcall 方式的函数,则不需要自己清除堆栈。因为这种函数的返回指令是 RET n,会自动清除堆栈。大多数 Windows API 函数均为 __stdcall 调用方式(仅除 wsprintf 等几个之外),下面是一个调用 MessageBox 函数的例子:

        #include <Windows.h>

        TCHAR g_tszAppName[] = TEXT("API Test");

        void main()
        {
            TCHAR tszHello[] = TEXT("Hello, world!");

            __asm
            {
                PUSH    MB_OK OR MB_ICONINFORMATION
                PUSH    OFFSET g_tszAppName         ; 全局变量用 OFFSET
                LEA     EAX, tszHello               ; 局部变量用 LEA
                PUSH    EAX
                PUSH    0
                CALL    DWORD PTR [MessageBox]      ; 注意这里不是 CALL MessageBox,而是调用重定位过的函数地址
            }
        }

    * 提示:可以不受限制地访问 C++ 成员变量,但是不能访问 C++ 的成员函数。

7. 定义 __asm 块为 C/C++ 宏

    使用  C/C++ 宏可以方便地把汇编代码插入到源代码中。但是这其中需要额外地注意,因为宏将会扩展到一个逻辑行中。
为了不会出现问题,请按以下规则编写宏:

    * 使用括号把 __asm 块包围住;
    * 把 __asm 关键字放在每条汇编指令之前;
    * 使用经典 C 风格的注释(“/* comment */”),不要使用汇编风格的注释(“; comment”)或单行的 C/C++ 注释(“// comment”);

    举个例子,下面定义了一个简单的宏:

        #define PORTIO __asm        \
        /* Port output */           \
        {                           \
            __asm MOV AL, 2         \
            __asm MOV DX, 0xD007    \
            __asm OUT DX, AL        \
        }

    乍一看来,后面的三个 __asm 关键字好像是多余的。其实它们是需要的,因为宏将被扩展到一个单行中:

        __asm /* Port output */ {__asm MOV AL, 2  __asm MOV DX, 0xD007  __asm OUT DX, AL}

    从扩展后的代码中可以看出,第三个和第四个 __asm 关键字是必须的(作为语句分隔符)。在 __asm 块中,只有 __asm 关键字和换行符会被认为是语句分隔符,又因为定义为宏的一个语句块会被认为是一个逻辑行,所以必须在每条指令之前使用 __asm 关键字。

    括号也是需要的,如果省略了它,编译器将不知道汇编代码在哪里结束,__asm 块后面的 C/C++ 语句看起来会被认为是汇编指令。

    同样是由于宏展开的原因,汇编风格的注释(“; comment”)和单行的 C/C++ 注释(“// commen”)也可能会出现错误。为了避免这些错误,在定义 __asm 块为宏时请使用经典 C 风格的注释(“/* comment */”)。

    和 C/C++ 宏一样 __asm 块写的宏也可以拥有参数。和 C/C++ 宏不一样的是,__asm 宏不能返回一个值,因此,不能使用这种宏作为 C/C++ 表达式。

    不要不加选择地调用这种类型的宏。比如,在声明为 __fastcall 的函数中调用汇编语言宏可能会导致不可预料的结果(请参看前文的说明)。

8. 转跳

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

        void MyFunction()
        {
            goto C_Dest;    /* 正确 */
            goto c_dest;    /* 错误 */

            goto A_Dest;    /* 正确 */
            goto a_dest;    /* 正确 */

            __asm
            {
                JMP C_Dest  ; 正确
                JMP c_dest  ; 错误

                JMP A_Dest  ; 正确
                JMP a_dest  ; 正确

        a_dest:             ; __asm 标号
            }

        C_Dest:             /* C/C++ 标号 */
            return;
        }

    不要使用函数名称当作标号,否则将转跳到函数中执行,而不是标号处。例如,由于 exit 是 C/C++ 的函数,下面的转跳将不会到 exit 标号处:

        ; 错误:使用函数名作为标号
        JNE exit
        .
        .
        .
        exit:
        .
        .
        .

    美元符号“$”用于指定当前指令位置,常用于条件跳转中,例如:

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

    五、在 Visual C++ 工程中使用独立汇编

    内联汇编代码不易于移植,如果你的程序打算在不同类型的机器(比如 x86 和 Alpha)上运行,你可能需要在不同的模块中使用特定的机器代码。这时候你可以使用 MASM(Microsoft Macro Assembler),因为 MASM 支持更多方便的宏指令和数据指示符。

    这里简单介绍一下在 Visual Studio .NET 2003 中调用 MASM 编译独立汇编文件的步骤。

    在 Visual C++ 工程中,添加按 MASM 的要求编写的 .asm 文件。在解决方案资源管理器中,右击这个文件,选择“属性”菜单项,在属性对话框中,点击“自定义生成步骤”,设置如下项目:

    命令行:ML.exe /nologo /c /coff "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
    输出:$(IntDir)\$(InputName).obj

    如果要生成调试信息,可以在命令行中加入“/Zi”参数,还可以根据需要生成 .lst 和 .sbr 文件。

    如果要在汇编文件中调用 Windows API,可以从网上下载 MASM32 包(包含了 MASM 汇编工具、非常完整的 Windows API 头文件/库文件、实用宏以及大量的 Win32 汇编例子等)。相应地,应该在命令行中加入“/I X:\MASM32\INCLUDE”参数指定 Windows API 汇编头文件(.inc)的路径。MASM32 的主页是:http://www.masm32.com,里面可以下载最新版本的 MASM32 包。

发表于 @ 2006年10月15日 22:40:00|评论(loading...)|编辑

新一篇: Why Learning Assembly Language is Still a Good Idea | 旧一篇: 用VC写Assembly代码

评论:没有评论。

发表评论  


登录
Csdn Blog version 3.1a
Copyright © FreeEIM Studio