首页
社区
课程
招聘
[原创]让你的MASM支持__fastcall调用方式
发表于: 2006-2-3 01:21 10823

[原创]让你的MASM支持__fastcall调用方式

2006-2-3 01:21
10823

让MASM支持__fastcall调用方式
作者:thebutterfly(Cloud)
熟悉逆向工程和破解的朋友都知道, 调用函数是要遵循一定的调用约定的. 常见的调用约定有C调用约定(__cdecl), 标准调用约定(__stdcall), Pascal调用约定, 快速调用约定(__fastcall)等几种. 在这些调用约定中, MASM对前三者都有很好的支持, 唯独对__fastcall调用约定不支持, 这不能不说是一种遗憾. 正因为这个, MASM在处理__fastcall类型的函数时显得相当不便, 例如在写内核模式驱动程序时有一个相当常见的函数IofCompleteRequest, 普通方法还真拿它没辙: 首先函数原型声明就是一个大问题, 普通的声明方法结果都是"Error xxxx:Cannot resolve external symbol yyyyy"; 其次是调用的语法, invoke显然是行不通的, 它只会把参数压栈然后一个call了事, 而这不是__fastcall的形式. 也同样是因为不支持__fastcall, 才使得汇编和其他语言(例如C)混合编程时一旦涉及__fastcall就举步维艰.

穷则思变, 我们都希望采取某些策略, 使MASM支持__fastcall调用方式, 换句话说就是使MASM能够定义和调用__fastcall类型的函数.

要达到这个目标,我们首先要了解(标准)__fastcall调用的特点.

__fastcall的特点是: 第1个参数放入ecx寄存器, 第2个参数放入edx寄存器, 如果还有参数则自右向左依次压栈; 由被调者负责维护堆栈平衡(清除压入的参数);如果函数有返回值则把返回值存放在eax寄存器中.  函数名的修饰特点是: 在函数名前加@, 后面加@并跟4*参数个数.例如有两个参数的IofCompleteRequest被修饰为:"@IofCompleteRequest@8".

我们先看看如何在汇编语言内部处理__fastcall函数. 假设我们要用汇编定义并调用这样一个函数:
int __fastcall AddNum(int x, int y, int z) {
        return x + y + z;
}
如何定义这个函数? 容易看出有3个参数, 根据__fastcall调用的特点, x应当存入ecx寄存器中, y则进edx, 还有一个参数z则采用堆栈传递, 函数执行完毕应当自己清理压入的参数(retn 4返回), 不难得到我们的初始想法如下:

AddNum        PROC        z                ;这里x, y不见了,因为它们都跑寄存器里去了
        mov        eax, ecx        ;ecx即为x参数
        add        eax, edx        ;edx即为y参数, 这儿计算x + y
        add        eax, z                ;最后加上z, 此时eax中为返回值x + y + z
        retn        4                ;自己清除z参数
AddNum        ENDP

如何调用它? 前面已经说过,invoke是肯定不行的, 因为它只会把参数一股脑儿全部压栈, 不符合__fastcall的特点. 我们只能采用原始的方式, 也就是写一堆处理参数的代码然后一个call xxx. 根据__fastcall的特点, 我们得到调用代码如下:

.....                        ;其他代码
mov        ecx, 实参x        ;ecx中存放实参x(第1参数)
mov        edx, 实参y        ;edx中存放实参y(第2参数)
push        实参z                ;根据调用特点, z应当通过栈传递
call        AddNum                ;调用
.....                        ;其他代码, 如处理返回值等. 调用者自己不必操心堆栈的平衡

经过试验, 这似乎是可以的, 不仅没有语法错误, 没有破坏堆栈, 而且反汇编和调用的结果也十分正确, 问题似乎已经解决了.

然而, 进一步的考察打碎了我们的迷梦, 我们做这样一个试验:把我们这个函数去和C语言做混合编程, 在C程序里如下调用:

extrn "C" int __fastcall AddNum(int x, int y, int z);        //声明外部函数
...
...
int result = AddNum(1, 2, 3);        //调用
...

编译没问题, 连接时却不对劲了, 提示 Error xxxx: An unresolved symbol "@AddNum@12"

