Действительно ли процессор останавливается во время отладки сборки?
Я изучал язык ассемблера x64 и, как и на других языках, я могу добавить точку останова при отладке и пройти программу. Говорят, что точка останова приостанавливает выполнение программы, а утилита отладки даже отображает, какие значения находятся в регистрах ЦП в данный момент времени. Тем не менее, как это возможно, что значения являются реальными значениями, если на компьютере запущено много других программ, которые должны использовать те же регистры ЦП для выполнения при отладке? Эти значения на самом деле не в процессоре, когда программа приостановлена во время отладки? Благодарю.
Обновление: код режима пользователя Windows 10.
1 ответ
Операционная система планирует потоки на основе различной информации, такой как приоритет, привязка процессора и т. Д. Когда ОС решает дать другому потоку возможность работать, это называется переключением контекста (Википедия). Во время переключения контекста операционная система сохранит регистры текущего потока, а затем восстановит регистры нового потока.
Внутренне операционная система должна поддерживать всю эту информацию. Вы можете легко иметь 1000 потоков, поэтому операционная система должна иметь 1000 регистров во всех регистрах где-то в памяти.
Вы можете безопасно использовать отладчик пользовательского режима и взглянуть на структуры ядра. Поскольку вы работаете в Windows, я буду использовать windbg, который является частью средств отладки для Windows.
Чтобы следовать, запустите любую программу (блокнот всегда хороший кандидат) и присоедините WinDbg (F6).
Во-первых, давайте получим правильную информацию от Microsoft:
0:000> .symfix
0:000> .reload /f
Эти команды будут гарантировать, что у нас есть правильные символы (PDB).
Далее, давайте посмотрим на поток ядра (не на часть потока пользовательского режима, поскольку ядро планирует это):
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 байт).
Если вы прочитали статью в Википедии, вы узнали:
Обычно это хранится в структуре данных, называемой блоком управления процессом (PCB) или коммутационным блоком.
Это _KPRCB
структура по смещению 1b8. Давайте посмотрим на это:
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
это то, на что нужно смотреть.
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-битному процессу, поэтому вы, вероятно, получили разные результаты. В любом случае, попробуйте еще раз, и вы получите:
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
по мере необходимости, где он поддерживает регистры. Всякий раз, когда происходит переключение контекста, ядро сохраняет текущие регистры и восстанавливает другие.
При отладке ваш поток приостанавливается, поэтому он не будет работать на процессоре. Процессор также не может быть остановлен, потому что вы должны иметь возможность взаимодействовать с отладчиком. ЦП выполняет инструкции отладчика. Однако отладчик выдаст информацию из _KTHREAD
тебе.
Это все довольно упрощенно, но может быть достаточно на данный момент. Есть такие вещи, как программные и аппаратные переключатели контекста ( читайте в OSWiki) и другие вещи. Конечно, также интересно, как ядро получает свои регистры, прежде чем восстанавливает другие регистры пользовательского режима и т. Д., Но это слишком много для SO сообщения.