CPU在汇编调试时真的会暂停吗? [英] Does the CPU really pause during assembly debugging?

查看:31
本文介绍了CPU在汇编调试时真的会暂停吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在学习 x64 汇编语言,并且与其他语言一样,我可以在调试和单步执行程序时抛出断点.断点被称为暂停程序执行,调试实用程序甚至会显示在给定时间点 CPU 寄存器中的值.但是,鉴于在计算机上运行的许多其他程序必须使用相同的 CPU 寄存器在我调试时执行,这些值怎么可能是真实值?当程序在调试期间暂停时,这些值实际上不是在 CPU 中吗?谢谢.

I've been learning x64 assembly language and, as in other languages, I can throw a breakpoint in when debugging and step through the program. The breakpoint is said to pause program execution and the debugging utility even displays which values are in the CPU registers at the given point in time. However, how is it possible that the values are the real values given there are many other programs running on the computer which must be using the same CPU registers to execute when I'm debugging? Are these values actually not in the CPU when the program is paused during debugging? Thanks.

更新:Windows 10 用户模式代码.

Update: Windows 10 user mode code.

推荐答案

操作系统根据优先级、处理器关联等各种信息调度线程.当操作系统决定给另一个线程运行的机会时,这称为上下文切换(维基百科).在上下文切换过程中,操作系统会先保存当前线程的寄存器,然后再恢复新线程的寄存器.

The operating system is scheduling threads based on various information like priority, processor affinity etc. When the OS decides to give another thread the chance to run, that's called a Context switch (Wikipedia). During the context switch, the operating system will save the current thread's registers and then restore the new thread's registers.

在内部,操作系统需要维护所有这些信息.您可以轻松拥有 1000 个线程,因此操作系统必须在内存中某处拥有 1000 倍的所有寄存器.

Internally, the operating system needs to maintain all that information. You can easily have 1000 threads, so the OS must have 1000 times all the registers somewhere in memory.

您可以安全地使用用户模式调试器并查看内核结构.由于您使用的是 Windows,我将使用 ,这是的一部分适用于 Windows 的调试工具.

You can safely use a user mode debugger and have a look at the kernel structures. Since you're on Windows, I'll use windbg, which is part of the Debugging Tools for Windows.

为了跟随,启动任何程序(记事本总是一个不错的选择)并附加 WinDbg(F6).

In order to follow, start any program (notepad is always a good candidate) and attach WinDbg (F6).

首先,让我们从 Microsoft 获取正确的信息:

First, let's get the correct information from Microsoft:

0:000> .symfix
0:000> .reload /f

这些命令将确保我们拥有正确的符号 (PDB).

Those commands will make sure that we have the correct symbols (PDBs).

接下来,让我们看一个内核线程(不是线程的用户模式部分,因为内核会调度它):

Next, let's look at a kernel thread (not at the user mode part of the thread, since the kernel schedules it):

0:000> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 CycleTime        : Uint8B
   +0x018 HighCycleTime    : Uint4B
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr32 Void
[...]
   +0x1b8 WaitPrcb         : Ptr32 _KPRCB
[...]
   +0x1f4 ThreadCounters   : Ptr32 _KTHREAD_COUNTERS
   +0x1f8 XStateSave       : Ptr32 _XSTATE_SAVE

如我们所见,维护一个线程的信息相当大(0x1f8+4 或 508 字节).

As we can see, the information to maintain a thread is quite large (0x1f8+4 or 508 bytes).

如果您阅读维基百科文章,您会发现:

If you read the Wikipedia article, you found out:

这通常存储在称为进程控制块 (PCB) 或开关帧的数据结构中.

This is usually stored in a data structure called a process control block (PCB) or switchframe.

这是偏移 1b8 处的 _KPRCB 结构.让我们看看那个:

That's the _KPRCB structure at offset 1b8. Let's look at that one:

0:000> dt nt!_KPRCB
ntdll!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
[...]
   +0x3658 Context          : Ptr32 _CONTEXT
   +0x365c ContextFlags     : Uint4B
   +0x3660 ExtendedState    : Ptr32 _XSAVE_AREA

鉴于我们切换了上下文,让我们假设 _CONTEXT 是正确的.