奇怪?! 我们明明定义的是名为AddNum的函数,为什么会提示找不到@AddNum@12呢?

再做一个试验, 这次由汇编语言来调用C写的AddNum函数,代码如下:

AddNum        PROTO        z:DWORD;声明原型, 因为x,y都进了寄存器, 这儿只有一个参数z
...
mov        ecx, 实参x        ;这几句不多说了, 和上面一模一样, 关键看结果
mov        edx, 实参y
push        实参z
call        AddNum
...

结果, 提示错误如下: Error xxxx: Cannot resolve external symbol _AddNum@4

还是奇怪?! 明明声明的是AddNum函数却提示找不到 _AddNum@4 !

事实使我们意识到, 我们上面所做的并没有完全达到我们的目标, 解决了"内政"却没有解决好"外交"问题.

静下心来分析一下为什么连接会出错. 每次我们定义AddNum函数时, 连接器总提示找不到 xxxAddNumxxx 形式的符号名, 这是为什么?

对, 符号名修饰! 这是问题的关键!

(这里顺便扯几句:大部分所谓"混合编程", 都要注意三个问题, 一个是函数调用方式, 第二个是obj文件格式, 最后一个就是符号修饰了. 那些所谓的"VB和VC混合编程", "VB和汇编混合编程"能够实现的重要原因是: 它们使用的都是同一个link.exe程序! 更本质的原因是: 虽然使用的是不同的编译器, 但是编译出来的obj文件是清一色的coff格式, 正是因为格式是统一的, 才使混合编程成为可能. 而如果想把tasm和VC++混合编程就麻烦多了, 因为两种obj的格式是不同的)

根据调用类型的不同, 符号名的修饰方式是不同的. 我们在写Win32汇编程序时都会写一句:
.model        flat, stdcall
这是没有办法的, 因为Masm32头文件里面的函数声明都没有指明语言类型, 没有stdcall声明会出错(依本人愚见, 这是Masm32的一个不足之处. 因为这给Masm和其他语言的混合编程造成了不少麻烦). 这使得我们写的每一个函数都是stdcall类型的, stdcall类型的修饰方法和fastcall类似,只是打头的是下划线_而不是at号@.例如MessageBoxA被修饰为: _MessageBoxA@16 .所以在第二个试验里, 我们自己写的函数被修饰为 _AddNum@4, 而真正的那个AddNum被修饰为 @AddNum@12 , 两者当然是不同的. 同理, 在第一个试验里, 我们写的函数修饰为_AddNum@4, 而连接器却去找@AddNum@12, 当然找不到.

大家可以试一下, 用十六进制编辑器打开编译好的那个obj文件, 查找AddNum字符串, 就知道怎么回事了.

怎么办? 看来无法混合编程的一个重要原因是MASM无法自动生成fastcall函数的修饰名, 这使得我们无法调用外部的__fastcall函数, 也使得外部无法调用我们写的函数.那么似乎要解决这个问题, 唯一的办法是自己生成符号修饰名! 这又要注意的是, 函数修饰名是和函数的语言类型(调用方式)相关的, 前面提到, 我们自己写的函数由于那句.model声明都自动变为stdcall类型, 编译时会被编译器自动修饰, 如果我们写 @AddNum@12 PROC z , 编译时会被自动修饰为 _@AddNum@12@4 (前面加了下划线,后面多了@4) , 这就违反了我们的本意了. 所以我们要找一种调用方式, 满足以下3个条件, 作为fastcall的替代
1.不对函数名进行修饰(便于我们自己修饰)
2.参数由右向左压栈(否则无法直接引用参数, 只能用 [ebp + 8}之类的操作符引用)
3.由函数清理入栈参数

这样的调用方式有吗? 有的! 就是 SYSCALL调用方式!

所以我们的函数可以修改如下:

@AddNum@12        PROC        SYSCALL        z        ;注意不同了吗? 函数名被修饰了, 而且加上了SYSCALL类型
        mov        eax, ecx                        ;这几句相同
        add        eax, edx
        add        eax, z
        retn        4
