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;
}
这些是来自SystemC库的systemc\kernel\sc_simcontext.cpp
和systemc\communication\sc_prim_channel.h
的摘录。
在上述代码的多次迭代后,出现了错误。调用m_prim_channel_registry->perform_update()
会抛出0xC0000005: Access violation writing location 0x0F4CD9E9.
异常。
只有在Release配置下构建应用程序时才会发生这种情况。
查看汇编代码,我发现函数sc_prim_channel_registry::perform_update()
已内联,并且内部函数调用m_update_array[i]->perform_update()
似乎破坏了调用函数的堆栈帧。
当执行m_update_last = -1;
时,&m_update_last不再指向有效的内存位置,因此抛出异常。
(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
m_prim_channel_registry
的地址在 [ebp-18h] 和 eax 中,而 [eax+28h] 是 m_update_last
。
在调用 perform_update()
内部之前,在观察窗口中查看 esp 和 ebp,我发现:
ebp-18h 0x0022fd5c unsigned int
esp 0x0022fd60 unsigned int
这很奇怪。它们之间的差异只有4个字节,下一个推到堆栈的操作将使它们相等并覆盖[ebp-18h]![ebp-18h]保存了this->m_prim_channel_registry的副本。调用1034D9BE call dword ptr [eax+14h]时,由于它向堆栈中推送数据,导致了ebp-18h内容的破坏。看起来堆栈已经增长(向下),过度增长导致了前一个帧的破坏。
我的问题是:
- 我是否正确分析了该问题?我有遗漏吗?
- 什么原因会导致这样的破坏?我认为问题与编译器或SystemC库无关,可能是在其他地方早些时候发生的。
- 有哪些调试技术可以解决这种破坏?
更新
我相信我找到了问题,但我不能说我完全理解这个问题。
在同一个函数 (sc_simcontext::crunch
) 中调用外部的 perform_update()
的地方,也会调用systemc方法:
// 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();
}
这些方法是之前注册的延迟函数调用。
其中一个方法通过执行
ret 4
返回,从而在每次调用时缩小堆栈帧,直到发生上述描述的损坏。
我是如何管理注册受损系统c方法的?
显然,当f是模块的虚函数时,使用SC_METHOD(f)
是个坏主意。 这样做会导致调用不同的、不相关的“随机”函数。
我不确定为什么会以这种方式发生,也不确定为什么存在这种限制。此外,我不记得看到任何关于将虚成员函数用作systemc方法的警告,但这绝对是问题所在。在SC_METHOD调用中调试方法注册时,我可以看到内部指向不同于SC_METHOD宏给出的函数的函数指针。
为了解决问题,我调用了SC_METHOD(wrapper_f)
,其中wrapper_f
是模块的简单非虚拟成员函数,它只调用原始虚拟函数f
。就是这样。