Given we switch the context, let's assume that _CONTEXT is the right thing to look at.

0:000> dt nt!_CONTEXT
   +0x000 ContextFlags     : Uint4B
   +0x004 Dr0              : Uint4B
   +0x008 Dr1              : Uint4B
   +0x00c Dr2              : Uint4B
   +0x010 Dr3              : Uint4B
   +0x014 Dr6              : Uint4B
   +0x018 Dr7              : Uint4B
   +0x01c FloatSave        : _FLOATING_SAVE_AREA
   +0x08c SegGs            : Uint4B
   +0x090 SegFs            : Uint4B
   +0x094 SegEs            : Uint4B
   +0x098 SegDs            : Uint4B
   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0a4 Ebx              : Uint4B
   +0x0a8 Edx              : Uint4B
   +0x0ac Ecx              : Uint4B
   +0x0b0 Eax              : Uint4B
   +0x0b4 Ebp              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0bc SegCs            : Uint4B
   +0x0c0 EFlags           : Uint4B
   +0x0c4 Esp              : Uint4B
   +0x0c8 SegSs            : Uint4B
   +0x0cc ExtendedRegisters : [512] UChar

是的,它们就是:寄存器.

So yes, there they are: the registers.

而且,知道吗?似乎我附加到 32 位进程,所以你可能得到不同的结果.不管怎样,再试一次,你会得到:

And, know what? Seems I attached to a 32 bit process, so you probably got different results. Anyway, try again and you'll get:

0:000> dt nt!_CONTEXT
   +0x000 P1Home           : Uint8B
   +0x008 P2Home           : Uint8B
   +0x010 P3Home           : Uint8B
   +0x018 P4Home           : Uint8B
   +0x020 P5Home           : Uint8B
   +0x028 P6Home           : Uint8B
   +0x030 ContextFlags     : Uint4B
[...]
   +0x078 Rax              : Uint8B
   +0x080 Rcx              : Uint8B
   +0x088 Rdx              : Uint8B
   +0x090 Rbx              : Uint8B
   +0x098 Rsp              : Uint8B
   +0x0a0 Rbp              : Uint8B
   +0x0a8 Rsi              : Uint8B
   +0x0b0 Rdi              : Uint8B
   +0x0b8 R8               : Uint8B
   +0x0c0 R9               : Uint8B
[...]
   +0x280 Xmm14            : _M128A
   +0x290 Xmm15            : _M128A
   +0x300 VectorRegister   : [26] _M128A
   +0x4a0 VectorControl    : Uint8B
   +0x4a8 DebugControl     : Uint8B
   +0x4b0 LastBranchToRip  : Uint8B
   +0x4b8 LastBranchFromRip : Uint8B
   +0x4c0 LastExceptionToRip : Uint8B
   +0x4c8 LastExceptionFromRip : Uint8B

总结:内核根据需要在它维护寄存器的地方创建尽可能多的 _CONTEXT 类型的对象".每当发生上下文切换时,内核都会保存当前寄存器并恢复其他寄存器.

Summary: the kernel creates as many "objects" of type _CONTEXT as needed where it maintains the registers. Whenever a context switch shall happen, the kernel saves the current registers and restores other ones.

在调试时,你的线程被挂起,所以它不会在 CPU 上运行.CPU 也不能停止,因为您需要能够与调试器进行交互.CPU 正在执行调试器的指令.但是,调试器会将 _KTHREAD 中的信息提供给您.

When debugging, your thread is suspended, so it will not run on the CPU. The CPU can also not be halted, because you need to be able to interact with the debugger. The CPU is executing instructions of the debugger. However, the debugger will give the information from _KTHREAD to you.

这一切都非常简单,但目前可能已经足够了.有软件和硬件上下文切换(在 OSWiki 上阅读)和其他内容.内核如何在恢复其他用户模式寄存器等之前获取其寄存器当然也很有趣,但这对于 SO 帖子来说太多了.

That's all quite simplified, but maybe enough for the moment. There are things like software and hardware context switches (read at OSWiki) and other things. It's certainly also interesting how the kernel gets its registers before it restores other user mode registers etc., but that's too much for a SO post.

这篇关于CPU在汇编调试时真的会暂停吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