@AddNum@12        ENDP                                ;照应开头, 这里同样修饰了

再去做试验1, 成功了!!!

同理, 试验2的call语句和PROTO语句要修改为
@AddNum@12        PROTO        SYSCALL        :DWORD

call        @AddNum@12

这样一来, 试验2也成功了. 问题终于被彻底解决.

总结如下:
1.如果是自己定义函数, 函数名一定要按照fastcall的规则修饰, 函数类型采用SYSCALL.
2.如果是调用其他模块的函数, call和proto的函数名也要修饰
简单吧, 只不过做的时候麻烦点.

最后补充一句, 如果要调用其他模块的函数, 除了用proto进行声明外, 用externdef 声明也是可以的, 象这样
EXTERNDEF        SYSCALL        @AddNum@12:PROC
具体采用哪种做法是个人偏好的问题
另外, 如果嫌总是写修饰名比较烦, 用TEXTEQU定义一个文本宏也是不错的注意, 象这样:
AddNum        TEXTEQU        <@AddNum@12>
这样程序里就可以直接用AddNum来调用了

********************************************************************************
********************************************************************************

为了方便定义和调用fastcall的函数,我写了一组宏, 下面简单说一下使用方法

定义一个fastcall函数:
BeginfcProc(函数名, 参数个数[, 距离]) [其他如Uses寄存器列表以及其余参数等}
示例:BeginfcProc(AddNum, 3) uses ebx edi esi  z:DWORD
结束定义:
EndfcProc(函数名, 参数个数)
示例:EndfcProc(AddNum, 3)
上面两个宏必须成对使用

调用一个fastcall的函数:
fastcall        函数名[, 参数1][, 参数2][.......]
参数支持ADDR和OFFSET操作符, 这个宏和invoke宏的语法基本相同
示例:fastcall        ExampleProc, ADDR dwNum, OFFSET szString, NULL

定义一个fastcall函数:
FcProto(函数名, 参数个数)
这个宏采取externdef定义
示例:FcProto(AddNum, 3)

********************************************************************************
以下是宏的内容, 粘贴下来保存为asm或inc文件就可以直接用了,转载请保持完整
如果有问题欢迎大家批评指正!
********************************************************************************

COMMENT >---------------------------------------------------------------------------------------
                                     FastCall Macros  Version 1.0
                                        Written By Cloud ,NJU
                                      Copyright by Cloud, 2006
这个头文件提供了对快速调用(FastCall)的支持
------------------------------------------------------------------------------------------------>
IFNDEF        _FASTCALL_M_
_FASTCALL_M_ = -1

;;------------------------------------------------------------------------------------
;;(MA)高级函数返回宏
;;
;;用法        :return 返回值[, 堆栈平衡数]
;;
;;参数        :返回值,函数的返回值
;;         平衡数,函数返回自动清栈时的参数(有几个压栈参数就写多少), 仅fastcall必需, 其他情况不要
;;
;;影响的寄存器:eax(必然)
;;
;;------------------------------------------------------------------------------------
IFNDEF        return
return        MACRO        arg, argsize
LOCAL        num, reax
        reax = 0
          IFB        <argsize>
                  num        TEXTEQU        <>
          ELSE
                  num        TEXTEQU        %(&argsize& * 4)
          ENDIF
         
              IFB <arg>
                      ret        num
              ELSE
                      IF        @SizeStr(arg) GE 8
                              IFIDNI        @SubStr(arg, 1, 7), <OFFSET >
                                      mov        eax, arg
                                      reax = -1
                              ENDIF
                      ENDIF
                      IF        @SizeStr(arg) GE 10
                              IFIDNI        @SubStr(arg, 1, 9), <LROFFSET >
                                      mov        eax, arg
                                      reax = -1
                              ENDIF
                      ENDIF
                      IF        NOT reax
                              IF (OPATTR (arg)) AND 00010000b        ;; Is a register value
                                    IFDIFI <arg>, <eax>                        ;; do not move eax onto itself
                                        mov  eax, arg
                                        ret  num
                                        reax = -1
                                    ELSE
                                            ret  num                               
                                    ENDIF
                        ELSEIF (OPATTR (arg)) AND 00000100b        ;; Is an immediate value
                                IF        (OPATTR (arg)) AND 00000001b
                                        mov        eax, arg
                                        ret        num
                                ELSE
                                            IF        arg EQ 0
                                                    xor  eax, eax
                                                    ret  num
                                            ELSEIF         arg EQ 1
                                                xor eax, eax
                                                inc eax
                                                ret num
                                        ELSEIF         arg EQ -1
                                                or eax, NOT 0
                                                ret num
                                        ELSE
                                                      mov eax, arg
                                                      ret num
                                            ENDIF
                                    ENDIF
                                    reax = -1
                          ELSE
                                    mov eax, arg
                                    ret        num
                                    reax = -1
                        ENDIF
                ENDIF       
        ENDIF
