Захват сборки пользовательского пространства с помощью ftrace и kprobes (с использованием виртуальной трансляции адресов)?

Извиняюсь за длинный пост, у меня возникли проблемы с формулировкой в ​​более краткой форме. Также, возможно, это больше подходит для Unix и Linux Stack Exchange, но я сначала попробую здесь, в SO, так как ftrace тег.

В любом случае - я хотел бы наблюдать за выполнением машинных инструкций пользовательской программы в контексте полной function_graph захватить с помощью ftrace, Одна проблема заключается в том, что мне нужно это для более старого ядра:

$ uname -a
Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux

... и в этом издании нет UPROBES - что, как отмечает Uprobes в 3.5 [LWN.net], должно быть в состоянии сделать что-то подобное. (Пока мне не нужно исправлять исходное ядро, я хотел бы попробовать модуль ядра, построенный из дерева, как это демонстрируют зонды пользовательского пространства (Uprobes) [chunghwan.com]; но насколько Я могу видеть из 0: Uprobes на основе Inode [LWN.net], 2.6, вероятно, потребуется полный патч)

Однако в этой версии есть /sys/kernel/debug/kprobes, а также /sys/kernel/debug/tracing/kprobe_events; и Documentation / trace / kprobetrace.txt подразумевает, что kprobe может быть установлен непосредственно по адресу; даже если я нигде не могу найти пример того, как это используется.

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

$ gcc -g -O0 wtest.c -o wtest
$ objdump -S wtest | less
...
08048474 <main>:
int main(void) {
 8048474:       55                      push   %ebp
 8048475:       89 e5                   mov    %esp,%ebp
 8048477:       83 e4 f0                and    $0xfffffff0,%esp
 804847a:       83 ec 30                sub    $0x30,%esp
 804847d:       65 a1 14 00 00 00       mov    %gs:0x14,%eax
 8048483:       89 44 24 2c             mov    %eax,0x2c(%esp)
 8048487:       31 c0                   xor    %eax,%eax
  char filename[] = "/tmp/wtest.txt";
...
  return 0;
 804850a:       b8 00 00 00 00          mov    $0x0,%eax
}
...

Я бы настроил ftrace logging через этот скрипт:

sudo bash -c '
KDBGPATH="/sys/kernel/debug/tracing"
echo function_graph > $KDBGPATH/current_tracer
echo funcgraph-abstime > $KDBGPATH/trace_options
echo funcgraph-proc > $KDBGPATH/trace_options
echo 0 > $KDBGPATH/tracing_on
echo > $KDBGPATH/trace
echo 1 > $KDBGPATH/tracing_on ; ./wtest ; echo 0 > $KDBGPATH/tracing_on
cat $KDBGPATH/trace > wtest.ftrace
'

Вы можете увидеть часть (в противном случае сложный) в результате ftrace отладка входа в систему - наблюдение за записью на жесткий диск в пространстве ядра (с драйверами / модулями) - Unix & Linux Stack Exchange (откуда я взял пример).

В принципе, я хотел бы распечатать в этом ftrace журнал, когда первые инструкции main - скажем, инструкции в 0x8048474, 0x8048475, 0x8048477, 0x804847a, 0x804847d, 0x8048483 и 0x8048487 - выполняются (любым) процессором. Проблема, насколько я могу понять из Анатомии Программы в Памяти: Густаво Дуарте, эти адреса являются виртуальными адресами, как видно с точки зрения самого процесса (и я понимаю, такая же перспектива показана /proc/PID/maps)... и, видимо, для krpobe_event Мне нужен физический адрес?

Итак, моя идея заключается в следующем: если я могу найти физические адреса, соответствующие виртуальным адресам разборки программы (скажем, путем кодирования модуля ядра, который будет принимать pid и address и возвращать физический адрес через procfs), я мог бы установить адреса как своего рода "точки трассировки" через /sys/kernel/debug/tracing/kprobe_events в приведенном выше сценарии - и, надеюсь, получить их в ftrace журнал. Может ли это работать в принципе?

Одна проблема с этим я обнаружил в Linux(ubuntu), язык C: преобразование виртуальных адресов в физические - переполнение стека:

В коде пользователя вы не можете знать физический адрес, соответствующий виртуальному адресу. Эта информация просто не экспортируется за пределы ядра. Это может даже измениться в любое время, особенно если ядро ​​решит выгрузить часть памяти вашего процесса.
...
Передайте виртуальный адрес ядру с помощью systemcall/procfs и используйте vmalloc_to_pfn. Вернуть физический адрес через procfs/registers.

Тем не мение, vmalloc_to_pfn тоже не кажется тривиальным:

x86 64 - vmalloc_to_pfn возвращает 32-битный адрес в системе Linux 32. Почему он отсекает старшие биты физического адреса PAE? - Переполнение стека

