Multithreading 多核系统上多线程应用程序中的记录指令步进/跟踪

Multithreading 多核系统上多线程应用程序中的记录指令步进/跟踪,multithreading,debugging,windbg,trace,dbgeng,Multithreading,Debugging,Windbg,Trace,Dbgeng,我使用DbgEng为WinDbg创建了一个扩展,它记录包含在用户模式和内核模式下执行的每条指令的寄存器和堆栈状态的跟踪。 对于步骤跟踪,我使用断点(我还尝试将陷阱标志设置为记录跟踪的线程上下文,但它给出了相同的结果)。在每个步骤完成并命中断点后,将调用IDebugEventCallbacks::breakpoint(),该函数返回DEBUG\u STATUS\u BREAK。这会导致调试器在引擎循环中完成IDebugControl::WaitForEvent()函数,我会记录当前指令的状态。 跟

我使用DbgEng为WinDbg创建了一个扩展,它记录包含在用户模式和内核模式下执行的每条指令的寄存器和堆栈状态的跟踪。
对于步骤跟踪,我使用断点(我还尝试将陷阱标志设置为记录跟踪的线程上下文,但它给出了相同的结果)。在每个步骤完成并命中断点后,将调用IDebugEventCallbacks::breakpoint(),该函数返回DEBUG\u STATUS\u BREAK。这会导致调试器在引擎循环中完成IDebugControl::WaitForEvent()函数,我会记录当前指令的状态。
跟踪记录在多核系统(4个物理核、8个虚拟核)上的测试多线程应用程序中。测试应用程序在主线程之外创建6个线程。4个线程异步记录同一函数func0的跟踪。其他3个创建的线程模拟计算。
测试程序代码:

