Самомодифицирующийся код для ловушек трассировки?

Я ищу способ с наименьшими накладными расходами вставить крючки трассировки/регистрации в какой-то очень чувствительный к производительности код драйвера. Этот материал для ведения журнала всегда должен быть скомпилирован, но в большинстве случаев ничего не делается (но ничего не делается очень быстро).

Нет ничего проще, чем иметь глобальное слово включения/выключения, выполняя if(enabled){log()}. Однако, если возможно, я хотел бы даже избежать затрат на загрузку этого слова каждый раз, когда я нажимаю на один из своих крючков. Мне приходит в голову, что потенциально я мог бы использовать для этого самомодифицирующийся код — т. е. везде, где у меня есть вызов моей функции трассировки, я перезаписываю переход с помощью NOP, когда хочу отключить хуки, и заменяю переход, когда захочу. чтобы включить их.

Быстрый поиск в гугле не дает никаких сведений об этом — кто-нибудь делал это? Осуществимо ли это, есть ли какие-то серьезные камни преткновения, которых я не предвижу?

(Линукс, x86_64)


person kdt    schedule 29.12.2010    source источник
comment
Обратите внимание на возможность эксклюзивного режима записи/выполнения. Это может затруднить написание самомодифицирующегося кода...   -  person dmckee --- ex-moderator kitten    schedule 29.12.2010


Ответы (4)


Да, эта техника реализована в ядре Linux точно для той же цели (отслеживание перехватчиков).

Для начала см. статью LWN о метках перехода.

На самом деле серьезных камней преткновения нет, но есть несколько второстепенных: многопоточные процессы (вам придется останавливать все остальные потоки, пока вы включаете или отключаете код); непоследовательный кэш инструкций (необходимо убедиться, что I-кэш очищен на каждом ядре).

person caf    schedule 29.12.2010
comment
Спасибо, это именно то, что я искал. - person kdt; 29.12.2010

Имеет ли значение, если ваш скомпилированный драйвер вдруг стал в два раза больше?

Создайте два пути кода — один с ведением журнала, один без него. Используйте глобальные указатели на функции, чтобы перейти к секциям, чувствительным к производительности, перезапишите их по мере необходимости.

person Nicholas Knight    schedule 29.12.2010

Если бы был способ каким-то образом объявить регистр глобальным, вы могли бы извне загрузить в регистр значение вашего слова в каждой точке входа в ваш драйвер, а затем просто проверить регистр. Конечно, тогда вы отказываете оптимизатору в использовании этого регистра, что может иметь неприятные последствия для производительности.

person Omnifarious    schedule 29.12.2010
comment
Вы не можете объявить глобальную переменную с помощью ключевого слова register, и даже если бы вы могли, было бы ужасной, ужасной идеей потерять этот регистр для всего остального. - person Adam Rosenfield; 29.12.2010
comment
@Adam Rosenfield - на самом деле есть способы объявить глобальные регистры с помощью некоторых компиляторов C. Особенно встроенные. И да, потерять его ради всего остального было бы очень плохо. Я просто выкинул верную идею и указал на ее недостатки. Я не думаю, что это заслуживает отрицательного голосования, но что угодно. Я оставляю свой ответ здесь, несмотря ни на что. - person Omnifarious; 29.12.2010
comment
Это был бы разумный подход для встроенной архитектуры, но, к сожалению, мне нужно работать как минимум с x86_64, а в идеале с переносимостью. Я тоже не понимаю, почему тебя минусуют... - person kdt; 29.12.2010

Я пишу не столько о том, возможно это или нет, сколько о том, выиграете ли вы что-нибудь существенное.

С одной стороны, вы не хотите проверять «включение ведения журнала» каждый раз, когда появляется возможность ведения журнала, а с другой стороны, вам нужно проверять «включение ведения журнала» и перезаписывать код либо кодом «да», либо кодом «нет». Или ваш драйвер "помнит", что это было не в прошлый раз, и так как в этот раз нет запроса, ничего не нужно делать?

Необходимая логика не кажется тривиальной по сравнению с тестированием каждый раз.

person Olof Forshell    schedule 24.01.2011
comment
вы неправильно понимаете. При использовании переписанных хуков работа выполняется только тогда, когда трассировка включена (вставьте код трассировки) или выключена (замените ничего не делающим кодом). Все дело в том, что при отключении трассировки точки трассировки становятся no-ops. - person kdt; 24.01.2011
comment
Я имел в виду, что логика и код, необходимые либо для вставки кода трассировки, либо для замены кода бездействия, вполне могут свести на нет время, выигранное при выполнении операций без операций. - person Olof Forshell; 25.01.2011