У меня есть драйвер IOKit, который происходит от IOService
базовый класс, и его необработанный указатель доставляется в некоторую функцию обратного вызова события из kauth
рамки, которые можно назвать очень часто.
Чтобы извлечь этот экземпляр из указателя, я использую безопасный метод OSDynamicCast
и убедитесь, что во время демонтажа драйвера я отключаю kauth
звонки и сбросить все существующие звонки, прежде чем освободить драйвер. Однако иногда я все еще испытываю панику ядра OSDynamicCast
:
frame #0: [inlined] OSMetaClass::checkMetaCast(check=0xffffff802b28d3f0)
frame #1: [inlined] OSMetaClassBase::metaCast(OSMetaClass const*) const
frame #2: kernel`OSMetaClassBase::safeMetaCast
Когда я отключил и очистил kauth
звонки до OSObject::free
на IOService::stop
Обратный вызов, проблема не повторяется (по крайней мере, после десятков попыток).
Возможно, у кого-нибудь есть идея, если какая-то память освобождается в период между ::stop
а также ::free
что вызывает эту панику?
Вот небольшой код, который подчеркивает мой дизайн, когда вызывает панику.
kauth_callback(kauth_cred_t credential,
void *idata, /* This is the RAW pointer for my IOService based instance */
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3)
{
...
// taking shared_lock with mutex
auto my_inst = OSDynamicCast(com_my_driver, reinterpret_cast<OSObject *>(idata));
...
}void com_my_driver::free(IOService *provider)
{
kauth_unlisten_scope(my_listener_);
// taking unique lock with mutex to make sure no outstanding kauth calls.
super::free(provider); //calling OSObject free
}
И если я перейду логику из ::free
в ::stop
оно работает :
void com_my_driver::stop(IOService *provider)
{
kauth_unlisten_scope(my_listener_);
// taking unique lock with mutex to make sure no outstanding kauth calls.
super::stop(provider); // Calling IOService::stop()
}
В kauth_unlisten_scope есть неотъемлемое состояние гонки. Ваше решение мьютекса почти наверняка не полностью решит проблему, потому что ваш код в обратном вызове может действительно выполняться после kauth_unlisten_scope()
возвращает — другими словами, обратный вызов kauth еще не заблокировал мьютекс.
Все, что вы можете сделать, это поспать некоторое время после kauth_unlisten_scope()
возвращается. Надеемся, что через секунду все обратные вызовы kauth успешно завершатся.
Если вы хотите быть очень осторожным, вы также можете добавить глобальный логический флаг, который обычно равен true, но имеет значение false перед тем, как отменить регистрацию слушателя kauth. Вы можете проверить флаг при входе в ваш обратный вызов, перед блокировкой мьютекса и т. Д. Если этот флаг имеет значение false, немедленно вернитесь из обратного вызова. Это по крайней мере предотвращает любой доступ к динамически распределенной памяти; однако, это все еще не решает проблему на 100% в принципе, потому что глобальная переменная, конечно, исчезнет, когда kext выгружен.
Apple знала об этой проблеме в течение ~ 7 лет, когда я использовала API kauth; они не исправили это в то время, и, поскольку они планируют постепенно отказаться от кекстов в течение следующих нескольких лет, я не думаю, что это изменится.
Примечания стороны:
Не использовать reinterpret_cast<>
отлить из непрозрачного void*
на конкретный тип указателя. static_cast<>
предназначен для этой цели.
Кроме того, вы можете использовать статическое приведение вместо динамического при условии, что вы всегда передаете объекты типа com_my_driver
в kauth_listen_scope()
, На самом деле, вы должны выполнять static_cast<>
до статического типа, который первоначально ухудшился до void*
что я подозреваю не OSObject
в твоем случае. Если это отличается от ожидаемого вами динамического типа, приведите результат этого к производному типу.
Например. ПЛОХОЙ:
//startup
com_my_driver* myobj = …;
// com_my_driver* implicitly degrading to void*
kauth_listen_scope("com_my_driver", kauth_callback, myobj);
// …
int kauth_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
// the void* came from a com_my_driver*, not an OSObject*!
auto my_inst = static_cast<com_my_driver*>(static_cast<OSObject *>(idata));
}
ЛУЧШЕ:
int kauth_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action, uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
auto my_inst = static_cast<com_my_driver*>(idata);
}
Это довольно незначительный момент, и предполагается, что вы ничего не делаете с множественным наследованием, что вы не должны делать в IOKit в любом случае, на практике он скомпилируется в тот же код, но обходит неопределенное поведение. Кроме того, неправильный код более запутанный для чтения, и если вы используете OSDynamicCast
, менее эффективный — и эффективность чрезвычайно важна в обратных вызовах kauth.
На самом деле так сильно, что я бы с осторожностью даже запирал мьютекс на «горячем» пути, который вы предлагаете делать. Это означает, что вы буквально однопоточны для всех файловых операций ввода-вывода во всех пользовательских процессах во всей системе. Не отправляйте такой код клиентам. Попробуйте вместо этого использовать RW-Lock и только блокировку для чтения в общем случае, когда блокировки записи зарезервированы для случаев, когда они действительно необходимы.
Других решений пока нет …