ENDM
ENDIF

;;------------------------------------------------------------------------------------
;;(MF)翻转参数列表
;;
;;用法        :$ArgRev(参数表)
;;
;;参数        :原参数列表(VARARG)
;
;;返回值:翻转后的参数列表
;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF        $ArgRev
$ArgRev MACRO args:VARARG
LOCAL         arg,y
        y         TEXTEQU         <>
        FOR         arg, <&args>
                y         CATSTR         <arg>, <!,>, y
        ENDM
        y         SUBSTR         y, 1, @SizeStr(%y) - 1
        EXITM         @CatStr(<!<>, y, <!>>)
ENDM
ENDIF
;;------------------------------------------------------------------------------------
;;(MA)快速调用
;;
;;用法        :fastcall 函数名, 参数1, 参数2, ....
;;
;;参数        :函数名+参数列表
;;
;;影响的寄存器:eax, ecx, edx(必须改变)
;;
;;------------------------------------------------------------------------------------
IFNDEF        fastcall
fastcall MACRO api:REQ, p1, p2, px:VARARG
LOCAL arg, line, recx, reax, redx
        recx = 0
        reax = 0
        redx = 0
       
        IFNB <px>
                % FOR arg, $ArgRev( <px> )
                        IF        @SizeStr(<arg>) GE 6
                                IFIDNI        @SubStr(<arg>, 1, 5), <ADDR >
                                        lea eax, @SubStr(<arg>, 6)
                                        push eax
                                        reax = -1
                                ELSE
                                        push        arg
                                ENDIF
                               
                        ELSE
                                IFIDNI        <arg>, <eax>        ;;Do not overwrite eax
                                        IF        reax
                                                line TEXTEQU %@Line
                                                % ECHO @FileCur(line) : ERROR! EAX register value overwritten by fastcall macro.
                                                .ERR
                                        ELSE
                                                push arg
                                        ENDIF
                                ELSE
                                        push        arg
                                ENDIF
                        ENDIF
                ENDM
        ENDIF
       
        IFNB <p1>
                       
                IF        @SizeStr(<p1>) GE 6
                        IFIDNI        @SubStr(<p1>, 1, 5), <ADDR >
%                                lea ecx, @SubStr(<p1>, 6)
                                recx = -1
                        ENDIF
                ENDIF
                IF        @SizeStr(<p1>) GE 8
                        IFIDNI        @SubStr(<p1>, 1, 7), <OFFSET >
                                mov        ecx, p1
                                recx = -1
                        ENDIF
                ENDIF       
                IF        @SizeStr(<p1>) GE 10
                        IFIDNI        @SubStr(<p1>, 1, 9), <LROFFSET >
                                mov        ecx, p1
                                recx = -1       
                        ENDIF
                ENDIF
                       
                IF         (NOT recx)
                        IF        (OPATTR (p1)) AND 00000100b        ;; Is an immediate value
                                IF        (OPATTR (p1)) AND 00000001b
                                        mov        ecx, p1
                                ELSE
                                        IF        p1 EQ 0
                                                xor ecx, ecx
                                        ELSEIF         p1 EQ 1
                                                xor ecx, ecx
                                                inc ecx
                                        ELSEIF         p1 EQ -1
                                                or ecx, -1                               
                                        ELSE
                                                mov ecx, p1
                                        ENDIF
                                ENDIF
                                recx = -1
                        ELSEIF        (OPATTR (p1)) AND 00010000b        ;; Is a register value
                                IFDIFI <p1>, <ecx>
                                        IFIDNI <p1>, <eax>
                                                IF        reax
                                                        line TEXTEQU %@Line
                                                        % ECHO @FileCur(line) : ERROR! EAX register value has changed.
                                                        .ERR
                                                ENDIF
                                        ENDIF
                                        mov ecx, p1
                                        recx = -1                                ;; no more ecx
                                ENDIF
                        ELSE
                                mov        ecx, p1
                                recx = -1                                                                               
                        ENDIF
                ENDIF               
        ENDIF
       
        IFNB <p2>
       
                IF        @SizeStr(<p2>) GE 6
                        IFIDNI        @SubStr(<p2>, 1, 5), <ADDR >
