在组装调试期间,CPU是否真的暂停? [英] Does the CPU really pause during assembly debugging?

查看:113
本文介绍了在组装调试期间,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.

推荐答案

操作系统正在根据优先级,处理器相似性等各种信息调度线程。当操作系统决定给另一个线程运行的机会时,这称为上下文切换(Wikipedia)。在上下文切换期间,操作系统将保存当前线程的寄存器,然后还原新线程的寄存器。

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,因此我将使用 windbg ,它是 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).

如果您阅读Wikipedia文章,则会发现:

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.

这是 _KPRCB 结构在偏移量1b8处。让我们来看一个:

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

所以是的,它们是:

然后知道吗?似乎我附加了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天全站免登陆