VA: 0xf8ab87fc PA с использованием vmalloc_to_pfn: 0x36f7f7fc. Но я на самом деле ожидаю: 0x136f7f7fc.
...
Физический адрес находится в диапазоне от 4 до 5 ГБ. Но я не могу получить точный физический адрес, я получаю только отрубленный 32-битный адрес. Есть ли другой способ получить настоящий физический адрес?

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

Обратите внимание, что я хотел бы вывод через ftrace, так что временные метки выражаются в одном и том же домене (см. Надежные временные метки ядра Linux (или их настройку) с использованием как usbmon, так и ftrace? - Переполнение стека для иллюстрации проблемы с временными метками). Таким образом, даже если бы я мог придумать, скажем, gdb сценарий, чтобы запустить и отследить программу из пространства пользователя (одновременно ftrace захват получается) - я бы хотел этого избежать, так как накладные расходы от gdb сам покажет в ftrace журналы.

Итак, в итоге:

  • Стоит ли подходить к получению (возможно, через отдельный модуль ядра) физических адресов с виртуальных (от разборки исполняемого) адресов - поэтому они используются для запуска kprobe_event, зарегистрированного ftrace? Если да, есть ли примеры модулей ядра, которые можно использовать для этой цели трансляции адресов?
  • Могу ли я иначе использовать модуль ядра, чтобы "зарегистрировать" функцию обратного вызова / обработчика, когда выполняется определенный адрес памяти? Тогда я мог бы просто использовать trace_printk в этой функции, чтобы иметь ftrace log (или даже без этого, само имя функции-обработчика должно отображаться в ftrace журнал), и не похоже, что с этим будет слишком много накладных расходов...

На самом деле, в этой публикации 2007 года, Джим Кенистон - uprobes на основе utrace: список рассылки systemtap, есть 11. Uprobes Example (Добавлено в Documentation/uprobes.txt), что, по-видимому, и есть - модуль ядра, регистрирующий функцию-обработчик. К сожалению, он использует linux/uprobes.h; и у меня есть только kprobes.h в моем /usr/src/linux-headers-2.6.38-16/include/linux/, Кроме того, в моей системе даже systemtap жалуется на CONFIG_UTRACE не включен (см. этот комментарий)... Так что, если есть какой-то другой подход, который я мог бы использовать для получения трассировки отладки, как я хочу, без необходимости перекомпилировать ядро, чтобы получить пробные копии, было бы здорово узнать...


wtest.c:

#include <stdio.h>
#include <fcntl.h>  // O_CREAT, O_WRONLY, S_IRUSR

int main(void) {
  char filename[] = "/tmp/wtest.txt";
  char buffer[] = "abcd";
  int fd;
  mode_t perms = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;

  fd = open(filename, O_RDWR|O_CREAT, perms);
  write(fd,buffer,4);
  close(fd);

  return 0;
}

1 ответ

Очевидно, это было бы намного проще со встроенными апроберами в ядрах 3.5+; но, учитывая, что апробация для моего ядра 2.6.38 - очень глубокий патч (который я не мог выделить в отдельном модуле ядра, чтобы избежать исправления ядра), вот что я могу отметить для автономного модуля на 2.6.38. (Поскольку я все еще не уверен во многих вещах, я все еще хотел бы увидеть ответ, который исправит любые недоразумения в этом посте.)

Я думаю, что я получил где-то, но не с kprobes, Я не уверен, но, похоже, мне удалось правильно определить физические адреса; тем не мение, kprobes в документации конкретно говорится, что при использовании " @ADDR: извлекать память в ADDR (ADDR должен быть в ядре) "; и физические адреса, которые я получаю, находятся ниже границы ядра 0xc0000000 (но тогда 0xc0000000 обычно вместе с макетом виртуальной памяти?).

Так что вместо этого я использовал аппаратную точку останова - модуль находится ниже, однако будьте осторожны - он ведет себя случайным образом и иногда может вызвать ошибку ядра! Скомпилировав модуль и запустив bash:

$ sudo bash -c 'KDBGPATH="/sys/kernel/debug/tracing" ;
echo function_graph > $KDBGPATH/current_tracer ; echo funcgraph-abstime > $KDBGPATH/trace_options
echo funcgraph-proc > $KDBGPATH/trace_options ; echo 8192 > $KDBGPATH/buffer_size_kb ;
echo 0 > $KDBGPATH/tracing_on ; echo > $KDBGPATH/trace'
$ sudo insmod ./callmodule.ko && sleep 0.1 && sudo rmmod callmodule && \
tail -n25 /var/log/syslog | tee log.txt && \
sudo cat /sys/kernel/debug/tracing/trace >> log.txt

... Я получаю журнал. Я хочу проследить первые две инструкции main() из wtest, которые для меня являются:

