Closed anutatesl closed 2 months ago
После просмотра хедеров ядра пока удалось вывести из dentry имя файла, номер inode, имя parent директории. Пока совсем "втупую" без проверок и прочего.
printf("%-6d %-16s %s %lu %s\n", pid, comm,
str($task->mm->exe_file->f_path.dentry->d_name.name),
$task->mm->exe_file->f_path.dentry->d_inode->i_ino,
str($task->mm->exe_file->f_path.dentry->d_parent->d_name.name));
Подключенные хедера в скрипте:
#include <linux/sched.h>
#include <linux/mm.h>
В итоге:
dkoptev@build:~$ sudo bpftrace test.bt | grep prometheus
758 prometheus-node prometheus-node-exporter 1052741 bin
758 prometheus-node prometheus-node-exporter 1052741 bin
...
dkoptev@build:~$ ls -i /usr/bin/prometheus-node-exporter
1052741 /usr/bin/prometheus-node-exporter
Покопайте дальше. Наверняка и через kprobe, и через tracepoint как-то напечатать/вычислить полный путь можно.
#ifndef BPFTRACE_HAVE_BTF
#include <linux/sched.h>
#include <linux/limits.h>
#endif
struct my_spa_t {
char spa_name[64];
}
BEGIN
{
printf("Tracing file system syscalls... Hit Ctrl-C to end.\n");
printf("%-6s %-16s %-16s %4s %-60s %-4s %-4s\n", "PID", "COMM", "COMMTASK", "FD", "PATH", "R", "W");
}
tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
{
@filename[tid] = args.filename;
}
tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
$ret = args.ret;
@fd[tid] = $ret >= 0 ? $ret : -1;
}
tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
$task = (struct task_struct *)curtask;
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
//printf("%-6d %-16s %-16 %4d %-60s %-4s %-4d\n", pid, comm, ((struct mm_struct *)$task->mm)->exe_file->f_path, @fd[tid], str(@filename[tid]), $nothing, $ret);
/* Ваши комментарии
printf("%-6d %-16s %s %lu %s\n", pid, comm,
str($task->mm->exe_file->f_path.dentry->d_name.name),
$task->mm->exe_file->f_path.dentry->d_inode->i_ino,
str($task->mm->exe_file->f_path.dentry->d_parent->d_name.name));
*/
/* Нашли реализацию ф-ции d_path (https://android.googlesource.com/kernel/common/+/baa23246e93f/fs/d_path.c) и по аналогии попытались выводить у себя:
$buf = (struct my_spa_t *)args0;
$res = $task->mm->exe_file->f_path->dentry->d_op->d_dname($task->mm->exe_file->f_path->dentry, $buf, PATH_MAX);
print(str($task->mm->exe_file->f_path->dentry->d_op->d_dname($task->mm->exe_file->f_path->dentry, $buf, PATH_MAX)));
Ругается: ERROR: syntax error, unexpected (, expecting ) or ","
print(str($task->mm->exe_file->f_path.dentry->d_op->d_dname($task->mm->exe_file->f_path.dentry, (struct my_spa_t *)args0, PATH_MAX));
*/
// Решили получить путь рекурсивно через d_parent
// Здесь упираемся в момент того, что не понимаем, какое условие задать для while
// Вывод сейчас:
// bin 5810 usr 264 snapshot 256 1 257 .snapshots 256 @ 256 / 256 / 256 / 256 / 256 / 256
// pipe 58366 bpftrace 5866 Desktop 274 anna 257 home 256 @ 256 / 256 / 256 / 256 / 256 / 256
$part_path = $task->mm->exe_file->f_path.dentry->d_parent;
$i = 1;
while ($i < 12) {
printf("%s %lu ", str($part_path->d_name.name), $part_path->d_inode->i_ino);
$part_path = $part_path->d_parent;
$i = $i + 1;
}
print("\n");
delete(@filename[tid]);
delete(@fd[tid]);
}
//Аналогично tracepoint:syscalls:sys_exit_write
/*
tracepoint:syscalls:sys_exit_read,
tracepoint:syscalls:sys_exit_readv,
tracepoint:syscalls:sys_exit_preadv
/@fd[tid]/
{
$task = (struct task_struct *)curtask;
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
//printf("%-6d %-16s %-16s %4d %-60s %-4d %-4s\n", pid, comm, str($task->nameidata->name->name), @fd[tid], str(@filename[tid]), $ret, $nothing);
//1.Вывод того, что выдает структура f_path
//Вывод: { .mnt = 0xffff973af64f31a0, .dentry = 0xffff973ad1517240 }
//print(((struct mm_struct *)$task->mm)->exe_file->f_path);
//2.Попытка получить путь к файлу через функцию path
//Ошибка: The path function can only be used with 'kfunc', 'kretfunc', 'iter' probes
//print(path(((struct mm_struct *)$task->mm)->exe_file->f_path));
delete(@filename[tid]);
delete(@fd[tid]);
}
*/
END
{
clear(@filename);
clear(@fd);
}
Мы определили, что inode равный 256 является inod корневой директории (есть проблема с snapshots - у него получается следующая ситуация с inode и папками: snapshot 256 /1 257/.snapshots 256)
Есть проблема с тем, что из-за рекурсии путь выводится в обратном порядке (от файла к корневой директории), попробовали через массив, не получилось (пометки сделала к скрипте)
Сам скрипт
#ifndef BPFTRACE_HAVE_BTF
#include <linux/sched.h>
#include <linux/limits.h>
#endif
//struct MyStruct {
// s64 p[1000];
//}
BEGIN
{
printf("Tracing file system syscalls... Hit Ctrl-C to end.\n");
printf("%-16s %-16s %4s %-60s %-4s %-4s\n", "COMM", "COMMTASK", "FD", "PATH", "R", "W");
}
tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat
{
@filename[tid] = args.filename;
}
tracepoint:syscalls:sys_exit_open,
tracepoint:syscalls:sys_exit_openat
/@filename[tid]/
{
$ret = args.ret;
@fd[tid] = $ret >= 0 ? $ret : -1;
}
tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
$task = (struct task_struct *)curtask;
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
// Двумя разными способами пробовали создавать массив, чтобы выводить путь в правильном порядке, но безуспешно
// 1. через струткуру (ошибка, не дает ей ничего присвоить)
//$full_path_comm = (struct MyStruct *)arg0;
//$full_path_comm->p[0] = comm;
// 2. следующим образом: (ошибка, ругается на цикл по данному массиву (строки 66 - 68))
//@full_path_comm[comm] = "";
$part_path = $task->mm->exe_file->f_path.dentry->d_parent;
$i = 1;
printf("%s",comm);
//Значение 1000 взято из головы
while ($i != 1000) {
//@full_path_comm[comm] = str($part_path->d_name.name);
printf("/%s", str($part_path->d_name.name));
$part_path = $part_path->d_parent;
if ((uint64)$part_path->d_inode->i_ino == 256) {
printf("/%s", str($part_path->d_name.name));
break;
}
$i = $i + 1;
}
//for ($kv : @full_path_comm) {
// print($kv.1); // value
//}
printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);
delete(@filename[tid]);
delete(@fd[tid]);
//delete(@full_path_comm)
}
tracepoint:syscalls:sys_exit_read,
tracepoint:syscalls:sys_exit_readv,
tracepoint:syscalls:sys_exit_preadv
/@fd[tid]/
{
$task = (struct task_struct *)curtask;
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
$part_path = $task->mm->exe_file->f_path.dentry->d_parent;
$i = 1;
printf("%s",comm);
while ($i != 1000) {
printf("/%s", str($part_path->d_name.name));
$part_path = $part_path->d_parent;
if ((uint64)$part_path->d_inode->i_ino == 256) {
printf("/%s", str($part_path->d_name.name));
break;
}
$i = $i + 1;
}
printf(" %d %s %d %s\n", @fd[tid], str(@filename[tid]), $ret, $nothing);
delete(@filename[tid]);
delete(@fd[tid]);
}
END
{
clear(@filename);
clear(@fd);
//clear(@$full_path_comm);
}
Вывод имеет следующий вид:
anna@localhost:~/Desktop/bpftrace/FileSystem> sudo bpftrace fsorw.bt
[sudo] password for root:
Attaching 12 probes...
Tracing file system syscalls... Hit Ctrl-C to end.
COMM COMMTASK FD PATH R W
KIO::WorkerThre/bin/usr/snapshot -1 /dev/disk/by-label - 8
KIO::WorkerThre/bin/usr/snapshot 26 /proc/self/mountinfo 1024 -
X/bin/usr/snapshot 62 /usr/bin/VBoxClient 31 -
VBoxClient/bin/usr/snapshot 8 /run/user/1000/xauth_TyieTb 111 -
X/bin/usr/snapshot 62 /usr/bin/VBoxClient 31 -
fifo2.out/pipe/bpftrace/Desktop/anna/home 3 /lib64/libc.so.6 832 -
fifo1.out/pipe/bpftrace/Desktop/anna/home 3 /lib64/libc.so.6 832 -
fifo1.out/pipe/bpftrace/Desktop/anna/home 3 myfifo2 8 -
fifo2.out/pipe/bpftrace/Desktop/anna/home 3 myfifo2 9 -
Корень файловой системы далеко не всегда будет иметь inode 256. Наоборот, в простых сценариях /
имеет inode=2.
В любом случае, завязываться на конкретный номер нельзя. Где-то в структурах должна храниться рутовая inode или её номер. Для референса можно внимательно изучить, как устроена функция тут: https://sourceware.org/git/gitweb.cgi?p=systemtap.git;a=blob;f=tapset/linux/dentry.stp;h=4e73532f24b96ee67be3adb4f7c16d5608f8f608;hb=HEAD#l184
И ещё вопрос: на какой системе это тестируется? Т.е. это локальная ВМка, где-то в облаке, докер, WSL или что-то ещё? inode 256 для /
выглядит крайне нетипично
FYI: ссылка, которую я скинул, это про реализацию для systemtap. Systemtap - это ещё одна утилита для трейсинга Linux. Мне в это не верится, но, если мы с концами упремся в то, что через bpftrace получить путь к бинарнику нельзя, можно будет попробовать systemtap.
Из того, что мы нашли, есть следующие вариации хоть каких-то массивов в bpftrace:
Скрипт
tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
$task = (struct task_struct *)curtask;
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
$part_path = $task->mm->exe_file->f_path.dentry->d_parent;
$i = 0;
//Значение 1000 взято из головы
while ($i != 1000) {
@full_path_comm[$i] = str($part_path->d_name.name);
/* Этот пример взят из документации и даже тут он ругается на for
@map[10] = 20;
for ($kv : @map) {
print($kv.0); // key
print($kv.1); // value
}*/
//printf("/%s %lu", str($part_path->d_name.name), $part_path->d_inode->i_ino);
$part_path = $part_path->d_parent;
if ((uint64)$part_path->d_inode->i_ino == 256) {
//printf("/%s", str($part_path->d_name.name));
break;
}
$i = $i + 1;
}
for ($kv : @full_path_comm) {
print($kv.1); // value
}
print("\n");
//printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);
delete(@filename[tid]);
delete(@fd[tid]);
delete(@full_path_comm)
}
Ошибка
anna@localhost:~/Desktop/bpftrace/FileSystem> sudo bpftrace fsorw.bt
fsorw.bt:59:3-6: ERROR: syntax error, unexpected for, expecting }
for ($kv : @full_path_comm) {
~~~
Кортежи Есть следующий пример
i:s:1 {
$a = (1,2);
$b = (3,4, $a);
печать($a);
печать($b);
печать($b.0);
}
Печать:
(1, 2)
(3, 4, (1, 2))
3
Мы решили, что его использовать не можем, так как при выводе всего кортежа получаем что-то подобное (3, 4, (1, 2))
, а не нормальный путь и это придется в go-коде обрабатывать. С таким же успехом можно получать просто перевернутый путь (что мы сейчас и делаем) и в go-коде обрабатывать его.
Массивы bpftrace поддерживает доступ к одномерным массивам, подобным тем, которые можно найти в C. Построение массивов с нуля, таких как int a[] = {1,2,3} в C, не поддерживается. Они могут быть считаны в переменную только из указателя.
Пример
struct MyStruct {
int y[4];
}
kprobe:dummy {
$s = (struct MyStruct *) arg0;
print($s->y[0]);
}
Мы не сообразили, как это адаптировать в нашем случае и записывать в массив свои кусочки имени полного пути.
Получилось выводить абсолютный путь к исполняемому файлу в нужной последовательности
Осталась проблема с тем, что если это файл из snapshots (у него получается следующая ситуация с inode и папками: /.snapshots(256)/1(257) /snapshot(256)/usr/bin/.....), то путь начинается со snapshot (пример вывода 1), с остальным такого не наблюдалось. Пробовала прям в скрипте убирать snapshot - ругается (пример вывода 2), можно будет убирать это в go в конце концов.
Пример вывода 1:
Attaching 9 probes...
Tracing file system syscalls... Hit Ctrl-C to end.
COMM FD PATH R W
snapshot/usr/bin/plasmashell -1 / - 408
snapshot/usr/bin/X 63 /usr/bin/VBoxClient - 8
snapshot/usr/bin/konsole 18 pipe : fifo1.out - 52
snapshot/usr/bin/VBoxClient 8 /run/user/1000/xauth_BkPaXD - 48
snapshot/usr/bin/X 63 /usr/bin/VBoxClient - 8
home/anna/Desktop/bpftrace/pipe/fifo1.out 3 myfifo2 - 7
home/anna/Desktop/bpftrace/pipe/fifo2.out 3 myfifo2 - 13
Пример вывода 2:
Attaching 9 probes...
WARNING: Error loading program: tracepoint:syscalls:sys_exit_write (try -v), skipping.
ioctl(PERF_EVENT_IOC_SET_BPF): Bad file descriptor
Часть, где были внесены изменения:
tracepoint:syscalls:sys_exit_write,
tracepoint:syscalls:sys_exit_writev,
tracepoint:syscalls:sys_exit_pwritev
/@fd[tid]/
{
$ret = args.ret;
$nbyte = $ret >= 0 ? $ret : -1;
$nothing = "-";
$task = (struct task_struct *)curtask;
$part_path = $task->mm->exe_file->f_path.dentry->d_parent;
$i = 0;
@full_path_comm[$i] = $part_path->d_name.name;
$i = 1;
while ($i != 1000) {
$part_path = $part_path->d_parent;
@full_path_comm[$i] = $part_path->d_name.name;
if ((uint64)$part_path->d_inode->i_ino == 256) {
break;
}
$i = $i + 1;
}
printf("/");
while ($i != -1) {
$str_ = @full_path_comm[$i];
printf("%s/", str($str_));
$i = $i - 1;
}
printf("%s",comm);
printf(" %d %s %s %d\n", @fd[tid], str(@filename[tid]), $nothing, $ret);
delete(@filename[tid]);
delete(@fd[tid]);
}