blob: 9165be429084ac16fda4a5afab7dc85200ca5f31 [file] [log] [blame]
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
// Copyright (c) 2020 Wenbo Zhang
//
// Based on ksyms improvements from Andrii Nakryiko, add more helpers.
// 28-Feb-2020 Wenbo Zhang Created this.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <time.h>
#include <bpf/bpf.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <limits.h>
#include "trace_helpers.h"
#include "uprobe_helpers.h"
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
#define DISK_NAME_LEN 32
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))
struct ksyms {
struct ksym *syms;
int syms_sz;
int syms_cap;
char *strs;
int strs_sz;
int strs_cap;
};
static int ksyms__add_symbol(struct ksyms *ksyms, const char *name, unsigned long addr)
{
size_t new_cap, name_len = strlen(name) + 1;
struct ksym *ksym;
void *tmp;
if (ksyms->strs_sz + name_len > ksyms->strs_cap) {
new_cap = ksyms->strs_cap * 4 / 3;
if (new_cap < ksyms->strs_sz + name_len)
new_cap = ksyms->strs_sz + name_len;
if (new_cap < 1024)
new_cap = 1024;
tmp = realloc(ksyms->strs, new_cap);
if (!tmp)
return -1;
ksyms->strs = tmp;
ksyms->strs_cap = new_cap;
}
if (ksyms->syms_sz + 1 > ksyms->syms_cap) {
new_cap = ksyms->syms_cap * 4 / 3;
if (new_cap < 1024)
new_cap = 1024;
tmp = realloc(ksyms->syms, sizeof(*ksyms->syms) * new_cap);
if (!tmp)
return -1;
ksyms->syms = tmp;
ksyms->syms_cap = new_cap;
}
ksym = &ksyms->syms[ksyms->syms_sz];
/* while constructing, re-use pointer as just a plain offset */
ksym->name = (void *)(unsigned long)ksyms->strs_sz;
ksym->addr = addr;
memcpy(ksyms->strs + ksyms->strs_sz, name, name_len);
ksyms->strs_sz += name_len;
ksyms->syms_sz++;
return 0;
}
static int ksym_cmp(const void *p1, const void *p2)
{
const struct ksym *s1 = p1, *s2 = p2;
if (s1->addr == s2->addr)
return strcmp(s1->name, s2->name);
return s1->addr < s2->addr ? -1 : 1;
}
struct ksyms *ksyms__load(void)
{
char sym_type, sym_name[256];
struct ksyms *ksyms;
unsigned long sym_addr;
int i, ret;
FILE *f;
f = fopen("/proc/kallsyms", "r");
if (!f)
return NULL;
ksyms = calloc(1, sizeof(*ksyms));
if (!ksyms)
goto err_out;
while (true) {
ret = fscanf(f, "%lx %c %s%*[^\n]\n",
&sym_addr, &sym_type, sym_name);
if (ret == EOF && feof(f))
break;
if (ret != 3)
goto err_out;
if (ksyms__add_symbol(ksyms, sym_name, sym_addr))
goto err_out;
}
/* now when strings are finalized, adjust pointers properly */
for (i = 0; i < ksyms->syms_sz; i++)
ksyms->syms[i].name += (unsigned long)ksyms->strs;
qsort(ksyms->syms, ksyms->syms_sz, sizeof(*ksyms->syms), ksym_cmp);
fclose(f);
return ksyms;
err_out:
ksyms__free(ksyms);
fclose(f);
return NULL;
}
void ksyms__free(struct ksyms *ksyms)
{
if (!ksyms)
return;
free(ksyms->syms);
free(ksyms->strs);
free(ksyms);
}
const struct ksym *ksyms__map_addr(const struct ksyms *ksyms,
unsigned long addr)
{
int start = 0, end = ksyms->syms_sz - 1, mid;
unsigned long sym_addr;
/* find largest sym_addr <= addr using binary search */
while (start < end) {
mid = start + (end - start + 1) / 2;
sym_addr = ksyms->syms[mid].addr;
if (sym_addr <= addr)
start = mid;
else
end = mid - 1;
}
if (start == end && ksyms->syms[start].addr <= addr)
return &ksyms->syms[start];
return NULL;
}
const struct ksym *ksyms__get_symbol(const struct ksyms *ksyms,
const char *name)
{
int i;
for (i = 0; i < ksyms->syms_sz; i++) {
if (strcmp(ksyms->syms[i].name, name) == 0)
return &ksyms->syms[i];
}
return NULL;
}
struct load_range {
uint64_t start;
uint64_t end;
uint64_t file_off;
};
enum elf_type {
EXEC,
DYN,
PERF_MAP,
VDSO,
UNKNOWN,
};
struct dso {
char *name;
struct load_range *ranges;
int range_sz;
/* Dyn's first text section virtual addr at execution */
uint64_t sh_addr;
/* Dyn's first text section file offset */
uint64_t sh_offset;
enum elf_type type;
struct sym *syms;
int syms_sz;
int syms_cap;
/*
* libbpf's struct btf is actually a pretty efficient
* "set of strings" data structure, so we create an
* empty one and use it to store symbol names.
*/
struct btf *btf;
};
struct map {
uint64_t start_addr;
uint64_t end_addr;
uint64_t file_off;
uint64_t dev_major;
uint64_t dev_minor;
uint64_t inode;
};
struct syms {
struct dso *dsos;
int dso_sz;
};
static bool is_file_backed(const char *mapname)
{
#define STARTS_WITH(mapname, prefix) \
(!strncmp(mapname, prefix, sizeof(prefix) - 1))
return mapname[0] && !(
STARTS_WITH(mapname, "//anon") ||
STARTS_WITH(mapname, "/dev/zero") ||
STARTS_WITH(mapname, "/anon_hugepage") ||
STARTS_WITH(mapname, "[stack") ||
STARTS_WITH(mapname, "/SYSV") ||
STARTS_WITH(mapname, "[heap]") ||
STARTS_WITH(mapname, "[vsyscall]"));
}
static bool is_perf_map(const char *path)
{
return false;
}
static bool is_vdso(const char *path)
{
return !strcmp(path, "[vdso]");
}
static int get_elf_type(const char *path)
{
GElf_Ehdr hdr;
void *res;
Elf *e;
int fd;
if (is_vdso(path))
return -1;
e = open_elf(path, &fd);
if (!e)
return -1;
res = gelf_getehdr(e, &hdr);
close_elf(e, fd);
if (!res)
return -1;
return hdr.e_type;
}
static int get_elf_text_scn_info(const char *path, uint64_t *addr,
uint64_t *offset)
{
Elf_Scn *section = NULL;
int fd = -1, err = -1;
GElf_Shdr header;
size_t stridx;
Elf *e = NULL;
char *name;
e = open_elf(path, &fd);
if (!e)
goto err_out;
err = elf_getshdrstrndx(e, &stridx);
if (err < 0)
goto err_out;
err = -1;
while ((section = elf_nextscn(e, section)) != 0) {
if (!gelf_getshdr(section, &header))
continue;
name = elf_strptr(e, stridx, header.sh_name);
if (name && !strcmp(name, ".text")) {
*addr = (uint64_t)header.sh_addr;
*offset = (uint64_t)header.sh_offset;
err = 0;
break;
}
}
err_out:
close_elf(e, fd);
return err;
}
static int syms__add_dso(struct syms *syms, struct map *map, const char *name)
{
struct dso *dso = NULL;
int i, type;
void *tmp;
for (i = 0; i < syms->dso_sz; i++) {
if (!strcmp(syms->dsos[i].name, name)) {
dso = &syms->dsos[i];
break;
}
}
if (!dso) {
tmp = realloc(syms->dsos, (syms->dso_sz + 1) *
sizeof(*syms->dsos));
if (!tmp)
return -1;
syms->dsos = tmp;
dso = &syms->dsos[syms->dso_sz++];
memset(dso, 0, sizeof(*dso));
dso->name = strdup(name);
dso->btf = btf__new_empty();
}
tmp = realloc(dso->ranges, (dso->range_sz + 1) * sizeof(*dso->ranges));
if (!tmp)
return -1;
dso->ranges = tmp;
dso->ranges[dso->range_sz].start = map->start_addr;
dso->ranges[dso->range_sz].end = map->end_addr;
dso->ranges[dso->range_sz].file_off = map->file_off;
dso->range_sz++;
type = get_elf_type(name);
if (type == ET_EXEC) {
dso->type = EXEC;
} else if (type == ET_DYN) {
dso->type = DYN;
if (get_elf_text_scn_info(name, &dso->sh_addr, &dso->sh_offset) < 0)
return -1;
} else if (is_perf_map(name)) {
dso->type = PERF_MAP;
} else if (is_vdso(name)) {
dso->type = VDSO;
} else {
dso->type = UNKNOWN;
}
return 0;
}
static struct dso *syms__find_dso(const struct syms *syms, unsigned long addr,
uint64_t *offset)
{
struct load_range *range;
struct dso *dso;
int i, j;
for (i = 0; i < syms->dso_sz; i++) {
dso = &syms->dsos[i];
for (j = 0; j < dso->range_sz; j++) {
range = &dso->ranges[j];
if (addr <= range->start || addr >= range->end)
continue;
if (dso->type == DYN || dso->type == VDSO) {
/* Offset within the mmap */
*offset = addr - range->start + range->file_off;
/* Offset within the ELF for dyn symbol lookup */
*offset += dso->sh_addr - dso->sh_offset;
} else {
*offset = addr;
}
return dso;
}
}
return NULL;
}
static int dso__load_sym_table_from_perf_map(struct dso *dso)
{
return -1;
}
static int dso__add_sym(struct dso *dso, const char *name, uint64_t start,
uint64_t size)
{
struct sym *sym;
size_t new_cap;
void *tmp;
int off;
off = btf__add_str(dso->btf, name);
if (off < 0)
return off;
if (dso->syms_sz + 1 > dso->syms_cap) {
new_cap = dso->syms_cap * 4 / 3;
if (new_cap < 1024)
new_cap = 1024;
tmp = realloc(dso->syms, sizeof(*dso->syms) * new_cap);
if (!tmp)
return -1;
dso->syms = tmp;
dso->syms_cap = new_cap;
}
sym = &dso->syms[dso->syms_sz++];
/* while constructing, re-use pointer as just a plain offset */
sym->name = (void*)(unsigned long)off;
sym->start = start;
sym->size = size;
return 0;
}
static int sym_cmp(const void *p1, const void *p2)
{
const struct sym *s1 = p1, *s2 = p2;
if (s1->start == s2->start)
return strcmp(s1->name, s2->name);
return s1->start < s2->start ? -1 : 1;
}
static int dso__add_syms(struct dso *dso, Elf *e, Elf_Scn *section,
size_t stridx, size_t symsize)
{
Elf_Data *data = NULL;
while ((data = elf_getdata(section, data)) != 0) {
size_t i, symcount = data->d_size / symsize;
if (data->d_size % symsize)
return -1;
for (i = 0; i < symcount; ++i) {
const char *name;
GElf_Sym sym;
if (!gelf_getsym(data, (int)i, &sym))
continue;
if (!(name = elf_strptr(e, stridx, sym.st_name)))
continue;
if (name[0] == '\0')
continue;
if (sym.st_value == 0)
continue;
if (dso__add_sym(dso, name, sym.st_value, sym.st_size))
goto err_out;
}
}
return 0;
err_out:
return -1;
}
static void dso__free_fields(struct dso *dso)
{
if (!dso)
return;
free(dso->name);
free(dso->ranges);
free(dso->syms);
btf__free(dso->btf);
}
static int dso__load_sym_table_from_elf(struct dso *dso, int fd)
{
Elf_Scn *section = NULL;
Elf *e;
int i;
e = fd > 0 ? open_elf_by_fd(fd) : open_elf(dso->name, &fd);
if (!e)
return -1;
while ((section = elf_nextscn(e, section)) != 0) {
GElf_Shdr header;
if (!gelf_getshdr(section, &header))
continue;
if (header.sh_type != SHT_SYMTAB &&
header.sh_type != SHT_DYNSYM)
continue;
if (dso__add_syms(dso, e, section, header.sh_link,
header.sh_entsize))
goto err_out;
}
/* now when strings are finalized, adjust pointers properly */
for (i = 0; i < dso->syms_sz; i++)
dso->syms[i].name =
btf__name_by_offset(dso->btf,
(unsigned long)dso->syms[i].name);
qsort(dso->syms, dso->syms_sz, sizeof(*dso->syms), sym_cmp);
close_elf(e, fd);
return 0;
err_out:
dso__free_fields(dso);
close_elf(e, fd);
return -1;
}
static int create_tmp_vdso_image(struct dso *dso)
{
uint64_t start_addr, end_addr;
long pid = getpid();
char buf[PATH_MAX];
void *image = NULL;
char tmpfile[128];
int ret, fd = -1;
uint64_t sz;
char *name;
FILE *f;
snprintf(tmpfile, sizeof(tmpfile), "/proc/%ld/maps", pid);
f = fopen(tmpfile, "r");
if (!f)
return -1;
while (true) {
ret = fscanf(f, "%lx-%lx %*s %*x %*x:%*x %*u%[^\n]",
&start_addr, &end_addr, buf);
if (ret == EOF && feof(f))
break;
if (ret != 3)
goto err_out;
name = buf;
while (isspace(*name))
name++;
if (!is_file_backed(name))
continue;
if (is_vdso(name))
break;
}
sz = end_addr - start_addr;
image = malloc(sz);
if (!image)
goto err_out;
memcpy(image, (void *)start_addr, sz);
snprintf(tmpfile, sizeof(tmpfile),
"/tmp/libbpf_%ld_vdso_image_XXXXXX", pid);
fd = mkostemp(tmpfile, O_CLOEXEC);
if (fd < 0) {
fprintf(stderr, "failed to create temp file: %s\n",
strerror(errno));
goto err_out;
}
/* Unlink the file to avoid leaking */
if (unlink(tmpfile) == -1)
fprintf(stderr, "failed to unlink %s: %s\n", tmpfile,
strerror(errno));
if (write(fd, image, sz) == -1) {
fprintf(stderr, "failed to write to vDSO image: %s\n",
strerror(errno));
close(fd);
fd = -1;
goto err_out;
}
err_out:
fclose(f);
free(image);
return fd;
}
static int dso__load_sym_table_from_vdso_image(struct dso *dso)
{
int fd = create_tmp_vdso_image(dso);
if (fd < 0)
return -1;
return dso__load_sym_table_from_elf(dso, fd);
}
static int dso__load_sym_table(struct dso *dso)
{
if (dso->type == UNKNOWN)
return -1;
if (dso->type == PERF_MAP)
return dso__load_sym_table_from_perf_map(dso);
if (dso->type == EXEC || dso->type == DYN)
return dso__load_sym_table_from_elf(dso, 0);
if (dso->type == VDSO)
return dso__load_sym_table_from_vdso_image(dso);
return -1;
}
static struct sym *dso__find_sym(struct dso *dso, uint64_t offset)
{
unsigned long sym_addr;
int start, end, mid;
if (!dso->syms && dso__load_sym_table(dso))
return NULL;
start = 0;
end = dso->syms_sz - 1;
/* find largest sym_addr <= addr using binary search */
while (start < end) {
mid = start + (end - start + 1) / 2;
sym_addr = dso->syms[mid].start;
if (sym_addr <= offset)
start = mid;
else
end = mid - 1;
}
if (start == end && dso->syms[start].start <= offset)
return &dso->syms[start];
return NULL;
}
struct syms *syms__load_file(const char *fname)
{
char buf[PATH_MAX], perm[5];
struct syms *syms;
struct map map;
char *name;
FILE *f;
int ret;
f = fopen(fname, "r");
if (!f)
return NULL;
syms = calloc(1, sizeof(*syms));
if (!syms)
goto err_out;
while (true) {
ret = fscanf(f, "%lx-%lx %4s %lx %lx:%lx %lu%[^\n]",
&map.start_addr, &map.end_addr, perm,
&map.file_off, &map.dev_major,
&map.dev_minor, &map.inode, buf);
if (ret == EOF && feof(f))
break;
if (ret != 8) /* perf-<PID>.map */
goto err_out;
if (perm[2] != 'x')
continue;
name = buf;
while (isspace(*name))
name++;
if (!is_file_backed(name))
continue;
if (syms__add_dso(syms, &map, name))
goto err_out;
}
fclose(f);
return syms;
err_out:
syms__free(syms);
fclose(f);
return NULL;
}
struct syms *syms__load_pid(pid_t tgid)
{
char fname[128];
snprintf(fname, sizeof(fname), "/proc/%ld/maps", (long)tgid);
return syms__load_file(fname);
}
void syms__free(struct syms *syms)
{
int i;
if (!syms)
return;
for (i = 0; i < syms->dso_sz; i++)
dso__free_fields(&syms->dsos[i]);
free(syms->dsos);
free(syms);
}
const struct sym *syms__map_addr(const struct syms *syms, unsigned long addr)
{
struct dso *dso;
uint64_t offset;
dso = syms__find_dso(syms, addr, &offset);
if (!dso)
return NULL;
return dso__find_sym(dso, offset);
}
struct syms_cache {
struct {
struct syms *syms;
int tgid;
} *data;
int nr;
};
struct syms_cache *syms_cache__new(int nr)
{
struct syms_cache *syms_cache;
syms_cache = calloc(1, sizeof(*syms_cache));
if (!syms_cache)
return NULL;
if (nr > 0)
syms_cache->data = calloc(nr, sizeof(*syms_cache->data));
return syms_cache;
}
void syms_cache__free(struct syms_cache *syms_cache)
{
int i;
if (!syms_cache)
return;
for (i = 0; i < syms_cache->nr; i++)
syms__free(syms_cache->data[i].syms);
free(syms_cache->data);
free(syms_cache);
}
struct syms *syms_cache__get_syms(struct syms_cache *syms_cache, int tgid)
{
void *tmp;
int i;
for (i = 0; i < syms_cache->nr; i++) {
if (syms_cache->data[i].tgid == tgid)
return syms_cache->data[i].syms;
}
tmp = realloc(syms_cache->data, (syms_cache->nr + 1) *
sizeof(*syms_cache->data));
if (!tmp)
return NULL;
syms_cache->data = tmp;
syms_cache->data[syms_cache->nr].syms = syms__load_pid(tgid);
syms_cache->data[syms_cache->nr].tgid = tgid;
return syms_cache->data[syms_cache->nr++].syms;
}
struct partitions {
struct partition *items;
int sz;
};
static int partitions__add_partition(struct partitions *partitions,
const char *name, unsigned int dev)
{
struct partition *partition;
void *tmp;
tmp = realloc(partitions->items, (partitions->sz + 1) *
sizeof(*partitions->items));
if (!tmp)
return -1;
partitions->items = tmp;
partition = &partitions->items[partitions->sz];
partition->name = strdup(name);
partition->dev = dev;
partitions->sz++;
return 0;
}
struct partitions *partitions__load(void)
{
char part_name[DISK_NAME_LEN];
unsigned int devmaj, devmin;
unsigned long long nop;
struct partitions *partitions;
char buf[64];
FILE *f;
f = fopen("/proc/partitions", "r");
if (!f)
return NULL;
partitions = calloc(1, sizeof(*partitions));
if (!partitions)
goto err_out;
while (fgets(buf, sizeof(buf), f) != NULL) {
/* skip heading */
if (buf[0] != ' ' || buf[0] == '\n')
continue;
if (sscanf(buf, "%u %u %llu %s", &devmaj, &devmin, &nop,
part_name) != 4)
goto err_out;
if (partitions__add_partition(partitions, part_name,
MKDEV(devmaj, devmin)))
goto err_out;
}
fclose(f);
return partitions;
err_out:
partitions__free(partitions);
fclose(f);
return NULL;
}
void partitions__free(struct partitions *partitions)
{
int i;
if (!partitions)
return;
for (i = 0; i < partitions->sz; i++)
free(partitions->items[i].name);
free(partitions->items);
free(partitions);
}
const struct partition *
partitions__get_by_dev(const struct partitions *partitions, unsigned int dev)
{
int i;
for (i = 0; i < partitions->sz; i++) {
if (partitions->items[i].dev == dev)
return &partitions->items[i];
}
return NULL;
}
const struct partition *
partitions__get_by_name(const struct partitions *partitions, const char *name)
{
int i;
for (i = 0; i < partitions->sz; i++) {
if (strcmp(partitions->items[i].name, name) == 0)
return &partitions->items[i];
}
return NULL;
}
static void print_stars(unsigned int val, unsigned int val_max, int width)
{
int num_stars, num_spaces, i;
bool need_plus;
num_stars = min(val, val_max) * width / val_max;
num_spaces = width - num_stars;
need_plus = val > val_max;
for (i = 0; i < num_stars; i++)
printf("*");
for (i = 0; i < num_spaces; i++)
printf(" ");
if (need_plus)
printf("+");
}
void print_log2_hist(unsigned int *vals, int vals_size, const char *val_type)
{
int stars_max = 40, idx_max = -1;
unsigned int val, val_max = 0;
unsigned long long low, high;
int stars, width, i;
for (i = 0; i < vals_size; i++) {
val = vals[i];
if (val > 0)
idx_max = i;
if (val > val_max)
val_max = val;
}
if (idx_max < 0)
return;
printf("%*s%-*s : count distribution\n", idx_max <= 32 ? 5 : 15, "",
idx_max <= 32 ? 19 : 29, val_type);
if (idx_max <= 32)
stars = stars_max;
else
stars = stars_max / 2;
for (i = 0; i <= idx_max; i++) {
low = (1ULL << (i + 1)) >> 1;
high = (1ULL << (i + 1)) - 1;
if (low == high)
low -= 1;
val = vals[i];
width = idx_max <= 32 ? 10 : 20;
printf("%*lld -> %-*lld : %-8d |", width, low, width, high, val);
print_stars(val, val_max, stars);
printf("|\n");
}
}
void print_linear_hist(unsigned int *vals, int vals_size, unsigned int base,
unsigned int step, const char *val_type)
{
int i, stars_max = 40, idx_min = -1, idx_max = -1;
unsigned int val, val_max = 0;
for (i = 0; i < vals_size; i++) {
val = vals[i];
if (val > 0) {
idx_max = i;
if (idx_min < 0)
idx_min = i;
}
if (val > val_max)
val_max = val;
}
if (idx_max < 0)
return;
printf(" %-13s : count distribution\n", val_type);
for (i = idx_min; i <= idx_max; i++) {
val = vals[i];
printf(" %-10d : %-8d |", base + i * step, val);
print_stars(val, val_max, stars_max);
printf("|\n");
}
}
unsigned long long get_ktime_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
}
bool is_kernel_module(const char *name)
{
bool found = false;
char buf[64];
FILE *f;
f = fopen("/proc/modules", "r");
if (!f)
return false;
while (fgets(buf, sizeof(buf), f) != NULL) {
if (sscanf(buf, "%s %*s\n", buf) != 1)
break;
if (!strcmp(buf, name)) {
found = true;
break;
}
}
fclose(f);
return found;
}
static bool fentry_try_attach(int id)
{
struct bpf_insn insns[] = { { .code = BPF_JMP | BPF_EXIT } };
LIBBPF_OPTS(bpf_prog_load_opts, opts);
int prog_fd, attach_fd;
opts.expected_attach_type = BPF_TRACE_FENTRY;
opts.attach_btf_id = id,
prog_fd = bpf_prog_load(BPF_PROG_TYPE_TRACING, "test", NULL, insns, 1, &opts);
if (prog_fd < 0)
return false;
attach_fd = bpf_raw_tracepoint_open(NULL, prog_fd);
if (attach_fd >= 0)
close(attach_fd);
close(prog_fd);
return attach_fd >= 0;
}
bool fentry_can_attach(const char *name, const char *mod)
{
const char sysfs_vmlinux[] = "/sys/kernel/btf/vmlinux";
struct btf *base, *btf = NULL;
char sysfs_mod[80];
int id = -1, err;
base = btf__parse(sysfs_vmlinux, NULL);
if (!base) {
err = -errno;
fprintf(stderr, "failed to parse vmlinux BTF at '%s': %s\n",
sysfs_vmlinux, strerror(-err));
goto err_out;
}
if (mod && module_btf_exists(mod)) {
snprintf(sysfs_mod, sizeof(sysfs_mod), "/sys/kernel/btf/%s", mod);
btf = btf__parse_split(sysfs_mod, base);
if (!btf) {
err = -errno;
fprintf(stderr, "failed to load BTF from %s: %s\n",
sysfs_mod, strerror(-err));
btf = base;
base = NULL;
}
} else {
btf = base;
base = NULL;
}
id = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
err_out:
btf__free(btf);
btf__free(base);
return id > 0 && fentry_try_attach(id);
}
bool kprobe_exists(const char *name)
{
char sym_name[256];
FILE *f;
int ret;
f = fopen("/sys/kernel/debug/tracing/available_filter_functions", "r");
if (!f)
goto slow_path;
while (true) {
ret = fscanf(f, "%s%*[^\n]\n", sym_name);
if (ret == EOF && feof(f))
break;
if (ret != 1) {
fprintf(stderr, "failed to read symbol from available_filter_functions\n");
break;
}
if (!strcmp(name, sym_name)) {
fclose(f);
return true;
}
}
fclose(f);
return false;
slow_path:
f = fopen("/proc/kallsyms", "r");
if (!f)
return false;
while (true) {
ret = fscanf(f, "%*x %*c %s%*[^\n]\n", sym_name);
if (ret == EOF && feof(f))
break;
if (ret != 1) {
fprintf(stderr, "failed to read symbol from kallsyms\n");
break;
}
if (!strcmp(name, sym_name)) {
fclose(f);
return true;
}
}
fclose(f);
return false;
}
bool vmlinux_btf_exists(void)
{
if (!access("/sys/kernel/btf/vmlinux", R_OK))
return true;
return false;
}
bool module_btf_exists(const char *mod)
{
char sysfs_mod[80];
if (mod) {
snprintf(sysfs_mod, sizeof(sysfs_mod), "/sys/kernel/btf/%s", mod);
if (!access(sysfs_mod, R_OK))
return true;
}
return false;
}