$ objdump -S wtest/wtest | grep -A3 'int main'
int main(void) {
 8048474:   55                      push   %ebp
 8048475:   89 e5                   mov    %esp,%ebp
 8048477:   83 e4 f0                and    $0xfffffff0,%esp

... по виртуальным адресам 0x08048474 и 0x08048475. в syslog На выходе я мог бы получить, скажем:

...
[ 1106.383011] callmodule: parent task a: f40a9940 c: kworker/u:1 p: [14] s: stopped
[ 1106.383017] callmodule: - wtest [9404]
[ 1106.383023] callmodule: Trying to walk page table; addr task 0xEAE90CA0 ->mm ->start_code: 0x08048000 ->end_code: 0x080485F4
[ 1106.383029] callmodule: walk_ 0x8048000 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @   (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383049] callmodule: walk_ 0x80483c0 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @   (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383067] callmodule: walk_ 0x8048474 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @   (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383083] callmodule: physaddr : (0x080483c0 ->) 0x639ec3c0 : (0x08048474 ->) 0x639ec474
[ 1106.383106] callmodule: 0x08048474 id [3]
[ 1106.383113] callmodule: 0x08048475 id [4]
[ 1106.383118] callmodule: (( 0x08048000 is_vmalloc_addr 0 virt_addr_valid 0 ))
[ 1106.383130] callmodule: cont pid task a: eae90ca0 c: wtest p: [9404] s: runnable
[ 1106.383147] initcall callmodule_init+0x0/0x1000 [callmodule] returned with preemption imbalance
[ 1106.518074] callmodule: < exit

... это означает, что виртуальный адрес 0x08048474 сопоставлен с физическим адресом 0x639ec474. Однако физический не используется для аппаратных точек останова - там мы можем предоставить виртуальный адрес непосредственно register_user_hw_breakpoint; Тем не менее, мы также должны предоставить task_struct процесса тоже. С этим я могу получить что-то подобное в ftrace выход:

...
  597.907256 |   1)   wtest-5339   |               |  handle_mm_fault() {
...
  597.907310 |   1)   wtest-5339   | + 35.627 us   |      }
  597.907311 |   1)   wtest-5339   | + 46.245 us   |    }
  597.907312 |   1)   wtest-5339   | + 56.143 us   |  }
  597.907313 |   1)   wtest-5339   |   1.039 us    |  up_read();
  597.907317 |   1)   wtest-5339   |   1.285 us    |  native_get_debugreg();
  597.907319 |   1)   wtest-5339   |   1.075 us    |  native_set_debugreg();
  597.907322 |   1)   wtest-5339   |   1.129 us    |  native_get_debugreg();
  597.907324 |   1)   wtest-5339   |   1.189 us    |  native_set_debugreg();
  597.907329 |   1)   wtest-5339   |               |  () {
  597.907333 |   1)   wtest-5339   |               |  /* callmodule: hwbp hit: id [3] */
  597.907334 |   1)   wtest-5339   |   5.567 us    |  }
  597.907336 |   1)   wtest-5339   |   1.123 us    |  native_set_debugreg();
  597.907339 |   1)   wtest-5339   |   1.130 us    |  native_get_debugreg();
  597.907341 |   1)   wtest-5339   |   1.075 us    |  native_set_debugreg();
  597.907343 |   1)   wtest-5339   |   1.075 us    |  native_get_debugreg();
  597.907345 |   1)   wtest-5339   |   1.081 us    |  native_set_debugreg();
  597.907348 |   1)   wtest-5339   |               |  () {
  597.907350 |   1)   wtest-5339   |               |  /* callmodule: hwbp hit: id [4] */
  597.907351 |   1)   wtest-5339   |   3.033 us    |  }
  597.907352 |   1)   wtest-5339   |   1.105 us    |  native_set_debugreg();
  597.907358 |   1)   wtest-5339   |   1.315 us    |  down_read_trylock();
  597.907360 |   1)   wtest-5339   |   1.123 us    |  _cond_resched();
  597.907362 |   1)   wtest-5339   |   1.027 us    |  find_vma();
  597.907364 |   1)   wtest-5339   |               |  handle_mm_fault() {
...

... где трассы, соответствующие сборке, помечены идентификатором точки останова. К счастью, они следуют сразу за другим, как и ожидалось; тем не мение, ftrace также захватил некоторые промежуточные команды отладки. В любом случае это то, что я хотел увидеть.

Вот несколько заметок о модуле:

  • Большая часть модуля происходит из программы " Выполнение / вызов пользовательского пространства" и получает ее pid из модуля ядра; где пользовательский процесс запущен и pid получен
    • Так как мы должны добраться до task_struct, чтобы добраться до pid; здесь я сохраняю оба (что отчасти избыточно)
  • Где символы функций не экспортируются; если символ находится в kallsyms затем я использую указатель функции на адрес; иначе другие необходимые функции копируются из источника
  • Я не знал, как запустить остановленный процесс пользовательского пространства, поэтому после появления я выдаю SIGSTOP (что само по себе кажется ненадежным в тот момент), и установите состояние в __TASK_STOPPED).
    • Я все еще могу получить статус "работоспособный" там, где я иногда этого не ожидаю - однако, если инициализация завершается рано с ошибкой, я заметил wtest зависание в списке процессов еще долго после его завершения, поэтому я думаю, что это работает.
  • Чтобы получить абсолютные / физические адреса, я использовал таблицы пешеходных страниц процесса в Linux, чтобы перейти на страницу, соответствующую виртуальному адресу, а затем копался в найденных источниках ядра. page_to_phys() попасть по адресу (внутренне через номер фрейма страницы); LDD3 ch.15 помогает понять взаимосвязь между pfn и физическим адресом.
    • Поскольку здесь я ожидаю, что у меня будет физический адрес, я не использую PAGE_SHIFT, но вычисляю смещения непосредственно из objdump Результат сборки - я не уверен на 100%, что это правильно.
    • Обратите внимание, (см. Также Как получить структуру страницы с любого адреса в ядре Linux), вывод модуля говорит, что виртуальный адрес 0x08048000 не является ни is_vmalloc_addr ни virt_addr_valid; Я думаю, это должно сказать мне, никто не мог использовать ни vmalloc_to_pfn() ни virt_to_page() добраться до его физического адреса!
  • Настройка kprobes за ftrace из пространства ядра довольно сложно (нужно скопировать функции)
    • Пытаясь установить kprobe на физических адресах, которые я получаю (например, 0x639ec474), всегда выдается " Не удалось вставить пробник (-22) "
    • Просто чтобы посмотреть, разбирается ли формат, я пытаюсь с kallsyms адрес tracing_on() функция (0xc10bcf60) ниже; это, кажется, работает - потому что это вызывает роковое " БУГ: планирование в то время как атомарное " (очевидно, мы не должны устанавливать точки останова в module_init?). Ошибка смертельна, потому что это делает kprobes каталог исчезает из ftrace каталог отладки
    • Просто создавая kprobe не заставит его появиться в ftrace log - его тоже нужно включить; необходимый код для включения есть - но я никогда не пробовал его из-за предыдущей ошибки
  • И наконец, настройка точки останова: наблюдать за изменением (адресом памяти) в ядре Linux и отслеживать трассировку стека печати при его изменении?
    • Я никогда не видел пример для установки исполняемой аппаратной точки останова; для меня это продолжалось до тех пор, пока через поиск по ядру я не обнаружил, что для HW_BREAKPOINT_X, attr.bp_len нужно установить на sizeof(long)
    • Если я попытаюсь printk attr переменная (и) - из _init или из обработчика - что-то серьезно испорчено, и какую бы переменную я ни пытался вывести следующей, я получаю для нее значение 0x5 (или 0x48) (?!)
    • Так как я пытаюсь использовать одну функцию-обработчик для обеих точек останова, единственная надежная часть информации, которая сохраняется от _init до обработчика, способного различать эти два, - bp->id
    • Эти идентификаторы автоматически назначаются, и, кажется, они не будут повторно заявлены, если вы отмените регистрацию точек останова (я не отменяю регистрацию их, чтобы избежать лишних распечаток ftrace).

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

