linux/scripts/tracepoint-update.c

233 lines
5.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include "elf-parse.h"
static Elf_Shdr *check_data_sec;
static Elf_Shdr *tracepoint_data_sec;
static inline void *get_index(void *start, int entsize, int index)
{
return start + (entsize * index);
}
static int compare_strings(const void *a, const void *b)
{
const char *av = *(const char **)a;
const char *bv = *(const char **)b;
return strcmp(av, bv);
}
struct elf_tracepoint {
Elf_Ehdr *ehdr;
const char **array;
int count;
};
#define REALLOC_SIZE (1 << 10)
#define REALLOC_MASK (REALLOC_SIZE - 1)
static int add_string(const char *str, const char ***vals, int *count)
{
const char **array = *vals;
if (!(*count & REALLOC_MASK)) {
int size = (*count) + REALLOC_SIZE;
array = realloc(array, sizeof(char *) * size);
if (!array) {
fprintf(stderr, "Failed memory allocation\n");
return -1;
}
*vals = array;
}
array[(*count)++] = str;
return 0;
}
/**
* for_each_shdr_str - iterator that reads strings that are in an ELF section.
* @len: "int" to hold the length of the current string
* @ehdr: A pointer to the ehdr of the ELF file
* @sec: The section that has the strings to iterate on
*
* This is a for loop that iterates over all the nul terminated strings
* that are in a given ELF section. The variable "str" will hold
* the current string for each iteration and the passed in @len will
* contain the strlen() of that string.
*/
#define for_each_shdr_str(len, ehdr, sec) \
for (const char *str = (void *)(ehdr) + shdr_offset(sec), \
*end = str + shdr_size(sec); \
len = strlen(str), str < end; \
str += (len) + 1)
static void make_trace_array(struct elf_tracepoint *etrace)
{
Elf_Ehdr *ehdr = etrace->ehdr;
const char **vals = NULL;
int count = 0;
int len;
etrace->array = NULL;
/*
* The __tracepoint_check section is filled with strings of the
* names of tracepoints (in tracepoint_strings). Create an array
* that points to each string and then sort the array.
*/
for_each_shdr_str(len, ehdr, check_data_sec) {
if (!len)
continue;
if (add_string(str, &vals, &count) < 0)
return;
}
/* If CONFIG_TRACEPOINT_VERIFY_USED is not set, there's nothing to do */
if (!count)
return;
qsort(vals, count, sizeof(char *), compare_strings);
etrace->array = vals;
etrace->count = count;
}
static int find_event(const char *str, void *array, size_t size)
{
return bsearch(&str, array, size, sizeof(char *), compare_strings) != NULL;
}
static void check_tracepoints(struct elf_tracepoint *etrace)
{
Elf_Ehdr *ehdr = etrace->ehdr;
int len;
if (!etrace->array)
return;
/*
* The __tracepoints_strings section holds all the names of the
* defined tracepoints. If any of them are not in the
* __tracepoint_check_section it means they are not used.
*/
for_each_shdr_str(len, ehdr, tracepoint_data_sec) {
if (!len)
continue;
if (!find_event(str, etrace->array, etrace->count)) {
fprintf(stderr, "warning: tracepoint '%s' is unused.\n", str);
}
}
free(etrace->array);
}
static void *tracepoint_check(struct elf_tracepoint *etrace)
{
make_trace_array(etrace);
check_tracepoints(etrace);
return NULL;
}
static int process_tracepoints(void *addr, char const *const fname)
{
struct elf_tracepoint etrace = {0};
Elf_Ehdr *ehdr = addr;
Elf_Shdr *shdr_start;
Elf_Shdr *string_sec;
const char *secstrings;
unsigned int shnum;
unsigned int shstrndx;
int shentsize;
int idx;
int done = 2;
shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr));
shentsize = ehdr_shentsize(ehdr);
shstrndx = ehdr_shstrndx(ehdr);
if (shstrndx == SHN_XINDEX)
shstrndx = shdr_link(shdr_start);
string_sec = get_index(shdr_start, shentsize, shstrndx);
secstrings = (const char *)ehdr + shdr_offset(string_sec);
shnum = ehdr_shnum(ehdr);
if (shnum == SHN_UNDEF)
shnum = shdr_size(shdr_start);
for (int i = 0; done && i < shnum; i++) {
Elf_Shdr *shdr = get_index(shdr_start, shentsize, i);
idx = shdr_name(shdr);
/* locate the __tracepoint_check in vmlinux */
if (!strcmp(secstrings + idx, "__tracepoint_check")) {
check_data_sec = shdr;
done--;
}
/* locate the __tracepoints_ptrs section in vmlinux */
if (!strcmp(secstrings + idx, "__tracepoints_strings")) {
tracepoint_data_sec = shdr;
done--;
}
}
if (!check_data_sec) {
fprintf(stderr, "no __tracepoint_check in file: %s\n", fname);
return -1;
}
if (!tracepoint_data_sec) {
fprintf(stderr, "no __tracepoint_strings in file: %s\n", fname);
return -1;
}
etrace.ehdr = ehdr;
tracepoint_check(&etrace);
return 0;
}
int main(int argc, char *argv[])
{
int n_error = 0;
size_t size = 0;
void *addr = NULL;
if (argc < 2) {
fprintf(stderr, "usage: tracepoint-update vmlinux...\n");
return 0;
}
/* Process each file in turn, allowing deep failure. */
for (int i = 1; i < argc; i++) {
addr = elf_map(argv[i], &size, 1 << ET_REL);
if (!addr) {
++n_error;
continue;
}
if (process_tracepoints(addr, argv[i]))
++n_error;
elf_unmap(addr, size);
}
return !!n_error;
}