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 windbg, 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.
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.link – HamidRezaint 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