1
votes

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.

Update: Windows 10 user mode code.

1
The cpu, debugger, and other programs are running when the test program reaches a break point. The debugger will display the values of the registers at the time of the break point. It will also restore the values back to what they were at the of the break point when the test program continues, over a step, or just left to run again.rcgldr
When your program throw a breakpoint , you can see registers and other resources values of a time slice. this means that you can just see values of regions that is yours and belongs to your program. that is result of sharing resources by your OS. actually every time that you request a BP , debugger put a INT 3 instruction in your code. Probably , OS on this instruction make a copy of your necessary values to a special region of RAM that is belongs to your debugger and then switch to next process.linkHamidReza
Other programs (including debugger itself) run in other processes, "process/thread" mechanisms are provided by OS, and OS does use the CPU interrupts/context_switching to interrupt currently running machine code while saving it's state, then loading the CPU with completely different state of some other process/thread which was "asleep" and running for short while the other one... At the single moment of time only single process is run by single core of CPU. The "multitasking" is achieved by constantly interrupting/saving/reloading state of different processes.Ped7g
The breakpointed assembly is only different in that aspect, that the code asks itself the OS to pause it (probably by running into int 3 replacement of original instruction, or by some other trap mechanism set by OS+debugger, not sure which is used in x86-64 environment). So it's cooperatively multitasking, while most of the other processes are involuntarily interrupted after they run out of allocated CPU time, that's preemptive multitasking enforced by OS.Ped7g
See also my answer here.Jester

1 Answers

3
votes

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.

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.

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

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

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

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

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

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:

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

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

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.

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

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.

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.

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.