Потоковое локальное хранилище в режиме ядра?
Есть ли в Windows эквивалент потокового хранилища (TLS) для драйверов режима ядра (точнее, Win32)?
Чего я пытаюсь добиться:
В конечном счете, в рамках процедуры диспетчеризации моего драйвера он может вызывать многие другие функции (может быть глубокий стек вызовов). Я хочу предоставить некоторую контекстную информацию, относящуюся к обрабатываемому запросу. То есть у меня есть некоторая структура, указатель на которую должен быть виден во всех вызываемых функциях, без явной передачи ее в качестве параметра каждой функции.
Использование static/global не является идеальным вариантом (многопоточность, синхронизация объектов и т. Д.).
Если бы это был код пользовательского режима - очевидно, в такой ситуации можно использовать TLS. Но AFAIK нет функций режима ядра, таких как TlsGetValue
/TlsSetValue
, И это имеет смысл - чтобы эти функции работали, нужно сначала выделить индекс TLS для всего процесса. Код драйвера OTOH может быть вызван в произвольном потоке, не ограничиваясь конкретным процессом.
Однако на самом деле мне не нужно постоянное потоковое хранилище. Мне просто нужно специфичное для потока хранилище для вызова функций верхнего уровня.
Я думаю, что знаю, как "реализовать" TLS, хотя и хакерским способом. Вместо выделения индекса TLS я всегда буду использовать предопределенный индекс (скажем, index=0). В функции верхнего уровня я сохраню сохраненное значение TLS и перезапишу его нужным значением. По завершении сохраненное значение будет восстановлено.
К счастью, я знаю, как TLS реализован в Win32. Есть TIB
структура (информационный блок потока) для каждого потока. В каждом потоке к нему можно получить доступ используя FS:[18h]
селектор. TIB
содержит (среди прочего) массив, используемый TLS. Остальное довольно просто.
Однако я бы предпочел использовать официальный API для достижения чего-то похожего.
- Есть ли официальный API режима ядра для достижения того, что мне нужно?
- Есть ли причины избегать того, что я планирую делать? Я знаю, что потенциально может быть проблема с повторным входом (т. Е. Какой-то код вызывает меня, я перезаписываю значение TLS и затем в конечном итоге вызываю исходный код, который может полагаться на TLS). Но это не возможно в моем конкретном случае?
- Есть ли менее грязные способы решить эту проблему?
Заранее спасибо.
PS Теоретически можно использовать SEH (в котором также хранится информация для каждого потока). То есть, обернуть код верхнего уровня __try/__except
тогда, где нужна контекстная информация - вызвать непрерывное исключение с некоторым параметром, в __except
Блок заполнить параметр контекстной информацией, а затем возобновить выполнение. И это на 100% действительный программный поток, без использования недокументированных функций. Но, тем не менее, это кажется мне безобразным хаком, не говоря уже о проблемах с производительностью.
3 ответа
Вместо использования FS:[18h] вам, вероятно, следует использовать PsGetCurrentThreadTeb. Даже тогда, я думаю, вы будете полагаться на детали, которые могут измениться в будущих выпусках ОС (возможно, включая пакеты обновления).
Вместо этого, не могли бы вы использовать KeGetCurrentProcessorNumber в качестве индекса в массиве, где вы можете хранить указатель на вашу контекстную информацию? (Конечно, при условии, что вы используете DISPATCH_LEVEL или выше, так что вы не сможете неожиданно переключиться на другой процессор.)
Если вы не гарантированно работаете с DISPATCH_LEVEL, вы можете использовать таблицу или связанный список, где каждая запись (представляющая поток, в котором в данный момент выполняется ваш код) помечена значением PsGetCurrentThread.
Не делай этого с TEB! TIB и TEB являются структурами пользовательского режима. Приложение пользовательского режима может изменять эту информацию по желанию из другого потока / процессора, пока работает ваш драйвер. Это будет уязвимость повышения привилегий в вашем драйвере.
Я бы порекомендовал передать контекстную структуру для кратковременного контекста, связанного с вашим запросом. Если вам нужно что-то более постоянное, вы можете использовать таблицу AVL или таблицу Hash, которую вы очищаете при выходе из потоков.
Вы можете создать структуру, которая содержит входящий запрос, и вы передаете его вместо фактического запроса, а затем просто вводите любые поля, которые вам нужны. Очевидно, это не полностью устраняет необходимость передавать объект, но обычно вы все равно передаете запрос.
Из большинства драйверов, которые я видел (что, правда, не так уж много), все всегда было ориентировано на запросы. Поэтому они всегда привязывали вещи к запросу, а не пытались хранить его в каком-то другом месте.