Захват сборки пользовательского пространства с помощью 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()
добраться до его физического адреса!
- Поскольку здесь я ожидаю, что у меня будет физический адрес, я не использую PAGE_SHIFT, но вычисляю смещения непосредственно из
- Настройка
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");