[ 1661.815114] callmodule: Trying to walk page table; addr task 0xEAF68CA0 ->mm ->start_code: 0x08048000 ->end_code: 0x080485F4
[ 1661.815319] callmodule: walk_ 0x8048000 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0
[ 1661.815837] callmodule: walk_ 0x80483c0 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0
[ 1661.816846] callmodule: walk_ 0x8048474 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0

... то есть, даже с правильным указателем задачи (судя по start_code), только 0x0 получается как физический адрес. Иногда вы получаете тот же результат, но с start_code: 0x00000000 ->end_code: 0x00000000, И иногда task_struct не может быть получено, даже если pid может:

[  833.380417] callmodule:c: pid 7663
[  833.380424] callmodule: everything all right; pid 7663 (7663)
[  833.380430] callmodule: p is NULL - exiting
[  833.516160] callmodule: < exit

Что ж, надеюсь, кто-то прокомментирует и прояснит некоторые аспекты поведения этого модуля. :)
Надеюсь, это кому-то поможет,
Ура!

Makefile:

EXTRA_CFLAGS=-g -O0
obj-m += callmodule.o
all:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

callmodule.c:

#include <linux/module.h>
#include <linux/slab.h> //kzalloc
#include <linux/syscalls.h> // SIGCHLD, ... sys_wait4, ...
#include <linux/kallsyms.h> // kallsyms_lookup, print_symbol
#include <linux/highmem.h> // ‘kmap_atomic’ (via pte_offset_map)
#include <asm/io.h> // page_to_phys (arch/x86/include/asm/io.h)