%                                lea edx, @SubStr(<p2>, 6)
                                redx = -1
                        ENDIF
                ENDIF
                IF        @SizeStr(<p2>) GE 8
                        IFIDNI        @SubStr(<p2>, 1, 7), <OFFSET >
                                mov        edx, p2
                                redx = -1
                        ENDIF
                ENDIF
                       
                IF        @SizeStr(<p2>) GE 10
                        IFIDNI        @SubStr(<p2>, 1, 9), <LROFFSET >
                                mov        edx, p2
                                redx = -1       
                        ENDIF
                ENDIF
                       
                IF (NOT redx)
                        IF (OPATTR (p2)) AND 00010000b        ;; Is a register value
                                IFDIFI <p2>, <edx>                ;; do not move edx onto itself
                                        IFIDNI        <p2>, <eax>
                                                IF        reax
                                                        line TEXTEQU %@Line
                                                        % ECHO @FileCur(line) : ERROR! EAX register value has changed.
                                                        .ERR
                                                ENDIF
                                        ENDIF
                                       
                                        IFIDNI        <p2>, <ecx>
                                                IF         recx                                                        ;; if ecx was used report error
                                                        line TEXTEQU %@Line
                                                        % ECHO @FileCur(line) : ERROR! ECX register value has changed.
                                                        .ERR
                                                ENDIF
                                        ENDIF
                                                       
                                        mov edx, p2
                                        redx = -1
                                ENDIF
               
                        ELSEIF (OPATTR (p2)) AND 00000100b        ;; Is an immediate value
                                IF        (OPATTR (p2)) AND 00000001b
                                        mov        ecx, p2
                                ELSE
                                        IF         p2 EQ 0
                                                xor edx, edx
                                        ELSEIF         p2 EQ 1
                                                xor edx, edx
                                                inc edx
                                        ELSEIF         p2 EQ -1
                                                or edx, -1
                                        ELSE
                                                mov edx, p2
                                        ENDIF
                                ENDIF
                                redx = -1
                        ELSE
                                mov        edx, p2       
                                redx = -1                               
                        ENDIF
                ENDIF
        ENDIF
       
        call api
ENDM
ENDIF

;;------------------------------------------------------------------------------------
;;(MF)快速调用宏函数
;;
;;用法        :返回值 = $fastcall(函数名,参数1,参数2...)
;;
;;参数        :同fastcall
;;
;;返回值:该函数的返回值(eax)
;;
;;影响的寄存器:同fastcall
;;
;;------------------------------------------------------------------------------------
IFNDEF        $fastcall
$fastcall        MACRO        api:REQ, p1, p2, px:VARARG
        fastcall api, p1, p2, px
        EXITM        <eax>
ENDM
ENDIF

;;------------------------------------------------------------------------------------
;;(MF)快速调用函数名修饰宏
;;
;;用法        :$FcallFuncNameGen(函数名,参数个数)
;;
;;参数        :函数名,该函数参数个数
;;
;;返回值:函数的修饰名
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF        $FcallFuncNameGen
$FcallFuncNameGen        MACRO          fname:REQ, argsize:REQ
LOCAL        num
        num        TEXTEQU        %(&argsize& * 4)
        EXITM        @CatStr(<@>, <fname>, <@>, %num)
ENDM
ENDIF

