【问题标题】:Msvc Inline assembly inner function callsMsvc 内联汇编内部函数调用
【发布时间】:2015-09-30 15:53:27
【问题描述】:

首先我想说我研究了这件事,但找不到任何相关的东西。

我正在 msvc 2013 上以 32 位发布模式编写一个 c++ 控制台程序。 我在其中一个文件中使用内联汇编,它工作得很好,除非我的内联汇编函数_1调用我的函数_2,当这种情况发生时,有指令被添加到函数_2,因此堆栈被损坏并且程序崩溃。 如果我停止使用呼叫,而只是“lea ebx,[eip+5]”,则推送 ebx,jmp xxxxxx,它可以正常工作。

所以在我的情况下,更具体一点,函数 2 的定义如下:

    void test()
{

_asm{
f_01758630:  // ; <= Procedure Start

    PUSH EBP
            MOV EBP, ESP
            PUSH ESI
            PUSH EDI
            mov edi, [ebp + 0x0C]
            XOR ESI, ESI
            SHR EDI, 0x2
            TEST EDI, EDI
            JLE f_0175866B
            PUSH EBX
            mov ebx, [ebp + 0x08]

    f_01758645:

        MOV EDX, DWORD PTR DS : [EBX + ESI * 0x4]
            ROL EDX, 0x10
            MOV ECX, EDX
            MOV EAX, EDX
            SHR ECX, 0x8
            SHL EAX, 0x8
            XOR ECX, EAX
            SHL EDX, 0x8
            AND ECX, 0xFF00FF
            XOR ECX, EDX
            MOV DWORD PTR DS : [EBX + ESI * 0x4], ECX
            INC ESI
            CMP ESI, EDI
            JL TERA_01758645
            POP EBX

        f_0175866B :

        POP EDI
            POP ESI
            POP EBP
            RETN//; <= Procedure End
}
}

但是,当我调试正在运行的程序时,我可以看到该功能是这样实现的:

push ebx push esi push edi push ebp mov ebp,esp push esi push edi

即 msvc 实现了 3 次推送,这可能与函数内的 _asm{} 相关,对我如何解决此问题有任何见解吗?

【问题讨论】:

  • 显示函数_1。同时尝试__declspec (naked) void test() {...}
  • 您编写程序集就像是单独编译(组装)一样,但是当成为C++函数的一部分时,编译器会在代码中添加标准函数序言和结尾,
  • 谢谢大家,rkhb 给出了完美的答案,它现在可以完美运行了。几个小时以来,这一直是个问题。再次感谢。
  • @MichaelPetch 这个简单的解决方法是使用__declspec(naked)。仅仅删除 RETN 指令是行不通的,因为 [ebp + 0x0X] 操作数仍然是错误的。最好的解决方法是删除 RETN 和所有与堆栈相关的代码,更改函数以声明参数并在程序集中引用这些参数。但这可能超出了原始海报所能处理的范围,因为他们只是从其他地方剪切并粘贴了一些反汇编的字节交换代码。
  • @rossridge 你是对的,我没有仔细查看代码,也没有注意到他正在引用 EBP,这当然会使他对堆栈中的内容的假设无效推。

标签: assembly call inline-assembly


【解决方案1】:

第一个

push    ebp
mov     ebp, esp
push    ebx
push    esi
push    edi

是函数自动生成的序言。函数的最后是结语:

pop     ebx
pop     edi
pop     esi
pop     ebx
pop     ebp
ret

您的_asm 块有自己的序言和尾声,因此代码执行了两次。更糟糕的是,_asm 块内的ret 获得了错误的返回地址,程序将崩溃。您可以通过将函数声明为naked 来避免函数序言/结语:

__declspec (naked) void test()
{
    _asm
    {
        own prolog
        ...
        own epilog
        ret
    }
}

这很危险,因为您可能会忘记保留要原样返回的寄存器,即“被调用者保存的寄存器”:EBX、EBP、EDI、ESI。在 MSVC 内联汇编中,很容易使用函数参数和局部变量,因此无需控制 Epilog 和 prolog。

看看这个例子(尽可能接近你的代码):

#include <stdio.h>

void function_2(unsigned* reg_ebx, unsigned reg_edi)
{
    _asm
    {
            mov edi, reg_edi      // take the second argument
            xor esi, esi
            shr edi, 2
            test edi, edi
            jle f_0175866b
            mov ebx, reg_ebx     // take the first argument

        f_01758645:

            mov edx, dword ptr ds : [ebx + esi * 0x4]
            rol edx, 16
            mov ecx, edx
            mov eax, edx
            shr ecx, 8
            shl eax, 8
            xor ecx, eax
            shl edx, 8
            and ecx, 0xff00ff
            xor ecx, edx
            mov dword ptr ds : [ebx + esi * 0x4], ecx
            inc esi
            cmp esi, edi
            jl f_01758645

        f_0175866b :
    }
}

void function_1 ()
{
    unsigned arr[8] = {1000,2000,3000,4000,5000,6000,7000,8000};
    int i;
    for (i=0; i < sizeof(arr)/sizeof(arr[0]); ++i) printf ("%08X ",arr[i]); puts ("");

    _asm
    {
        push LENGTH arr
        lea eax, arr
        push eax
        call function_2
    }

    for (i=0; i < sizeof(arr)/sizeof(arr[0]); ++i) printf ("%08X ",arr[i]); puts ("");
}

int main ( void )
{
    function_1();
    return 0;
}

【讨论】:

    猜你喜欢
    • 2012-02-07
    • 1970-01-01
    • 1970-01-01
    • 2011-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多