调试堆栈损坏问题

时间:2017-01-23 08:34:42

标签: c++ callstack corruption systemc

我正在使用C ++(Visual Studio 2015)调试大型应用程序上的“访问冲突”异常。该应用程序是由几个库构建的,其中一个(SystemC)会出现问题,但我怀疑问题的根源在其他地方。

我看到的是一个函数调用,它破坏了调用者的成员函数的地址。

m_update_phase = true;
m_prim_channel_registry->perform_update();
m_update_phase = false;
inline
void
sc_prim_channel_registry::perform_update()
{
    for( int i = m_update_last; i >= 0; -- i ) {
    m_update_array[i]->perform_update();
    }
    m_update_last = -1;
}

(这些是[{1}}和systemc\kernel\sc_simcontext.cpp摘录,SystemC库的一部分)

上述代码经过多次迭代后发生错误。对systemc\communication\sc_prim_channel.h的调用会引发m_prim_channel_registry->perform_update()例外 只有在发布配置中构建应用程序时才会发生这种情况。

查看汇编代码,我看到函数0xC0000005: Access violation writing location 0x0F4CD9E9.被内联,内部函数调用sc_prim_channel_registry::perform_update()似乎破坏了调用函数的的堆栈框架
执行m_update_array[i]->perform_update()时,& m_update_last不再指向有效的内存位置并抛出异常。
m_update_last = -1;是类m_update_last的简单原生成员,类型为sc_prim_channel_registry

int

在地址 m_update_phase = true; m_prim_channel_registry->perform_update(); 1034D99E mov eax,dword ptr [esi+10h] 1034D9A1 mov byte ptr [esi+0A3h],1 1034D9A8 mov dword ptr [ebp-18h],eax 1034D9AB mov ebx,dword ptr [eax+28h] 1034D9AE test ebx,ebx 1034D9B0 js $LN163+0FEh (1034D9D0h) 1034D9B2 mov esi,eax 1034D9B4 mov eax,dword ptr [esi+20h] 1034D9B7 mov edi,dword ptr [eax+ebx*4] 1034D9BA mov ecx,edi 1034D9BC mov eax,dword ptr [edi] 1034D9BE call dword ptr [eax+14h] 1034D9C1 sub ebx,1 1034D9C4 mov byte ptr [edi+1Ch],0 1034D9C8 jns $LN163+0E2h (1034D9B4h) 1034D9CA mov esi,dword ptr [this] 1034D9CD mov eax,dword ptr [ebp-18h] 1034D9D0 mov dword ptr [eax+28h],0FFFFFFFFh m_update_phase = false; 处抛出异常 所以最后执行的指令是

1034D9D0

0F97D9CD mov eax,dword ptr [ebp-18h] 0F97D9D0 mov dword ptr [eax+28h],0FFFFFFFFh 地址位于[ebp-18h]和eax中,[eax + 28h]位于m_prim_channel_registry

在内部通话m_update_last之前,在esp和ebp的观察窗口中查看,我看到了:

perform_update()

这很奇怪。它们之间的区别只有4个字节,下一次推送到堆栈将使它们相等并覆盖[ebp-18h]!
[ebp-18h]持有 ebp-18h 0x0022fd5c unsigned int esp 0x0022fd60 unsigned int 的副本。调用this->m_prim_channel_registry时,调用堆栈会破坏ebp-18h的内容。看起来堆栈已经过度增长(向下),并破坏了前一帧。

我的问题是:

  • 我是否正确分析了这个问题?我在这里错过了什么吗?
  • 什么可能导致这样的腐败?我认为这个问题与编译器或SystemC库无关,可能是早先在其他地方发生的事情。
  • 调试此类损坏的技术有哪些?

更新

我相信我发现了问题,但我不能说我完全明白这一点 在调用外部1034D9BE call dword ptr [eax+14h]的同一函数(sc_simcontext::crunch)中,调用 systemc methods

perform_update()

这些方法是先前注册的延迟函数调用 其中一种方法是通过执行 // execute method processes sc_method_handle method_h = pop_runnable_method(); while( method_h != 0 ) { try { method_h->execute(); } catch( const sc_exception& ex ) { cout << "\n" << ex.what() << endl; m_error = true; return; } method_h = pop_runnable_method(); } 来返回,从而在每次调用时缩小堆栈帧,直至发生上述损坏。

我是如何管理注册损坏的systemc方法的呢? 当f是模块的虚函数时,使用ret 4显然是个坏主意。这样做会导致调用不同的,无关的“随机”函数。
我不确定为什么会发生这种情况以及存在这种限制的原因。此外,我不记得看到有关使用虚拟成员函数作为systemc方法的任何警告,但它肯定是问题所在。在SC_METHOD调用本身中调试方法注册时,我可以看到内部的函数指针指向与给予SC_METHOD宏不同的函数。

要解决我调用SC_METHOD(f)的问题,其中SC_METHOD(wrapper_f)是模块的简单非虚拟成员函数,它只需调用wrapper_f ,原始的虚函数。就是这样。

2 个答案:

答案 0 :(得分:3)

您可能在MSVC上遇到成员函数指针问题。

请考虑以下代码,文件main.cpp:

#include <cstdio>

struct base;
typedef void (base::*baseptr_t)();

struct base {
    void foo() { }
};

void callfoo(base *obj, baseptr_t ptr);

int main()
{
    base obj;
    std::printf("sizeof(baseptr_t)=%llu\n", sizeof(baseptr_t));
    callfoo(&obj, &base::foo);
}

和文件callfoo.cpp:

#include <cstdio>

struct base;
typedef void (base::*baseptr_t)();

void callfoo(base *obj, baseptr_t ptr)
{
    std::printf("sizeof(baseptr_t)=%llu\n", sizeof(baseptr_t));
    (obj->*ptr)();
}

在x86_64上打印:

sizeof(baseptr_t)=8
sizeof(baseptr_t)=24

在崩溃之前发生访问冲突。

这是因为MSVC为具有已知定义的类生成8字节指针,但如果类定义不可用,则必须生成24字节指针。

编译器有办法控制这种行为:

PS:我无法重现这一点,但你也可以从SystemC查看sc_process.h标题,它有以下几行:

#if defined(_MSC_VER)
#if ( _MSC_VER > 1200 )
#   define SC_USE_MEMBER_FUNC_PTR
#endif
#else
#   define SC_USE_MEMBER_FUNC_PTR
#endif

您可以尝试为构建定义此宏,在这种情况下,SystemC将在调用流程函数时尝试使用不同的策略。

PS2:成员函数指针大小可以是8,16和24字节大小,具体取决于它的层次结构,因此应该有3种方法来取消引用成员函数指针,加上每种方式都必须处理虚拟和非虚拟调用。 / p>

答案 1 :(得分:0)

看来你知道自己在做什么。

我可以给你一个建议,而不是一个解决方案,但这是我遇到过的次数,这会破坏堆栈。

检查导致损坏的功能perform_update()。它是否将大数组定义为局部变量?如果是这样,它可能超过堆栈并覆盖返回数据和其他重要数据。这是我遇到的堆栈损坏最常见的问题。

这是一个偷偷摸摸的问题,因为它取决于本地数组的大小和您拥有的堆栈数量。这在系统之间会发生变化。