Concept familiarization

Reading event data

In the previous exercise, we used a helper to ask “Who is running?”.

Now let’s answer questions about the running process by querying the data available to the tracepoint.

For sched_process_exec, the kernel provides a structure with specific event data, like the Process ID (PID) and the filename.

You can find the format of the context that the kernel provides on any Linux computer, by running:

$ cat /sys/kernel/tracing/events/sched/sched_process_exec/format
format:
  unsigned short common_type;           offset:0;   size:2;
  unsigned char common_flags;           offset:2;   size:1;
  unsigned char common_preempt_count;   offset:3;   size:1;
  int common_pid;                       offset:4;   size:4;
  __data_loc char[] filename;           offset:8;   size:4;
  pid_t pid;                            offset:12;  size:4;
  pid_t old_pid;                        offset:16;  size:4;

Alternatively, Ctrl+Click on the trace_event_raw_sched_process_exec in the editor to see the definition.

The challenge

Extract the PID and the filename of the program being executed, directly from the event data.

Getting the PID is straightforward in this case, we can just read it:

int pid = ctx->pid;
DEBUG_NUM("PID", pid);

Reading the filename, however, is more involved. If you look at the type declaration above, it is __data_loc char[].

This is not a regular pointer; the __data_loc prefix means the field just contains the encoded location of the string, not the string itself.

Basically, the filename field contains a 32-bit integer that packs two pieces of information:

  • Lower 16 bits: The Offset (How far away the string is from the start of the structure)
  • Upper 16 bits: The Length (How long the string is)

To get the actual string, we need to decode its location:

  1. Read the __data_loc_filename field
  2. Mask the lower 16 bits (val & 0xFFFF) to get the offset
  3. Right shift the upper bits (val >> 16) to get the length
  4. Add that offset to the ctx pointer

However, we can’t really read the data directly from the calculated address:

char* fname = (void *)ctx + off;
DEBUG_STR("Filename", fname);

Why not?

As we saw in the intro, eBPF programs run in a sandbox. Calculated addresses like this one (ctx + off) point to Kernel memory (not the program’s stack).

When we call DEBUG_STR with a non-local address, the kernel helpers will refuse to read from that memory range, leaving the “DEBUG” memory uninitialized.

You can try it though! It will not crash the kernel, instead, it will return something like: �����.

To solve this, we need to copy that data to “local memory” (stack) using the bpf_probe_read_kernel_str helper.

char fname[32];
bpf_probe_read_kernel_str(fname, sizeof(fname), (void *)ctx + off);

After this, the fname buffer will be populated, and we can call DEBUG_STR on it.

Your task

If you added DEBUG_STR/DEBUG_NUM as explained above, you should see multiple standard programs, but there’s one that stands out. Use SUBMIT_STR_LEN(buff, len) to submit it.

Quick reference
Compare two strings for equality
Function bpf_strncmp Full documentation
Args:
char* bufdynamic buffer to compare
u32 buf_szlength of dynamic buffer
const char* buf2literal string to compare against
Returns 0 if strings match, non-zero if they differ
Run your code to see execution events here