;;------------------------------------------------------------------------------------
;;(MF)定义一个快速调用的函数头
;;
;;用法        :[$]BeginfcProc(函数名,参数个数[,距离]) Uses 寄存器列表 除第1,2参数外其他参数
;;
;;参数        :不用多说
;;
;;返回值:无需操心
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF        $BeginfcProc
$BeginfcProc        MACRO        fname:REQ, argsize:REQ, distance
        fname        TEXTEQU        $FcallFuncNameGen(fname, argsize)
        IFB        <distance>
                EXITM        <$FcallFuncNameGen(fname, argsize) PROC SYSCALL >
        ELSE
                EXITM        <$FcallFuncNameGen(fname, argsize) PROC &distance& SYSCALL >
        ENDIF
ENDM
BeginfcProc        TEXTEQU        <$BeginfcProc>
ENDIF

;;------------------------------------------------------------------------------------
;;(MF)快速调用的函数结尾, 必须和$BeginfcProc成对使用
;;
;;用法        :$EndfcProc(函数名,参数个数)
;;
;;参数        :不用多讲
;;
;;返回值:无需操心
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF        $EndfcProc
$EndfcProc        MACRO        fname:REQ, argsize:REQ
        EXITM        <$FcallFuncNameGen(fname, argsize)        ENDP>
ENDM
EndfcProc        TEXTEQU        <$EndfcProc>
ENDIF

;;------------------------------------------------------------------------------------
;;(MA)快速调用函数的专用返回宏
;;
;;
;;用法        :fcret        返回值, 参数个数
;;
;;参数        :同return, 会自动修正清栈参数
;;
;;
;;影响的寄存器:eax(必须)
;;
;;------------------------------------------------------------------------------------
IFNDEF        fcret
fcret        MACRO        retv, argsize:REQ
LOCAL        num
        IF        argsize        GT 2
                num        TEXTEQU        %(&argsize& - 2)
        ELSE
                num        TEXTEQU        <0>
        ENDIF
        return        retv, num
ENDM
ENDIF

;;------------------------------------------------------------------------------------
;;用于声明一个外部的fastcall函数
;;
;;用法        :$FcProto(函数名,参数个数)
;;
;;参数        :不用多说
;;
;;
;;影响的寄存器:无
;;
;;------------------------------------------------------------------------------------
IFNDEF        $FcProto
$FcProto        MACRO        fname:REQ, argsize:REQ
        fname        TEXTEQU        $FcallFuncNameGen(fname, argsize)
        EXITM        @CatStr(<EXTERNDEF SYSCALL >, <$FcallFuncNameGen(fname, argsize)>, <:PROC>)
ENDM
FcProto        TEXTEQU        <$FcProto>
ENDIF

;;End of File
ENDIF ;;FastCall.inc


[注意]APP应用上架合规检测服务,协助应用顺利上架!

收藏
免费 7
支持
分享
最新回复 (6)
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
DING !!!
2006-2-3 11:08
0
雪    币: 214
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
又学到东西了,谢谢
2006-2-4 01:47
0
雪    币: 210
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
讲的真好!谢谢!学到东西了!
2006-2-4 12:47
0
雪    币: 217
活跃值: (99)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
NASM也逐渐流行起来了.灵活性和兼容性都很好.
它可以很方便地自由定义函数名.
2006-2-4 13:36
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
谢谢楼主!有更多的吗?
2006-2-4 23:28
0
雪    币: 238
活跃值: (326)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
7
FastCall 在汇编中如何定义和如何使用,我倒是很熟悉,可从没有想到象楼主那样弄个宏,谢谢楼主.
另外多说说一句,底层许多函数其实都是有两套的,楼主举的例子中. IofCompleteRequest 你将前面那个 f 拿掉, 变成 IoCompleteRequest 就变成 Stdcall 了,所以我虽然知道如何使用 FastCall 但具体使用中也是尽量避免,实在没有办法了才用那个 FastCall. 既然楼主提供了这么一个宏, 以后可以直接方便的使用 FastCall 了, 再次谢谢.
2006-2-7 15:04
0
游客
登录 | 注册 方可回帖
返回
//