struct subprocess_infoB; // forward declare
// global variable - to avoid intervening too much in the return of call_usermodehelperB:
static int callmodule_pid;
static struct subprocess_infoB* callmodule_infoB;
#define TRY_USE_KPROBES 0 // 1 // enable/disable kprobes usage code
#include <linux/kprobes.h> // enable_kprobe
// for hardware breakpoint:
#include <linux/perf_event.h>
#include <linux/hw_breakpoint.h>

// define a modified struct (with extra fields) here:
struct subprocess_infoB {
  struct work_struct work;
  struct completion *complete;
  char *path;
  char **argv;
  char **envp;
  int wait; //enum umh_wait wait;
  int retval;
  int (*init)(struct subprocess_info *info);
  void (*cleanup)(struct subprocess_info *info);
  void *data;
  pid_t pid;
  struct task_struct *task;
  unsigned long long last_page_physaddr;
};

struct subprocess_infoB *call_usermodehelper_setupB(char *path, char **argv,
                          char **envp, gfp_t gfp_mask);

static inline int
call_usermodehelper_fnsB(char *path, char **argv, char **envp,
            int wait, //enum umh_wait wait,
            int (*init)(struct subprocess_info *info),
            void (*cleanup)(struct subprocess_info *), void *data)
{
  struct subprocess_info *info;
  struct subprocess_infoB *infoB;
  gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;
  int ret;

  populate_rootfs_wait();

  infoB = call_usermodehelper_setupB(path, argv, envp, gfp_mask);
  printk(KBUILD_MODNAME ":a: pid %d\n", infoB->pid);
  info = (struct subprocess_info *) infoB;

  if (info == NULL)
      return -ENOMEM;

  call_usermodehelper_setfns(info, init, cleanup, data);
  printk(KBUILD_MODNAME ":b: pid %d\n", infoB->pid);

  // this must be called first, before infoB->pid is populated (by __call_usermodehelperB):
  ret = call_usermodehelper_exec(info, wait);

  // assign global pid (and infoB) here, so rest of the code has it:
  callmodule_pid = infoB->pid;
  callmodule_infoB = infoB;    
  printk(KBUILD_MODNAME ":c: pid %d\n", callmodule_pid);

  return ret;
}

static inline int
call_usermodehelperB(char *path, char **argv, char **envp, int wait) //enum umh_wait wait)
{
  return call_usermodehelper_fnsB(path, argv, envp, wait,
                     NULL, NULL, NULL);
}

static void __call_usermodehelperB(struct work_struct *work)
{
  struct subprocess_infoB *sub_infoB =
      container_of(work, struct subprocess_infoB, work);
  int wait = sub_infoB->wait; // enum umh_wait wait = sub_info->wait;
  pid_t pid;
  struct subprocess_info *sub_info;
  // hack - declare function pointers
  int (*ptrwait_for_helper)(void *data);
  int (*ptr____call_usermodehelper)(void *data);
  // assign function pointers to verbatim addresses as obtained from /proc/kallsyms
  int killret;
  struct task_struct *spawned_task;
  ptrwait_for_helper = (void *)0xc1065b60;
  ptr____call_usermodehelper = (void *)0xc1065ed0;

  sub_info = (struct subprocess_info *)sub_infoB;

  if (wait == UMH_WAIT_PROC)
      pid = kernel_thread((*ptrwait_for_helper), sub_info, //(wait_for_helper, sub_info,
                  CLONE_FS | CLONE_FILES | SIGCHLD);
  else
      pid = kernel_thread((*ptr____call_usermodehelper), sub_info, //(____call_usermodehelper, sub_info,
                  CLONE_VFORK | SIGCHLD);

  spawned_task = pid_task(find_vpid(pid), PIDTYPE_PID);

  // stop/suspend/pause task
  killret = kill_pid(find_vpid(pid), SIGSTOP, 1); 
  if (spawned_task!=NULL) {
    // does this stop the process really?
    spawned_task->state = __TASK_STOPPED;
    printk(KBUILD_MODNAME ": : exst %d exco %d exsi %d diex %d inex %d inio %d\n", spawned_task->exit_state, spawned_task->exit_code, spawned_task->exit_signal, spawned_task->did_exec, spawned_task->in_execve, spawned_task->in_iowait);
  }
  printk(KBUILD_MODNAME ": : (kr: %d)\n", killret);
  printk(KBUILD_MODNAME ": : pid %d (%p) (%s)\n", pid, spawned_task,
    (spawned_task!=NULL)?((spawned_task->state==-1)?"unrunnable":((spawned_task->state==0)?"runnable":"stopped")):"null" );
  // grab and save the pid (and task_struct) here:
  sub_infoB->pid = pid;
  sub_infoB->task = spawned_task;
    switch (wait) {
    case UMH_NO_WAIT:
        call_usermodehelper_freeinfo(sub_info);
        break;
    case UMH_WAIT_PROC:
        if (pid > 0)
            break;
        /* FALLTHROUGH */
    case UMH_WAIT_EXEC:
        if (pid < 0)
            sub_info->retval = pid;
        complete(sub_info->complete);
    }
}

