Почему kprobes отключает вытеснение и когда его можно снова включить?
Согласно документам, kprobes отключает приоритетное прерывание:
Обработчики проб выполняются с отключенным вытеснением. В зависимости от архитектуры и состояния оптимизации, обработчики также могут работать с отключенными прерываниями (например, обработчики kretprobe и оптимизированные обработчики kprobe работают без отключенных прерываний на x86/x86-64).
Из коммита 9a09f261a мы ясно видим, что оптимизированные kprobes запускались с включенным вытеснением.
Почему это так? Я понимаю, что kprobes позволяет внедрить некоторый код по определенному адресу в ядре, и с этим пониманием любой код должен быть в порядке.
- Что делает kprobes особенным, так что выгрузка должна быть отключена?
- При каких обстоятельствах я могу включить приоритетное прерывание?
1 ответ
По крайней мере, в x86 реализация Kprobes опирается на тот факт, что вытеснение отключено во время работы обработчиков Kprobe.
Когда вы помещаете в инструкцию обычный Kprobe (не основанный на Ftrace), первый байт этой инструкции перезаписывается 0xcc (int3, "программная точка останова"). Если ядро пытается выполнить эту инструкцию, возникает ловушка и kprobe_int3_handler()
вызывается (см. реализацию do_int3 ()).
Чтобы вызвать ваши обработчики Kprobe, kprobe_int3_handler () находит, какой Kprobe нажал, сохраняет его как переменную percpu current_kprobe
и вызывает ваш пре-обработчик. После этого он готовит все к пошаговому выполнению оригинальной инструкции. После пошагового вызова вызывается ваш пост-обработчик, а затем выполняется некоторая очистка. current_kprobe
и некоторые другие данные для каждого процессора используются для всего этого. Выгрузка включается только после этого.
Теперь представьте, что пре-обработчик включил выгрузку, сразу выгрузился и возобновил работу на другом процессоре. Если реализация Kprobes пытается получить доступ current_kprobe
или другие данные для каждого процессора, ядро, скорее всего, будет зависать (указатель NULL разыменовывается, если в данный момент на этом процессоре не будет current_kprobe) или хуже.
Или прерванный обработчик мог возобновить работу на том же процессоре, но другой Kprobe мог попасть туда, пока он спал - current_kprobe
и т. д. будут перезаписаны, и катастрофа будет очень вероятным.
Повторное включение вытеснения в обработчиках Kprobe может привести к трудным для отладки сбоям ядра и другим проблемам.
Короче говоря, это потому, что Kprobes спроектированы таким образом, по крайней мере, на x86. Я не могу много сказать об их реализации на других архитектурах.
В зависимости от того, что вы пытаетесь выполнить, могут быть полезны другие возможности ядра.
Например, если вам нужно запустить код только при запуске некоторых функций, взгляните на Ftrace. Ваш код будет работать в тех же условиях, что и функции, к которым вы его подключаете.
При этом в одном из моих проектов на самом деле было необходимо использовать Kprobes, чтобы обработчики работали в тех же условиях, что и приоритетные инструкции. Вы можете найти реализацию здесь. Однако, чтобы достичь этого, ничего не сломав, пришлось прыгать через обручи. До сих пор он работал нормально, но он сложнее, чем хотелось бы, имеет проблемы с переносимостью.