lcc/glcc/lib/hook/hook.c (323 lines of code) (raw):
#include <stdio.h>
#include <stdarg.h>
#include <linux/bpf.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <linux/limits.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/perf_event.h>
#include <dlfcn.h>
#include "hook.h"
#define RTLD_NEXT ((void *)-1l)
typedef int (*ioctl_t)(int __fd, unsigned long int __request, ...);
typedef FILE *(*fopen_t)(const char *__filename, const char *__modes);
typedef FILE *(*fopen64_t)(const char *__filename, const char *__modes);
typedef long int (*syscall_t)(long int __sysno, ...);
static ioctl_t ioctl_p = NULL;
static fopen_t fopen_p = NULL;
static syscall_t syscall_p = NULL;
static fopen64_t fopen64_p = NULL;
static struct hook_env
{
bool init_done;
bool init_failed;
int ebpfdrv_fd;
} env = {
.init_done = false,
.init_failed = false,
};
struct ebpfdrv_attr ebpfdrv_atrr;
#define __NR_perf_event_open 298
#define __NR_bpf 321
static int env_init()
{
int err = 0;
if (env.init_done)
return 0;
if (env.init_failed)
{
pr_dbg("ebpfdrv has init failed, we don't try again.\n");
return -EACCES;
}
ioctl_p = (ioctl_t)dlsym(RTLD_NEXT, "ioctl");
if (ioctl_p == NULL)
return -ENOTSUP;
#if 1 // we need this to avoid libbpf create perf event by kprobe_events
fopen_p = (fopen_t)dlsym(RTLD_NEXT, "fopen");
if (fopen_p == NULL)
return -ENOTSUP;
fopen64_p = (fopen64_t)dlsym(RTLD_NEXT, "fopen64");
if (fopen64_p == NULL)
return -ENOTSUP;
#endif
syscall_p = (syscall_t)dlsym(RTLD_NEXT, "syscall");
if (syscall_p == NULL)
return -ENOTSUP;
#define EBPFDRV_PATH "/dev/ebpfdrv"
#define F_OK 0 /* Test for existence. */
env.ebpfdrv_fd = open(EBPFDRV_PATH, O_RDWR);
if (env.ebpfdrv_fd < 0)
{
env.init_failed = true;
err = -errno;
pr_err("failed to open: %s, error message: %s\n", EBPFDRV_PATH, strerror(errno));
return err;
}
pr_dbg("init ebpfdrv sucessfully.\n");
env.init_done = true;
return err;
}
static int bpf_prog_attach(union bpf_attr *attr)
{
pr_dbg("attach program type is %u and program name is %s\n", attr->attach_type, attr->prog_name);
return 0;
}
long int handle_bpf_call(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
{
int err;
switch (cmd)
{
case BPF_MAP_CREATE:
pr_dbg("BPF_MAP_CREATE\n");
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_MAP_CREATE, attr);
pr_dbg("BPF_MAP_CREATE result is %d\n", err);
break;
case BPF_MAP_LOOKUP_ELEM:
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_MAP_LOOKUP_ELEM, attr);
break;
case BPF_MAP_UPDATE_ELEM:
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_MAP_UPDATE_ELEM, attr);
break;
case BPF_MAP_DELETE_ELEM:
pr_dbg("BPF_MAP_DELETE_ELEM\n");
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_MAP_DELETE_ELEM, attr);
pr_dbg("BPF_MAP_DELETE_ELEM result is %d\n", err);
break;
case BPF_MAP_GET_NEXT_KEY:
pr_dbg("BPF_MAP_GET_NEXT_KEY\n");
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_MAP_GET_NEXT_KEY, attr);
pr_dbg("BPF_MAP_GET_NEXT_KEY result is %d\n", err);
break;
case BPF_PROG_LOAD:
pr_dbg("BPF_PROG_LOAD\n");
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_PROG_LOAD, attr);
pr_dbg("BPF_PROG_LOAD result is %d\n", err);
break;
case BPF_PROG_ATTACH:
pr_dbg("BPF_PROG_ATTACH\n");
err = bpf_prog_attach(attr);
break;
case BPF_PROG_DETACH:
pr_dbg("BPF_PROG_DETACH\n");
// err = bpf_prog_detach(&attr);
break;
case BPF_PROG_TEST_RUN:
pr_dbg("BPF_PROG_TEST_RUN\n");
break;
case BPF_BTF_LOAD:
pr_dbg("BPF_BTF_LOAD\n");
err = 0;
break;
case BPF_OBJ_GET_INFO_BY_FD:
pr_dbg("BPF_OBJ_GET_INFO_BY_FD\n");
err = ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_OBJ_GET_INFO_BY_FD, attr);
pr_dbg("BPF_OBJ_GET_INFO_BY_FD result is %d\n", err);
break;
default:
pr_dbg("Unexpected bpf cmd, cmd is %d\n", cmd);
err = -EINVAL;
break;
}
return err;
}
int handle_perf_call(struct perf_event_attr *old_attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
struct perf_event_attr attr = {};
int type;
int err;
int pfd = -EINVAL;
const char *name = (char *)old_attr->config1;
if (old_attr->config == PERF_COUNT_SW_BPF_OUTPUT || old_attr->config == PERF_COUNT_SW_DUMMY)
{
pr_dbg("perf_event_open create perf buffer\n");
old_attr->config = PERF_COUNT_SW_DUMMY;
pfd = syscall_p(__NR_perf_event_open, old_attr, pid, /* pid */ cpu, /* cpu */ group_fd /* group_fd */, flags);
if (pfd < 0)
{
err = -errno;
pr_err("create perf buffer failed: %s\n", strerror(err));
return err;
}
}
else // kprobe or tracepoint
{
// config1 is function name
if (old_attr->config1 == 0)
{
// tracepoint
pr_dbg("perf_event_open create perf event, type is tracepoint\n");
pfd = 0xbeef; // fake perf event fd
}
else
{
pr_dbg("perf_event_open create perf event, type is kprobe\n");
// kprobe
strcpy(ebpfdrv_atrr.name, name);
pr_dbg("store kprobe function name: %s\n", ebpfdrv_atrr.name);
pfd = 0xbeef; // fake perf event fd
}
}
return pfd;
}
long int syscall(long int __sysno, ...)
{
int err;
struct perf_event_attr *attr = NULL;
pid_t pid;
int cpu;
int group_fd;
unsigned long flags;
va_list valist;
va_start(valist, __sysno);
err = env_init();
if (err < 0)
{
pr_err("env init error, error %d, error string %s\n", err, strerror(err));
return err;
}
switch (__sysno)
{
case __NR_perf_event_open:
attr = va_arg(valist, struct perf_event_attr *);
pid = va_arg(valist, pid_t);
cpu = va_arg(valist, int);
group_fd = va_arg(valist, int);
flags = va_arg(valist, unsigned long);
va_end(valist);
err = handle_perf_call(attr, pid, cpu, group_fd, flags);
break;
case __NR_bpf:
{
enum bpf_cmd cmd;
union bpf_attr *attr;
unsigned int size;
cmd = va_arg(valist, enum bpf_cmd);
attr = va_arg(valist, union bpf_attr *);
size = va_arg(valist, unsigned int);
va_end(valist);
err = handle_bpf_call(cmd, attr, size);
break;
}
default:
{
pr_dbg("unexpected syscall nr %u\n", __sysno);
unsigned long addr = (unsigned long)&__sysno;
err = syscall_p(__sysno, (char *)(addr + 8));
break;
}
}
return err;
}
int ioctl(int __fd, unsigned long int __request, ...)
{
int err;
va_list valist;
__u32 prog_fd;
va_start(valist, __request);
err = env_init();
if (err < 0)
{
pr_err("env init error, error %d, error string %s\n", err, strerror(err));
return err;
}
// todo: check __fd not only __request
switch (__request)
{
// detach and free buffer
case PERF_EVENT_IOC_DISABLE:
{
pr_dbg("PERF_EVENT_IOC_DISABLE not implement.\n");
err = 0;
break;
}
// attach
case PERF_EVENT_IOC_SET_BPF:
{
pr_dbg("PERF_EVENT_IOC_SET_BPF\n");
prog_fd = va_arg(valist, __u32);
va_end(valist);
ebpfdrv_atrr.prog_fd = prog_fd;
dump_ebpfdrv_attr(&ebpfdrv_atrr);
ioctl_p(env.ebpfdrv_fd, IOCTL_BPF_PROG_ATTACH, &ebpfdrv_atrr);
pr_dbg("PERF_EVENT_IOC_SET_BPF result %d\n", err);
break;
}
// attach and enable
case PERF_EVENT_IOC_ENABLE:
{
err = 0;
pr_dbg("PERF_EVENT_IOC_ENABLE result %d\n", err);
break;
}
default:
{
pr_dbg("unexpected ioctl\n");
unsigned long addr = (unsigned long)__request;
err = ioctl_p(__fd, __request, (char *)(addr + 8));
break;
}
}
return err;
}
// Just let sucessfully open file but not used.
#if 1
FILE *fopen_common_handle(const char *__filename, const char *__modes, bool is64)
{
int err;
char subsys[128];
char eventname[128];
#define REAL_KPROBE_TYPE_FILE "/sys/bus/event_source/devices/kprobe/type"
// It does not matter let libbpf read tracepoint type.
#define FAKE_KPROBE_TYPE_FILE "/sys/bus/event_source/devices/tracepoint/type"
#define REAL_KRETPROBE_TYPE_FILE "/sys/bus/event_source/devices/kretprobe/type"
#define FAKE_KRETPROBE_TYPE_FILE FAKE_KPROBE_TYPE_FILE
#define TRACEPOINT_TYPE_FILE_PREFIX "/sys/kernel/debug/tracing/events"
// pr_dbg("fopen%s :filename: %s\n", is64 ? "64" : "", __filename);
err = env_init();
if (err < 0)
{
pr_err("env init error, error %d, error string %s\n", err, strerror(err));
return NULL;
}
if (strncmp(__filename, REAL_KPROBE_TYPE_FILE, sizeof(REAL_KPROBE_TYPE_FILE) - 1) == 0)
{
ebpfdrv_atrr.is_return = false;
if (!is64)
return fopen_p(FAKE_KPROBE_TYPE_FILE, __modes);
else
return fopen64_p(FAKE_KPROBE_TYPE_FILE, __modes);
}
if (strncmp(__filename, REAL_KRETPROBE_TYPE_FILE, sizeof(REAL_KRETPROBE_TYPE_FILE) - 1) == 0)
{
ebpfdrv_atrr.is_return = true;
if (!is64)
return fopen_p(FAKE_KRETPROBE_TYPE_FILE, __modes);
else
return fopen64_p(FAKE_KRETPROBE_TYPE_FILE, __modes);
}
if (strncmp(TRACEPOINT_TYPE_FILE_PREFIX, __filename, sizeof(TRACEPOINT_TYPE_FILE_PREFIX) - 1) == 0)
{
sscanf(__filename, "/sys/kernel/debug/tracing/events/%[^/]/%[^/]/id", subsys, eventname);
pr_dbg("subsys:%s, eventname:%s\n", subsys, eventname);
strcpy(ebpfdrv_atrr.name, eventname);
pr_dbg("store tracepoint function name: %s\n", ebpfdrv_atrr.name);
}
if (!is64)
return fopen_p(__filename, __modes);
else
return fopen64_p(__filename, __modes);
}
FILE *fopen(const char *__filename, const char *__modes)
{
return fopen_common_handle(__filename, __modes, false);
}
// Just let sucessfully open file but not used.
FILE *fopen64(const char *__filename, const char *__modes)
{
return fopen_common_handle(__filename, __modes, true);
}
#endif