struct subprocess_infoB *call_usermodehelper_setupB(char *path, char **argv,
                          char **envp, gfp_t gfp_mask)
{
    struct subprocess_infoB *sub_infoB;
    sub_infoB = kzalloc(sizeof(struct subprocess_infoB), gfp_mask);
    if (!sub_infoB)
        goto out;

    INIT_WORK(&sub_infoB->work, __call_usermodehelperB);
    sub_infoB->path = path;
    sub_infoB->argv = argv;
    sub_infoB->envp = envp;
  out:
    return sub_infoB;
}

#if TRY_USE_KPROBES
// copy from /kernel/trace/trace_probe.c (is unexported)
int traceprobe_command(const char *buf, int (*createfn)(int, char **))
{
  char **argv;
  int argc, ret;

  argc = 0;
  ret = 0;
  argv = argv_split(GFP_KERNEL, buf, &argc);
  if (!argv)
    return -ENOMEM;

  if (argc)
    ret = createfn(argc, argv);

  argv_free(argv);

  return ret;
}

// copy from kernel/trace/trace_kprobe.c?v=2.6.38 (is unexported)
#define TP_FLAG_TRACE   1
#define TP_FLAG_PROFILE 2
typedef void (*fetch_func_t)(struct pt_regs *, void *, void *);
struct fetch_param {
  fetch_func_t    fn;
  void *data;
};
typedef int (*print_type_func_t)(struct trace_seq *, const char *, void *, void *);
enum {
  FETCH_MTD_reg = 0,
  FETCH_MTD_stack,
  FETCH_MTD_retval,
  FETCH_MTD_memory,
  FETCH_MTD_symbol,
  FETCH_MTD_deref,
  FETCH_MTD_END,
};
// Fetch type information table * /
struct fetch_type {
  const char      *name;          /* Name of type */
  size_t          size;           /* Byte size of type */
  int             is_signed;      /* Signed flag */
  print_type_func_t       print;  /* Print functions */
  const char      *fmt;           /* Fromat string */
  const char      *fmttype;       /* Name in format file */
  // Fetch functions * /
  fetch_func_t    fetch[FETCH_MTD_END];
};
struct probe_arg {
  struct fetch_param      fetch;
  struct fetch_param      fetch_size;
  unsigned int            offset; /* Offset from argument entry */
  const char              *name;  /* Name of this argument */
  const char              *comm;  /* Command of this argument */
  const struct fetch_type *type;  /* Type of this argument */
};
struct trace_probe {
  struct list_head        list;
  struct kretprobe        rp;     /* Use rp.kp for kprobe use */
  unsigned long           nhit;
  unsigned int            flags;  /* For TP_FLAG_* */
  const char              *symbol;        /* symbol name */
  struct ftrace_event_class       class;
  struct ftrace_event_call        call;
  ssize_t                 size;           /* trace entry size */
  unsigned int            nr_args;
  struct probe_arg        args[];
};
static  int probe_is_return(struct trace_probe *tp)
{
  return tp->rp.handler != NULL;
}
static int probe_event_enable(struct ftrace_event_call *call)
{
  struct trace_probe *tp = (struct trace_probe *)call->data;

  tp->flags |= TP_FLAG_TRACE;
  if (probe_is_return(tp))
    return enable_kretprobe(&tp->rp);
  else
    return enable_kprobe(&tp->rp.kp);
}
#define KPROBE_EVENT_SYSTEM "kprobes"
#endif // TRY_USE_KPROBES

// <<<<<<<<<<<<<<<<<<<<<<

static struct page *walk_page_table(unsigned long addr, struct task_struct *intask)
{
  pgd_t *pgd;
  pte_t *ptep, pte;
  pud_t *pud;
  pmd_t *pmd;

  struct page *page = NULL;
  struct mm_struct *mm = intask->mm;

  callmodule_infoB->last_page_physaddr = 0ULL; // reset here, in case of early exit

  printk(KBUILD_MODNAME ": walk_ 0x%lx ", addr);

  pgd = pgd_offset(mm, addr);
  if (pgd_none(*pgd) || pgd_bad(*pgd))
    goto out;
  printk(KBUILD_MODNAME ": Valid pgd ");

  pud = pud_offset(pgd, addr);
  if (pud_none(*pud) || pud_bad(*pud))
    goto out;
  printk( ": Valid pud");

