Ilk eBPF Programi ve Tracepoint'ler

Reading event data

Önceki alıştırmada, “Kim çalışıyor?” sorusunu sormak için bir helper kullandık.

Şimdi tracepoint’e sunulan verileri sorgulayarak çalışan process hakkında soruları yanıtlayalım.

sched_process_exec için kernel, Process ID (PID) ve filename gibi özel event verileri içeren bir structure sağlar.

Kernel’in sağladığı context’in formatını herhangi bir Linux bilgisayarında şu komutu çalıştırarak bulabilirsiniz:

$ 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;

Alternatif olarak, tanımı görmek için editörde trace_event_raw_sched_process_exec üzerine Ctrl+Click yapın.

Görev

Çalıştırılan programın PID ve filename bilgilerini doğrudan event verisinden çıkartın.

PID’yi almak bu durumda basit, doğrudan okuyabiliriz:

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

Ancak filename’i okumak daha fazla işlem gerektirir. Yukarıdaki tip bildiriminde gördüğünüz gibi, tipi __data_loc char[] şeklindedir.

Bu normal bir pointer değildir; __data_loc ön eki, alanın string’in kendisini değil, string’in kodlanmış konumunu içerdiğini belirtir.

Temel olarak, filename alanı iki bilgiyi paketleyen 32-bit bir integer içerir:

  • Alt 16 bit: Offset (String’in structure’ın başından ne kadar uzakta olduğu)
  • Üst 16 bit: Length (String’in ne kadar uzun olduğu)

Gerçek string’e ulaşmak için konumunu çözümlememiz gerekiyor:

  1. __data_loc_filename alanını okuyun
  2. Offset’i elde etmek için alt 16 bit’i maskeleyin (val & 0xFFFF)
  3. Length’i elde etmek için üst bit’leri sağa kaydırın (val >> 16)
  4. Bu offset’i ctx pointer’ına ekleyin

Ancak hesaplanan adresten veriyi doğrudan okuyamayız:

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

Neden?

Giriş bölümünde gördüğümüz gibi, eBPF programları bir sandbox içinde çalışır. Bunun gibi hesaplanan adresler (ctx + off), kernel memory’ye işaret eder (programın stack’ine değil).

DEBUG_STR’yi yerel olmayan bir adresle çağırdığımızda, kernel helper’ları o bellek aralığını okumayı reddeder ve “DEBUG” belleği başlatılmamış olarak kalır.

Yine de deneyebilirsiniz! Kernel’ı çökertmez, bunun yerine şu şekilde bir şey döndürür: �����.

Bunu çözmek için, bpf_probe_read_kernel_str helper’ını kullanarak o veriyi “yerel bellek” (stack) alanına kopyalamamız gerekir.

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

Bundan sonra fname buffer’ı doldurulmuş olacak ve üzerinde DEBUG_STR çağırabilirsiniz.

Göreviniz

Yukarıda açıklandığı gibi DEBUG_STR/DEBUG_NUM eklediyseniz, birden fazla standart program göreceksiniz, ancak bir tanesi dikkat çekiyor. Göndermek için SUBMIT_STR_LEN(buff, len) kullanın.

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