I have the ebpf code for tracing execve with tracepoints:
#define EXECVE_COMM_LENGTH 1024
#define EXECVE_ARGS_COUNT 100
#define EXECVE_ARG_SIZE 20
#define DEFAULT_SUB_BUF_LEN 16
#define DEFAULT_SUB_BUF_SIZE 255
struct p_exec_program_event
{
u64 ktime;
u32 pid;
u8 filename[EXECVE_COMM_LENGTH];
u8 args[EXECVE_ARGS_COUNT][EXECVE_ARG_SIZE];
u8 pwd[DEFAULT_SUB_BUF_LEN][DEFAULT_SUB_BUF_SIZE];
};
inline int read_dentry_strings(struct dentry *dentry, u8 buf[DEFAULT_SUB_BUF_LEN][DEFAULT_SUB_BUF_SIZE])
{
struct dentry *curr_dtry = dentry;
struct dentry *lastdtryp = dentry;
unsigned int i = 0;
if (buf)
{
bpf_probe_read_str(buf[i], DEFAULT_SUB_BUF_SIZE, BPF_CORE_READ(curr_dtry, d_name.name)); // problems start here
for (i = 1; i < DEFAULT_SUB_BUF_LEN; i++)
{
struct dentry *parent = BPF_CORE_READ(curr_dtry, d_parent);
if (parent != lastdtryp)
{
lastdtryp = parent;
curr_dtry = parent;
bpf_probe_read_str(buf[i], DEFAULT_SUB_BUF_SIZE, BPF_CORE_READ(curr_dtry, d_name.name));
}
else
break;
}
}
return 0;
}
inline int read_argv(const char *const *argv, u8 args[EXECVE_ARGS_COUNT][EXECVE_ARG_SIZE])
{
for (int i = 1; i < EXECVE_ARGS_COUNT; i++)
{
char arg[EXECVE_ARG_SIZE];
void *curr_arg = (void *)&argv[i];
if (curr_arg != NULL)
{
const char *argp = NULL;
bpf_probe_read_user(&argp, sizeof(argp), curr_arg);
if (argp != NULL)
{
bpf_probe_read_user_str(args[i - 1], EXECVE_ARG_SIZE, (void *)argp);
}
else
{
break;
}
}
else
{
break;
}
}
return 0;
}
inline int handle_execveat(char *filename, const char *const *argv)
{
struct p_exec_program_event *event_info;
if (!filename || !argv)
{
return 0;
}
event_info = bpf_ringbuf_reserve(&p_exec_program_events, sizeof(struct p_exec_program_event), 0);
if (!event_info)
{
return 0;
}
event_info->ktime = bpf_ktime_get_ns();
event_info->pid = bpf_get_current_pid_tgid();
long res = bpf_probe_read_user_str(event_info->filename, sizeof(event_info->filename), filename);
if (res)
{
if (event_info->filename[0] == '.')
{
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
struct dentry *dentry = BPF_CORE_READ(task, fs, pwd.dentry);
read_dentry_strings(dentry, event_info->pwd);
}
// else -> absolute path in filename already resolved
}
read_argv(argv, event_info->args);
bpf_ringbuf_submit(event_info, 0);
return 0;
}
SEC("tracepoint/syscalls/sys_enter_execve")
int sys_enter_execve(struct enter_execve_info *ctx)
{
return handle_execveat(ctx->filename, ctx->argv);
}
When I run this code I've got the error message:
2023/12/27 21:41:28 Loading objects: field SysEnterExecve: program sys_enter_execve: Call at insn 36: symbol "handle_execveat": unsatisfied program reference
exit status 1
Can you help me to understand what's wrong with code in read_dentry_strings (or in code at all)?
I am using ebpf2go (latest version on github)
I experimented with code and found that problem is in read_dentry_strings - without calling this function, program works fine. I began to comment all lines and found that error appears when I call bpf_probe_read_str(buf[i], DEFAULT_SUB_BUF_SIZE, BPF_CORE_READ(curr_dtry, d_name.name));
TL;DR. The error indicates the function handle_execveat
wasn't inlined. You can use __attribute__((always_inline))
to ensure the compiler will inline it.
2023/12/27 21:41:28 Loading objects: field SysEnterExecve: program sys_enter_execve: Call at insn 36: symbol "handle_execveat": unsatisfied program reference
This error means that the loader—in your case, cilium/ebpf—tried to resolve the symbol handle_execveat
in a function call.
BPF-to-BPF function calls are possible (with limitations), but given the use of inline
, this is probably not what you are trying to achieve. So we can conclude that handle_execveat
wasn't inlined as expected.
Making a function inline
doesn't actually guarantee the compiler will inline it. Making the function static
increases the chances the compiler will actually inline it. Marking it __attribute__((always_inline))
ensures it will be inline.