  pmd = pmd_offset(pud, addr);
  if (pmd_none(*pmd) || pmd_bad(*pmd))
    goto out;
  printk( ": Valid pmd");

  ptep = pte_offset_map(pmd, addr);
  if (!ptep)
    goto out;
  pte = *ptep;

  page = pte_page(pte);
  if (page) {
    callmodule_infoB->last_page_physaddr = (unsigned long long)page_to_phys(page);
    printk( ": page frame struct is @ %p; *virtual (page_address) @ %p (is_vmalloc_addr %d virt_addr_valid %d virt_to_phys 0x%llx) page_to_pfn %lx page_to_phys 0x%llx", page, page_address(page), is_vmalloc_addr((void*)page_address(page)), virt_addr_valid(page_address(page)), (unsigned long long)virt_to_phys(page_address(page)), page_to_pfn(page), callmodule_infoB->last_page_physaddr);
  }

  //~ pte_unmap(ptep);

out:
  printk("\n");
  return page;
}

static void sample_hbp_handler(struct perf_event *bp,
             struct perf_sample_data *data,
             struct pt_regs *regs)
{
  trace_printk(KBUILD_MODNAME ": hwbp hit: id [%llu]\n", bp->id );
  //~ unregister_hw_breakpoint(bp);
}

// ----------------------