void func3();
void func2();
void func1();
DWORD WINAPI func0(LPVOID lpArg);
DWORD WINAPI循环处理(LPVOID lpArg);
void func3()
{
INTA=1;
a+=2;
a*=60;
a+=31;
printf(u8“func3\n”);
a/=2;
a*=777;
a-=31;
}
void func2()
{
INTA=1;
a+=5;
a*=41;
a+=55;
printf(u8“func2\n”);
a/=3;
a*=414;
a-=41;
}
void func1()
{
INTA=1;
a+=95;
a*=14;
a+=72;
printf(u8“func1\n”);
a/=1;
a*=42;
a-=37;
}
DWORD WINAPI func0(LPVOID lpArg)
{
func1();
func2();
func3();
返回0;
}
DWORD WINAPI循环处理(LPVOID lpArg)
{
int a=1,
b=2,
c=3;
b=2*c+1;
c=a+b*c;
a=b*c+a;
SwitchToThread();
返回0;
}
int main(int argc,char*argv[])
{
const size_t szthreadscont=6;
处理hThreads[szThreadsCount];
对于(int-IIndexitation=0;IIndexitation<15;IIndexitation++)
{
printf(“==================================迭代%i开始===============================================\n”,i退出);
hThreads[0]=CreateThread(NULL,NULL,func0,NULL,NULL,NULL);
hThreads[1]=CreateThread(NULL,NULL,LoopProcessing,NULL,NULL,NULL);
hThreads[2]=CreateThread(NULL,NULL,func0,NULL,NULL,NULL);
hThreads[3]=CreateThread(NULL,NULL,LoopProcessing,NULL,NULL,NULL);
hThreads[4]=CreateThread(NULL,NULL,func0,NULL,NULL,NULL);
hThreads[5]=CreateThread(NULL,NULL,LoopProcessing,NULL,NULL,NULL);
对于(int iIndex=0;iIndex<3;iIndex++)
{
func0(0);
}
WaitForMultipleObjects(szThreadsCount、hThreads、TRUE、INFINITE);
对于(int iIndex=0;iIndex
在大多数情况下,一切都按预期进行,我得到了如下完整记录的跟踪:

…
036|00007ff6`ad314215 call    Saek0!ILT+550(__CheckForDebuggerJustMyCode) (00007ff6`ad30122b)
Registers:
rax: 0x00000000CCCCCCCC rbx: 0x0000000000000000 rcx: 0x00007FF6AD33208C
rdx: 0x00007FF6AD30155F rsi: 0x0000000000000000 rdi: 0x0000000000A2FE18
rip: 0x00007FF6AD314215 rsp: 0x0000000000A2FD10 rbp: 0x0000000000A2FD30
r8:  0x0000000000000001 r9:  0x00007FF6AD30155F r10: 0x0000000000000000
r11: 0x0000000000000000 r12: 0x0000000000000000 r13: 0x0000000000000000
r14: 0x0000000000000000 r15: 0x0000000000000000
EFlags: 0xCCCCCCCC00000302
 
 
037|00007ff6`ad31421a mov     dword ptr [rbp+4],1     ss:00000000`00000004=????????
Registers:
rax: 0x0000000000000001 rbx: 0x0000000000000000 rcx: 0x00007FF6AD33208C
rdx: 0x00007FF6AD30155F rsi: 0x0000000000000000 rdi: 0x0000000000A2FE18
rip: 0x00007FF6AD31421A rsp: 0x0000000000A2FD10 rbp: 0x0000000000A2FD30
r8:  0x0000000000000001 r9:  0x00007FF6AD30155F r10: 0x0000000000000000
r11: 0x0000000000000000 r12: 0x0000000000000000 r13: 0x0000000000000000
r14: 0x0000000000000000 r15: 0x0000000000000000
EFlags: 0xCCCCCCCC00000202
 
 
038|00007ff6`ad314221 mov     eax,dword ptr [rbp+4]   ss:00000000`00000004=????????
Registers:
rax: 0x0000000000000001 rbx: 0x0000000000000000 rcx: 0x00007FF6AD33208C
rdx: 0x00007FF6AD30155F rsi: 0x0000000000000000 rdi: 0x0000000000A2FE18
rip: 0x00007FF6AD314221 rsp: 0x0000000000A2FD10 rbp: 0x0000000000A2FD30
r8:  0x0000000000000001 r9:  0x00007FF6AD30155F r10: 0x0000000000000000
r11: 0x0000000000000000 r12: 0x0000000000000000 r13: 0x0000000000000000
r14: 0x0000000000000000 r15: 0x0000000000000000
EFlags: 0xCCCCCCCC00000302
…
但有时调试器会跳过步骤,不会触发断点。
例如:

00007ff6`ad314215 call    Saek0!ILT+550(__CheckForDebuggerJustMyCode) (00007ff6`ad30122b)
00007ff6`ad31421a mov     dword ptr [rbp+4],1
00007ff6`ad314221 mov     eax,dword ptr [rbp+4]
假设已记录状态rip=00007ff6'ad314215,并在以下地址00007ff6'ad31421a处设置断点。之后,我继续执行程序,并期望触发地址00007ff6'ad31421a处的断点。但是,我当前的rip=00007ff6'ad314221和我使用以下参数调用了IDebugEventCallbacks::Exception(),而不是预期的点击到00007ff6'ad31421a处的断点并调用IDebugEventCallbacks::breakpoint()

Exception code: (0x80000003) == EXCEPTION_BREAKPOINT
Exception address: 0x7FF6AD314221 - address of the next instruction after the breakpoint.
threadContext.Rip: 0x7FF6AD314221 - instruction address of current thread context.
因此,系统已经在00007ff6'ad31421a处执行了该指令,但由于某种原因,调试器没有命中设置的断点,并在未设置任何断点的下一条指令(00007ff6`ad314221)上引发了异常

我还尝试检查IDebugEventCallbacks::ChangeEngineState()中正在记录其执行情况的线程的程序计数器(threadContext.rip),但从未得到一个计数器,该计数器等于被跳过的指令的地址(00007ff6'ad31421a)

因此,我的跟踪中没有地址00007ff6'ad31421a的状态:

…
036|00007ff6`ad314215 call    Saek0!ILT+550(__CheckForDebuggerJustMyCode) (00007ff6`ad30122b)
Registers:
rax: 0x00000000CCCCCCCC rbx: 0x0000000000000000 rcx: 0x00007FF6AD33208C
rdx: 0x00007FF6AD30155F rsi: 0x0000000000000000 rdi: 0x0000000000A2FE18
rip: 0x00007FF6AD314215 rsp: 0x0000000000A2FD10 rbp: 0x0000000000A2FD30
r8:  0x0000000000000001 r9:  0x00007FF6AD30155F r10: 0x0000000000000000
r11: 0x0000000000000000 r12: 0x0000000000000000 r13: 0x0000000000000000
r14: 0x0000000000000000 r15: 0x0000000000000000
EFlags: 0xCCCCCCCC00000302
 
 
037|00007ff6`ad314221 mov     eax,dword ptr [rbp+4]   ss:00000000`00000004=????????
Registers:
rax: 0x0000000000000001 rbx: 0x0000000000000000 rcx: 0x00007FF6AD33208C
rdx: 0x00007FF6AD30155F rsi: 0x0000000000000000 rdi: 0x0000000000A2FE18
rip: 0x00007FF6AD314221 rsp: 0x0000000000A2FD10 rbp: 0x0000000000A2FD30
r8:  0x0000000000000001 r9:  0x00007FF6AD30155F r10: 0x0000000000000000
r11: 0x0000000000000000 r12: 0x0000000000000000 r13: 0x0000000000000000
r14: 0x0000000000000000 r15: 0x0000000000000000
EFlags: 0xCCCCCCCC00000302
…
问题:

  • 这种行为的原因是什么
  • 我曾遇到过这样的观点,即这种行为可能是由多核系统上的执行引起的,但本判决书的作者没有提到任何有理由这样做的来源。如果情况确实如此,是否有人可以提供有关理由的链接
  • 有什么解决问题的建议吗
  • 是否有工具允许您在Windows中记录与用户模式和内核模式下状态相似的跟踪

  • 有趣的项目。1.)我在想,这2.)不仅会发生在多核系统中。3.)提前设置更多断点4.)我不知道是否使用分步调试器可以设置内部一次性临时bp并处理异常,请查看是否正在处理this@ThomasWeller谢谢你的回复。我学到了更多关于“无序执行”的知识。我运行了更多的测试,并得出结论,问题毕竟不是“无序执行”。我认为00007ff6'ad314221处的指令不可能在00007ff6'ad31421a处的指令之前执行,因为它们都引用[rbp+4]。在启动程序之前,我还尝试在所有指令上设置断点,结果也一样。我还假设,当指令被覆盖时,调试器在执行指令队列之前会注意更新指令队列。@blabb感谢您的回复。是的,我正在处理这个有趣的项目。1.)我在想,这2.)不仅会发生在多核系统中。3.)提前设置更多断点4.)我不知道是否使用分步调试器可以设置内部一次性临时bp并处理异常,请查看是否正在处理this@ThomasWeller谢谢你的帮助