“Чужие” spinlock’и
Иногда приходится делать хаки при написании драйверов, для того чтобы иметь возможность работать со какими-то недокументированными фичами ядра. И для того чтобы обеспечить синхронизацию, приходится использовать чужие объекты синхронизации. Это, как оказалось, вместо стабильности может наоборот привнести хаоса в код. Я хочу рассказать как я обжегся на спинлоках.
Для работы со спинлоком есть целый набор функций:
Before calling any support routine that requires access to a caller-supplied executive spin lock, a driver must call KeInitializeSpinLock to initialize the corresponding executive spin lock. Support routines that require an initialized executive spin lock include the following:
KeAcquireSpinLock and, subsequently, KeReleaseSpinLock
KeAcquireSpinLockAtDpcLevel and, subsequently, KeReleaseSpinLockFromDpcLevel
KeAcquireInStackQueuedSpinLock and, subsequently, KeReleaseInStackQueuedSpinLock
KeAcquireInStackQueuedSpinLockAtDpcLevel and, subsequently, KeReleaseInStackQueuedSpinLockFromDpcLevel
Но не пишется о том, что будет если вы используете разные функции на одном и том же спинлоке. Как показал опыт, ничего не будет, ровно до того момента пока не будет захвачен спинлок и начнется ожидание.
Например, ОС инициализирует спинлок через KeInitializeSpinLock, и дальше использует пару KeAcquireSpinLock/KeReleaseSpinLock для работы с ним. Тут вы своим кодом начинаете пытаться лочить его через KeAcquireInStackQueuedSpinLock/KeReleaseInStackQueuedSpinLock. И вот если лок был уже захвачен через KeAcquireSpinLock, KeAcquireInStackQueuedSpinLock рухнет, потому что KeAcquireSpinLock хранит в SPIN_LOCK просто DWORD равный 0 или 1. А KeAcquireInStackQueuedSpinLock хранит там указатель на очередь. Спинлок один и тот же данные абсолютно разные. Соответственно, в спинлоке, залоченом через KeAcquireSpinLock хранится число 1, а KeAcquireInStackQueuedSpinLock попытается прочитать по указателю = 1.
В моем случае все оказалось еще веселее, в одних версии ОС для этого спинлока использовались KeAcquireSpinLock/KeReleaseSpinLock, а в других KeAcquireInStackQueuedSpinLock/KeReleaseInStackQueuedSpinLock. Баг получился плавающим.
Вывод: при использовании чужих спинлоков обязательно проверьте какими функциями пользуется ОС.