static int __init callmodule_init(void)
{
  int ret = 0;
  char userprog[] = "/path/to/wtest";
  char *argv[] = {userprog, "2", NULL };
  char *envp[] = {"HOME=/", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
  struct task_struct *p;
  struct task_struct *par;
  struct task_struct *pc;
  struct list_head *children_list_head;
  struct list_head *cchildren_list_head;
  char *state_str;
  unsigned long offset, taddr;
  int (*ptr_create_trace_probe)(int argc, char **argv); 
  struct trace_probe* (*ptr_find_probe_event)(const char *event, const char *group);
  //int (*ptr_probe_event_enable)(struct ftrace_event_call *call); // not exported, copy
  #if TRY_USE_KPROBES
  char trcmd[256] = "";
  struct trace_probe *tp;
  #endif //TRY_USE_KPROBES
  struct perf_event *sample_hbp, *sample_hbpb;
  struct perf_event_attr attr, attrb;

  printk(KBUILD_MODNAME ": > init %s\n", userprog);

  ptr_create_trace_probe = (void *)0xc10d5120;
  ptr_find_probe_event = (void *)0xc10d41e0;
  print_symbol(KBUILD_MODNAME ": symbol @ 0xc1065b60 is %s\n", 0xc1065b60); // shows wait_for_helper+0x0/0xb0
  print_symbol(KBUILD_MODNAME ": symbol @ 0xc1065ed0 is %s\n", 0xc1065ed0); // shows ____call_usermodehelper+0x0/0x90
  print_symbol(KBUILD_MODNAME ": symbol @ 0xc10d5120 is %s\n", 0xc10d5120); // shows create_trace_probe+0x0/0x590
  ret = call_usermodehelperB(userprog, argv, envp, UMH_WAIT_EXEC); 
  if (ret != 0)
      printk(KBUILD_MODNAME ": error in call to usermodehelper: %i\n", ret);
  else
      printk(KBUILD_MODNAME ": everything all right; pid %d (%d)\n", callmodule_pid, callmodule_infoB->pid);
  tracing_on(); // earlier, so trace_printk of handler is caught!
  // find the task:
  rcu_read_lock();
  p = pid_task(find_vpid(callmodule_pid), PIDTYPE_PID);
  rcu_read_unlock();
  if (p == NULL) {
    printk(KBUILD_MODNAME ": p is NULL - exiting\n");
    return 0;
  }
  state_str = (p->state==-1)?"unrunnable":((p->state==0)?"runnable":"stopped");
  printk(KBUILD_MODNAME ": pid task a: %p c: %s p: [%d] s: %s\n",
    p, p->comm, p->pid, state_str);
  // find parent task:
  par = p->parent;
  if (par == NULL) {
    printk(KBUILD_MODNAME ": par is NULL - exiting\n");
    return 0;
  }
  state_str = (par->state==-1)?"unrunnable":((par->state==0)?"runnable":"stopped");
  printk(KBUILD_MODNAME ": parent task a: %p c: %s p: [%d] s: %s\n",
    par, par->comm, par->pid, state_str);

  // iterate through parent's (and our task's) child processes:
  rcu_read_lock(); // read_lock(&tasklist_lock);
  list_for_each(children_list_head, &par->children){
    p = list_entry(children_list_head, struct task_struct, sibling);
    printk(KBUILD_MODNAME ": - %s [%d] \n", p->comm, p->pid);
    if (p->pid == callmodule_pid) {
      list_for_each(cchildren_list_head, &p->children){
        pc = list_entry(cchildren_list_head, struct task_struct, sibling);
        printk(KBUILD_MODNAME ": - - %s [%d] \n", pc->comm, pc->pid);
      }
    }
  }
  rcu_read_unlock(); //~ read_unlock(&tasklist_lock);

  // NOTE: here p == callmodule_infoB->task !!
  printk(KBUILD_MODNAME ": Trying to walk page table; addr task 0x%X ->mm ->start_code: 0x%08lX ->end_code: 0x%08lX \n", (unsigned int) callmodule_infoB->task, callmodule_infoB->task->mm->start_code, callmodule_infoB->task->mm->end_code);
  walk_page_table(0x08048000, callmodule_infoB->task);
  // 080483c0 is start of .text; 08048474 start of main; for objdump -S wtest
  walk_page_table(0x080483c0, callmodule_infoB->task);
  walk_page_table(0x08048474, callmodule_infoB->task);

  if (callmodule_infoB->last_page_physaddr != 0ULL) {
    printk(KBUILD_MODNAME ": physaddr ");
    taddr = 0x080483c0; // .text
    offset = taddr - callmodule_infoB->task->mm->start_code;
    printk(": (0x%08lx ->) 0x%08llx ", taddr, callmodule_infoB->last_page_physaddr+offset);
    taddr = 0x08048474; // main
    offset = taddr - callmodule_infoB->task->mm->start_code;
    printk(": (0x%08lx ->) 0x%08llx ", taddr, callmodule_infoB->last_page_physaddr+offset);
    printk("\n");

    #if TRY_USE_KPROBES // can't use this here (BUG: scheduling while atomic, if probe inserts)
    //~ sprintf(trcmd, "p:myprobe 0x%08llx", callmodule_infoB->last_page_physaddr+offset);
    // try symbol for c10bcf60 - tracing_on
    sprintf(trcmd, "p:myprobe 0x%08llx", (unsigned long long)0xc10bcf60);
    ret = traceprobe_command(trcmd, ptr_create_trace_probe); //create_trace_probe);
    printk("%s -- ret: %d\n", trcmd, ret);
    // try find probe and enable it (compiles, but untested):
    tp = ptr_find_probe_event("myprobe", KPROBE_EVENT_SYSTEM);
    if (tp != NULL) probe_event_enable(&tp->call);
    #endif //TRY_USE_KPROBES
  }

  hw_breakpoint_init(&attr);
  attr.bp_len = sizeof(long); //HW_BREAKPOINT_LEN_1;
  attr.bp_type = HW_BREAKPOINT_X ;
  attr.bp_addr = 0x08048474; // main
  sample_hbp = register_user_hw_breakpoint(&attr, (perf_overflow_handler_t)sample_hbp_handler, p);
  printk(KBUILD_MODNAME ": 0x08048474 id [%llu]\n", sample_hbp->id); //
  if (IS_ERR((void __force *)sample_hbp)) {
    int ret = PTR_ERR((void __force *)sample_hbp);
    printk(KBUILD_MODNAME ": Breakpoint registration failed (%d)\n", ret);
    //~ return ret;
  }

  hw_breakpoint_init(&attrb);
  attrb.bp_len = sizeof(long);
  attrb.bp_type = HW_BREAKPOINT_X ;
  attrb.bp_addr = 0x08048475; // first instruction after main
  sample_hbpb = register_user_hw_breakpoint(&attrb, (perf_overflow_handler_t)sample_hbp_handler, p);
  printk(KBUILD_MODNAME ": 0x08048475 id [%llu]\n", sample_hbpb->id); //45
  if (IS_ERR((void __force *)sample_hbpb)) {
    int ret = PTR_ERR((void __force *)sample_hbpb);
    printk(KBUILD_MODNAME ": Breakpoint registration failed (%d)\n", ret);
    //~ return ret;
  }

  printk(KBUILD_MODNAME ": (( 0x08048000 is_vmalloc_addr %d virt_addr_valid %d ))\n", is_vmalloc_addr((void*)0x08048000), virt_addr_valid(0x08048000));

  kill_pid(find_vpid(callmodule_pid), SIGCONT, 1); // resume/continue/restart task
  state_str = (p->state==-1)?"unrunnable":((p->state==0)?"runnable":"stopped");
  printk(KBUILD_MODNAME ": cont pid task a: %p c: %s p: [%d] s: %s\n",
    p, p->comm, p->pid, state_str);

  return 0;
}

static void __exit callmodule_exit(void)
{
  tracing_off(); //corresponds to the user space /sys/kernel/debug/tracing/tracing_on file
  printk(KBUILD_MODNAME ": < exit\n");
}

module_init(callmodule_init);
module_exit(callmodule_exit);
MODULE_LICENSE("GPL");
Другие вопросы по тегам