Получение аргументов функции с использованием kprobes
Я поместил kprobe в функцию, и теперь мне нужно получить значения ее аргументов в функции предварительной обработки kprobe.
Вот моя функция:
void foobar(int arg, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8)
{
printk("foobar called\n");
}
Помещаем в него kprobe и вызываем функцию:
...
kp.addr = (kprobe_opcode_t *) foobar;
register_kprobe(&kp);
foobar(0xdead1, 0xdead2, 0xdead3, 0xdead4, 0xdead5, 0xdead6, 0xdead7, 0xdead8);
И, наконец, функция prehandler (взято отсюда):
static int inst_generic_make_request(struct kprobe *p, struct pt_regs *regs)
{
printk(KERN_INFO "eax: %08lx ebx: %08lx ecx: %08lx edx: %08lx\n",
regs->ax, regs->bx, regs->cx, regs->dx);
printk(KERN_INFO "esi: %08lx edi: %08lx ebp: %08lx esp: %08lx\n",
regs->si, regs->di, regs->bp, regs->sp);
regs++;
//...
}
Вывод из функции prehandler выглядит следующим образом (я увеличил regs
указатель 3 раза)
May 10 22:58:07 kernel: [ 402.640994] eax: 000dead1 ebx: f7d80086 ecx: 000dead3 edx: 000dead2
May 10 22:58:07 kernel: [ 402.640996] esi: 00000000 edi: b77c8040 ebp: 00000000 esp: f7d8006c
May 10 22:58:07 kernel: [ 402.641006] eax: f7d8032c ebx: 000dead5 ecx: 000dead6 edx: 000dead7
May 10 22:58:07 kernel: [ 402.641007] esi: 000dead8 edi: f7d800e0 ebp: f7d80330 esp: 08049674
May 10 22:58:07 kernel: [ 402.641014] eax: 00000080 ebx: 0992b018 ecx: 0000108e edx: 0992b008
May 10 22:58:07 kernel: [ 402.641015] esi: 08049674 edi: b77c8040 ebp: bfe23fb8 esp: bfe23f50
Теперь я вижу аргументы foobar
функция в различных регистрах (но где 0xdead4
?) Разве они не должны быть в стеке? Как я могу получить доступ к стеку из функции prehandler? Или как я могу получить аргументы любой функции, не зная их типов и количества? Я знаю, что это может быть непростой задачей (и даже невозможно получить все значения), но достаточно только приблизительных значений. Я вычисляю соотношение между аргументами двух функций, и мне действительно не нужны точные значения. Это помогло бы, если бы у меня был ассемблерный код функции вызывающего, где аргументы помещаются в стек)?
2 ответа
Есть как минимум два подхода.
Подход 1: Jprobes
Возможно, самый простой: если Jprobes подходит для вашей задачи, вы можете попробовать. Они являются близкими родственниками kprobes (см. Их подробное описание и ссылки на примеры в документации к ядру).
Jprobes позволяет вызывать вашу функцию с той же подписью, что и проверяемая, при входе в последнюю. Вы получаете все аргументы автоматически таким образом.
Подход 2: регистры и стек
Другим подходом может быть немного расширить то, что вы уже делаете, и извлечь аргументы из регистров и стека. Из выходного журнала в вашем вопросе я предполагаю, что вы работаете в 32-битной системе x86.
x86, 32 бит
Насколько я видел, есть два наиболее распространенных соглашения о передаче аргументов в ядре Linux на x86 (подробности доступны в руководстве Агнера Фога). Обратите внимание, что системные вызовы следуют другим соглашениям (подробности см. В руководстве), но я предполагаю, что вы заинтересованы в анализе "обычной" функции, а не в системном вызове.
Конвенция 1
Для функций, отмеченных asmlinkage
а также для функций с переменными списками аргументов все параметры передаются в стек. Адрес возврата функции должен быть на вершине стека при входе в функцию, причем первый параметр находится прямо под ней. Второй параметр ниже первого и так далее.
Как у вас есть сохраненное значение esp
, вы можете найти то, на что он указывает. *(esp+4)
должен быть первым аргументом, *(esp+8)
- второй и т. д., если используется это соглашение.
Конвенция 2
Кажется, он используется для большинства функций ядра, включая ту, которую вы упомянули в вопросе.
Ядро скомпилировано с -mregparm=3
и, следовательно, первые 3 аргумента передаются в eax
, edx
а также ecx
в этом порядке остальные идут в стек. *(esp+4)
должен быть 4-м аргументом, *(esp+8)
- пятый и так далее.
x86, 64 бит
Кажется, на x86-64 все немного проще. Большинство функций ядра (включая функции с переменным списком аргументов) получают первые 6 аргументов в rdi
, rsi
, rdx
, rcx
, r8
, r9
в этом порядке остальные идут в стек. *(esp+8)
должен быть седьмым аргументом, *(esp+16)
- восьмое и так далее.
РЕДАКТИРОВАТЬ:
Обратите внимание, что на x86-32 значение esp
не сохраняется в pt_regs
для ловушек режима ядра (включая программные точки останова, на которые опирается KProbes). kernel_stack_pointer()
функция для получения правильного значения esp
работает как на x86-32, так и на x86-64. Смотрите описание kernel_stack_pointer()
в этом заголовочном файле для деталей.
К тому же, regs_get_kernel_stack_nth()
(также определено в этом заголовке) предоставляет удобный способ получить содержимое стека в обработчике.
Насколько я знаю, анализ кода сборки и поиск аргументов функций будет сложной задачей, особенно в случае кода ядра Linux. Один из способов узнать аргументы функции - использовать отладочную информацию. Позвольте мне описать это шаг за шагом.
1) Создайте ваше ядро или модуль с отладочной информацией (опция -g), для примера давайте предположим, что я создал модуль с именем 'test.ko' с отладочной информацией.
2) Используйте команду readelf для декодирования отладочной информации. Как это:
$readelf debug-dump=info test.ko > log.info
Здесь я перенаправил вывод readelf в файл log.info.
3) Теперь откройте log.info и найдите функцию, для которой вы хотите узнать аргументы функции, в нашем случае, скажем, "foobar()". Там будет запись Dwarf с TAG DW_TAG_subprogram для функции foobar(). После этой TAG вы найдете несколько других записей карликов с именами аргументов функции. В этой записи вы найдете расположение этих аргументов функции при вызове функции. Например, он говорит, что первый аргумент 'arg' будет в регистре ebx, второй аргумент будет в esp+8, а третий аргумент будет в регистре ecx и так далее.
4) После того, как вы получите эту информацию, в вашем обработчике kprobe выведите все регистры. А также распечатывает данные стека, это вы можете распечатать, как вы знаете регистр esp в prehandler.
5) На основе информации, которую вы получите на 3-м шаге, найдите значения аргументов.