Доброе утро! Люди, проясните пожалуйста. Слышал, что регистр FS как-то связан с потоками, тогда вопрос, почему при переключении потоков регистр FS не меняется, но указывает на TEB активного? Вот например: В fs регистре лежит 0x0053, когда кликаю на это число в x64dbg попадаю на указатель начала структуры TEB именно текущего потока. Как вычисляется с помощью этого магического числа 0x0053 указатель на TEB текущего потока с помощью FS ? - опишите пожалуйста этот процесс подробно, очень интересно.
При переключении потоков меняется запись GDT https://devblogs.microsoft.com/oldnewthing/20190520-00/?p=102503
В сегментных регистрах лежат всегда одинаковые значения, которые называют "селекторы". Селектор - это указатель на 8-байтную запись в глобальной таблице дескрипторов GDT, где хранятся "дескрипторы" сегментов памяти. Каждый дескриптор описывает свойства сегмента, к примеру его тип (кода/данных), базу сегмента в памяти, лимит (макс.адрес) и атрибуты. Поскольку в РМ память виртуальная, то для всех процессов и потоков эти значения всегда одинаковы (внутри одной линейки Windows/Linux), за исключением сегмента задачи TSS (Task-State-Segment). Когда планировщик переключает потоки, в TSS сбрасывается состояние регистров предыдущей задачи на момент переключения. Командой dg (с аргументом в виде селектора) в отладчике WinDbg можно просмотреть значения всех дескрипторов. В юзер-отладчике x64Dbg видно, что в сег.регистрах лежат всего три селектора с номерами 2Bh, 33h, 53h - вот пример вывода дампа их дескрипторов: Код (Text): 0: kd> r gdtr ---------------------- gdtr=fffff80000b91000 <----- адрес таблицы GDT в сис.памяти (лежит в регистре GDTR текущего CPU) 0: kd> r gdtl ---------------------- gdtl=007f <----- размер GDT (мл.слово в GDTR) 0: kd> dg 2b 53 <----- запрашиваем дамп в диапазоне от 2B до 53 ---------------------- P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 002B 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb 003B 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0043 00000000`00b92080 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b 004B 00000000`0000ffff 00000000`0000f800 <Reserved> 0 Nb By Np Nl 00000000 0053 ffffffff`fffa2000 00000000`00003c00 Data RW Ac 3 Bg By P Nl 000004f3 0: kd> r gdtr gdtr=fffff80000b91000 <--- адрес GDT для CPU-0 0: kd> ~1 <--- меняем CPU на 1 1: kd> r gdtr gdtr=fffff880009f24c0 <--- адрес GDT для CPU-1 1: kd> dg 2b 53 P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 002B 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3 0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb 003B 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0043 00000000`009ebec0 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b 004B 00000000`0000ffff 00000000`0000f880 <Reserved> 0 Nb By Np Nl 00000000 0053 ffffffff`fffdd000 00000000`00007c00 Data RW Ac 3 Bg By P Nl 000004f3 Система создаёт таблицу GDT для каждого CPU в отдельности, что видно по столбцу "Base" в дескрипторах 43/53.
Спасибо. Понятно --- Сообщение объединено, 1 мар 2023 --- Проясните пожалуйста, а то новичок: Под сис.памятью вы понимаете - RAM(оперативную)? Я правильно понимаю, что под CPU в данном контексте - вы имеете в виду ЯДРО процессора? Если так, тогда получается, что система в оперативной памяти резервирует место для хранения таблицы GTD под каждое ядро, но вопрос, а как программа понимает, к GTD какого ядра ей обратиться, чтобы при взятии измененного указателя на TEB потока по селектору 0x0053 - не ошибиться?
ось разруливает, а не прикладная программа, драйвер же может изменять это поведение. что такое сис память определяется особенностями оси. к примеру, в сис память входит и своп.
Скачайте книги Руссиновича и Рихтера, там всё описано. Глобальная вирт.память делится на 2 части - нижняя половина юзеру, а верхнюю система забирает себе. Имеются "кольца защиты" 0-3, номера которых хранятся как-раз в селекторах сегментных регистров (см.поле CPL/DPL, 2 мл.бита из 16-ти). Когда ОС выделяет себе сегменты памяти, то выставляет CPL=0, а в сегментах юзера ставит CPL=3. Так она запрещает юзверю проникать в своё пространство. Селекторы ядра всегда чётные, а юзера нечётные. В логах WinDbg, кольцо защиты указывается в столбце "PL" (Privilege Level) атрибутов. Можно запросить весь дамп GDT в диапазоне селекторов 00-7F, только здесь отладчик путается, и выдаёт неправильные номера селекторов (без учёта 3 мл.бит). В таблице GDT x64, селектор(10h) это всегда секция-кода ядра, хотя на чистом х32 (без WOW64) он имел строго значение(08h): Код (Text): 0: kd> dg 00 80 **************** P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- ----------------- ----------------- ---------- - -- -- -- -- -------- 0000 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0008 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0010 00000000`00000000 00000000`00000000 Code RE Ac 0 Nb By P Lo 0000029b 0018 00000000`00000000 00000000`ffffffff Data RW Ac 0 Bg Pg P Nl 00000c93 0020 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb 0028 00000000`00000000 00000000`ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3 0030 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb 0038 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0040 00000000`00b92080 00000000`00000067 TSS32 Busy 0 Nb By P Nl 0000008b 0048 00000000`0000ffff 00000000`0000f800 <Reserved> 0 Nb By Np Nl 00000000 0050 ffffffff`fffdf000 00000000`00003c00 Data RW Ac 3 Bg By P Nl 000004f3 0058 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0060 00000000`00000000 00000000`ffffffff Code RE 0 Bg Pg P Nl 00000c9a 0068 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0070 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0078 00000000`00000000 00000000`00000000 <Reserved> 0 Nb By Np Nl 00000000 0080 Unable to get descriptor ------------------------------ 0: kd> r gdtr gdtr=fffff80000b91000 0: kd> dq fffff80000b91000 fffff800`00b91000 00000000`00000000 00000000`00000000 <---- сырой Raw-дамп GDT fffff800`00b91010 00209b00`00000000 00cf9300`0000ffff fffff800`00b91020 00cffb00`0000ffff 00cff300`0000ffff fffff800`00b91030 0020fb00`00000000 00000000`00000000 fffff800`00b91040 00008bb9`20800067 00000000`fffff800 fffff800`00b91050 ff40f3fd`f0003c00 00000000`00000000 fffff800`00b91060 00cf9a00`0000ffff 00000000`00000000 fffff800`00b91070 00000000`00000000 00000000`00000000 • В нёдрах системы, процесс описывает структура "_EPROCESS", в которой имеется поле с идентификатором "Cid" (clientId) - по нему система отличает один процесс от другого. Первым членом в структуре _EP является "Pcb" или "Process-Control-Block" (блок управления процессом, структурирован как KPROCESS). • Любой процесс имеет хоть 1 поток (основной), и для системы это структура "_ETHREAD" со своим ID. Она так-же начинается с блока управления "Tcb" = KTHREAD. По смещению 300h от начала структуры родителя "_EPROCESS" прописано поле "ThreadListHead", со связанным списком на структуры "_ETHREAD", которые описывают потоки данного процесса. • Структуры _EP и _ET лежат в пространстве ядра с CPL=0, поэтому юзеру не доступны. В его пространство (нижняя половина вирт.адресов с CPL=3) система кидает структуру "РЕВ" (блок окружения процесса), а так-же "TEB" для каждого из тредов процесса. Вот пример того, как собрать инфу например об AkelPad.exe 1. Сначала запрашиваем дамп всех процессов, что получить указатели на их структуры "EPROCESS". 2. Находим в дампе свой AkelPad и передаём адрес команде !process 3. Видим в логах ID процесса + двух его потоков и прочую инфу, включая линки на PEB+TEB в пространстве юзера. При смене потока, именно адрес TEB записывается в дескриптор(53) регистра FS: Код (Text): 0: kd> !process 0 0 **** NT ACTIVE PROCESS DUMP **** ...... PROCESS fffffa800292c060 SessionId: 1 Cid: 0a68 Peb: 7efdf000 ParentCid : 05f0 DirBase : 68db9000 ObjectTable: fffff8a0021f9740 HandleCount: 87. Image: AkelPad.exe --------------------------------------------------- 0: kd> !process fffffa800292c060 4 PROCESS fffffa800292c060 SessionId: 1 Cid: 0a68 Peb: 7efdf000 ParentCid : 05f0 DirBase : 68db9000 ObjectTable: fffff8a0021f9740 HandleCount: 87. Image: AkelPad.exe THREAD fffffa8004191b50 Cid 0a68.0a6c Teb: 7efdb000 Win32Thread: fffff900c1e74c30 WAIT THREAD fffffa8003b7d060 Cid 0a68.0a70 Teb: 7efd8000 Win32Thread: 0000000000000000 WAIT ----------- Фрагмент структуры EPROCESS ----------------------------------------- 0: kd> dt _eprocess fffffa800292c060 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x180 UniqueProcessId : 0x00000000`00000a68 Void <--- PID процесса +0x198 ProcessQuotaUsage : [2] 0x2858 +0x1d8 VirtualSize : 0x4f89000 +0x210 WorkingSetPage : 0x68a3d +0x2d8 ImageFileName : [15] "AkelPad" +0x300 ThreadListHead : _LIST_ENTRY [0x00000000`00000000 - 0xfffffa80`04191f78] <--- список указателей на "_ETHREAD'ы" +0x318 Wow64Process : (null) +0x320 ActiveThreads : 0x7efde000 +0x330 Peb : 0x00000000`00000001 _PEB +0x428 HighestUserAddress : 0xfffffa80`02842480 Void +0x440 VadRoot : _MM_AVL_TABLE Так, чтобы правильно жонглировать структурами процессов и потоков, системе достаточно знать их ID. Какое из ядер процессора будет исполнять тред, назначает планировщик потоков "Scheduler", но функцией SetProcessAffinityMask() даже юзер (с определёнными правами) сможет назначить конкретные ядра для своих поток, освобождая от этой задачи системный планировщик.