1120 lines
22 KiB
C
1120 lines
22 KiB
C
/* 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;
|
|
}
|