Skip to content

Commit

Permalink
feat: add kprobe sample and link impli
Browse files Browse the repository at this point in the history
  • Loading branch information
takehaya committed Sep 13, 2024
1 parent 6e92815 commit 9860624
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 42 deletions.
4 changes: 2 additions & 2 deletions lib/Sys/Ebpf/Asm.pm
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ sub get_imm { $_[0]->{imm} }
sub deserialize_128bit_instruction {
my ($binary) = @_;

my $high = Sys::Ebpf::asm->deserialize( substr( $binary, 0, 8 ) );
my $low = Sys::Ebpf::asm->deserialize( substr( $binary, 8, 8 ) );
my $high = Sys::Ebpf::Asm->deserialize( substr( $binary, 0, 8 ) );
my $low = Sys::Ebpf::Asm->deserialize( substr( $binary, 8, 8 ) );

return ( $high, $low );
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Sys/Ebpf/Elf/Parser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ sub parse_elf {
$elf->{e_type} = $e_type;
$elf->{e_machine} = $e_machine;
$elf->{e_machine_name}
= Sys::Ebpf::elf::MachineType->get_machine_name($e_machine);
= Sys::Ebpf::Elf::MachineType->get_machine_name($e_machine);
$elf->{e_version} = $e_version;
$elf->{e_entry} = $e_entry;
$elf->{e_phoff} = $e_phoff;
Expand Down
40 changes: 40 additions & 0 deletions lib/Sys/Ebpf/Link/Perf/Arch.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package Sys::Ebpf::Link::Perf::Arch;

use strict;
use warnings;
use utf8;

use Config qw( %Config );

sub platform_prefix {
my $arch = $Config{archname};
if ( $arch =~ /^i[3456]86/ ) {
return "__ia32_";
}
elsif ( $arch =~ /^x86_64/ ) {
return "__x64_";
}
elsif ( $arch =~ /^arm/ ) {
return "__arm_";
}
elsif ( $arch =~ /^aarch64/ ) {
return "__arm64_";
}
elsif ( $arch =~ /^mips/ ) {
return "__mips_";
}
elsif ( $arch =~ /^s390/ ) {
return "__s390_";
}
elsif ( $arch =~ /^powerpc/ ) {
return $arch =~ /64/ ? "__powerpc64_" : "__powerpc_";
}
elsif ( $arch =~ /^riscv/ ) {
return "__riscv_";
}
else {
return "";
}
}

1;
216 changes: 216 additions & 0 deletions lib/Sys/Ebpf/Link/Perf/Kprobe.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package Sys::Ebpf::Link::Perf::Kprobe;

use strict;
use warnings;
use utf8;

use POSIX qw( close fcntl open sprintf );
use Fcntl qw( F_GETFL );
use IO::Handle qw( print );
use Sys::Ebpf::Link::Perf::Arch qw( platform_prefix );
use open ':std', ':encoding(UTF-8)';

use constant {
PERF_EVENT_IOC_ENABLE => 0x2400,
PERF_EVENT_IOC_DISABLE => 0x2401,
PERF_EVENT_IOC_SET_BPF => 0x40042408,
PERF_TYPE_TRACEPOINT => 2,
PERF_SAMPLE_RAW => 1 << 10,
PERF_FLAG_FD_CLOEXEC => 0x00000008,
SYS_perf_event_open => 298, # x86_64 システムの場合。他のアーキテクチャでは異なる可能性があります。
SYS_ioctl => 16,
};

sub attach_kprobe {
my ( $prog_fd, $kprobe_fn ) = @_;

# kprobe 関数名の抽出
my $func_name;
if ( $kprobe_fn =~ m{^kprobe/(.+)$} ) {
$func_name = $1;
}
else {
die "無効な kprobe 関数名です: $kprobe_fn";
}

my $prefix = Sys::Ebpf::Link::Perf::Arch::platform_prefix();
$func_name = $prefix . $func_name unless $func_name =~ /^$prefix/;

# ユニークなイベント名を生成
my $current_pid = $$;
my $event_name = "p_${func_name}_${current_pid}";

# kprobe_events ファイルのパスを動的に取得
my $kprobe_events;
if ( -e '/sys/kernel/debug/tracing/kprobe_events' ) {
$kprobe_events = '/sys/kernel/debug/tracing/kprobe_events';
}
elsif ( -e '/sys/kernel/tracing/kprobe_events' ) {
$kprobe_events = '/sys/kernel/tracing/kprobe_events';
}
else {
die "kprobe_events ファイルが見つかりません";
}

print "kprobe_events: $kprobe_events\n";
print "p:$event_name $func_name\n";

# kprobe イベントの作成
open my $fh, '>>', $kprobe_events or die "kprobe_events を開けません: $!";
$fh->autoflush(1);
print $fh "p:$event_name $func_name\n"
or die "kprobe_events への書き込みに失敗しました: $!";
close $fh or warn "kprobe_events のクローズに失敗しました: $!";

# イベント ID の取得
my $id_file = $kprobe_events;
$id_file =~ s/kprobe_events/events\/kprobes\/$event_name\/id/;
unless ( -e $id_file ) {
die "イベント ID ファイルが存在しません。kprobe イベントの作成に失敗した可能性があります。";
}
open my $id_fh, '<', $id_file or die "イベント ID ファイルを開けません: $!";
my $id = <$id_fh>;
chomp $id;
close $id_fh;

my $pack_str = "L" . # type, __u32
"L" . #size, __u32
"Q" . #config, __u64
"Q" . #sample_period, __u64
"Q" . #sample_type, __u64
"Q" . #read_format, __u64
"Q" . #ビットフィールド (disabled, inherit, etc.), __u64
"L" . #wakeup_events, __u32
"L" . #bp_type, __u32
"Q" . #config1 (bp_addr / kprobe_func / uprobe_path), __u64
"Q" . #config2 (bp_len / kprobe_addr / probe_offset), __u64
"Q" . #branch_sample_type, __u64
"Q" . #sample_regs_user, __u64
"L" . #sample_stack_user, __u32
"L" . #clockid, __s32
"Q" . #sample_regs_intr, __u64
"L" . #aux_watermark. __u32
"S" . #sample_max_stack, __u16
"S" . #__reserved_2, __u16
"L" . #aux_sample_size, __u32
"L" . #__reserved_3, __u32
"Q" . #sig_data, __u64
"Q"; #config3, __u64
my $attr_size = 0;
$attr_size += { C => 1, S => 2, L => 4, Q => 8 }->{$_}
for split //, $pack_str;
print "attr_size: $attr_size\n";
my $attr = pack(
$pack_str,
PERF_TYPE_TRACEPOINT, # type, __u32
$attr_size, # size (構造体全体のサイズ), __u32
$id, # config (kprobe_id), __u64
1, # sample_period, __u64
PERF_SAMPLE_RAW, # sample_type, __u64
0, # read_format, __u64
0, # ビットフィールド (disabled, inherit, etc.), __u64
1, # wakeup_events, __u32
0, # bp_type, __u32
0, # config1 (bp_addr / kprobe_func / uprobe_path), __u64
0, # config2 (bp_len / kprobe_addr / probe_offset), __u64
0, # branch_sample_type, __u64
0, # sample_regs_user, __u64
0, # sample_stack_user, __u32
0, # clockid, __s32
0, # sample_regs_intr, __u64
0, # aux_watermark. __u32
0, # sample_max_stack, __u16
0, # __reserved_2, __u16
0, # aux_sample_size, __u32
0, # __reserved_3, __u32
0, # sig_data, __u64
0, # config3, __u64
);

# perf_event_open システムコールの呼び出し
my $target_pid = -1; # すべてのプロセスを監視
my $cpu = 0; # すべてのCPUで監視
my $group_fd = -1;
my $flags = PERF_FLAG_FD_CLOEXEC;

my $perf_fd
= syscall( SYS_perf_event_open, $attr, $target_pid, $cpu, $group_fd,
$flags );
if ( $perf_fd < 0 ) {
my $errno = $! + 0;
my $error_msg = $!;
die sprintf( "perf イベントのオープンに失敗しました: %s (errno: %d)\n",
$error_msg, $errno );
}

print "perf_event_open successful. File descriptor: $perf_fd\n";

# BPF プログラムを perf イベントにアタッチ
$! = 0; # reset errno
my $res
= syscall( &SYS_ioctl, $perf_fd, PERF_EVENT_IOC_SET_BPF, $prog_fd );
my $errno = $! + 0;
print "ioctl res: $res\n";
if ( !defined $res || $res < 0 ) {
my $error_msg = $!;
die sprintf( "BPF プログラムのアタッチに失敗しました: %s (errno: %d)\n",
$error_msg, $errno );
}

print "BPF program attached successfully\n";

# perf イベントを有効化
$! = 0; # reset errno
# $res = ioctl($perf_fh, PERF_EVENT_IOC_ENABLE, 0)|| -1;
$res = syscall( &SYS_ioctl, $perf_fd, PERF_EVENT_IOC_ENABLE, 0 );
$errno = $! + 0;
if ( !defined $res || $res < 0 ) {
my $error_msg = $!;
die sprintf( "perf イベントの有効化に失敗しました: %s (errno: %d)\n",
$error_msg, $errno );
}

print "perf event enabled successfully\n";

# デタッチ時に必要な情報を返す
return {
perf_fd => $perf_fd,
event_name => $event_name,
kprobe_fn => $kprobe_fn,
kprobe_events => $kprobe_events,
};
}

sub check_prog_fd {
my ($prog_fd) = @_;
return 0 if $prog_fd < 0;

my $flags = fcntl( $prog_fd, F_GETFL, 0 );
return 0 if !defined $flags || $flags < 0;

return 1;
}

sub detach_kprobe {
my ($kprobe_info) = @_;

my $perf_fd = $kprobe_info->{perf_fd};
my $event_name = $kprobe_info->{event_name};
my $kprobe_events = $kprobe_info->{kprobe_events};

# perf イベントを無効化
my $res = syscall( SYS_ioctl, $perf_fd, PERF_EVENT_IOC_DISABLE, 0 );
if ( $res != 0 ) {
warn "perf イベントの無効化に失敗しました: $!";
}

# perf イベントのファイルディスクリプタをクローズ
close($perf_fd);

# kprobe イベントの削除
open my $fh, '>>', $kprobe_events or warn "kprobe_events を開けません: $!";
$fh->autoflush(1);
print $fh "-:$event_name\n" or warn "kprobe_events への書き込みに失敗しました: $!";
close $fh or warn "kprobe_events のクローズに失敗しました: $!";
}
1;
17 changes: 8 additions & 9 deletions lib/Sys/Ebpf/Loader.pm
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,12 @@ sub load_bpf_program {
# prob_section: プログラムセクション
# reloc_sections: リロケーションセクション
# elf: ELFデータ
# map_data: マップデータのリファレンス
# map_hash_ref: マップデータのリファレンス
# r_offsetを使って、修正すべき命令(インストラクション)をkprobe/sys_execveセクション内から特定します。
# r_infoからシンボルインデックスを取得し、そのシンボルのアドレスをシンボルテーブル(.symtab)から取得します。
# 修正すべきインストラクションに、シンボルのアドレスを適用して、正しいマップへの参照に書き換えます。
sub apply_map_relocations {
my ( $self, $prob_section, $reloc_sections, $elf, $map_data ) = @_;
my ( $self, $prob_section, $reloc_sections, $elf, $map_hash_ref ) = @_;
my $symbols_section = $elf->{symbols};
for my $reloc_section (@$reloc_sections) {
my $r_info = $reloc_section->{r_info};
Expand All @@ -249,14 +249,13 @@ sub apply_map_relocations {

# `$map_data` の中のタプルを確認して、期待してるマップ名が存在するかチェック(fdを取得)
my $map_fd = undef;
for my $tuple (@$map_data) {
my ( $map_name, $map ) = @$tuple;
for my $map_name ( keys %$map_hash_ref ) {
my $map = $map_hash_ref->{$map_name};
if ( $sym_name eq $map_name ) {
$map_fd = $map->{map_fd};
last; # マップが見つかったらループを抜ける
}
}

if ( defined $map_fd ) {

# # 指定されたオフセット位置にある `lddw` 命令(16バイト)を取得
Expand Down Expand Up @@ -308,7 +307,7 @@ sub load_bpf {
my $bpfelf = $self->{bpfelf};
my $maps = $self->extract_bpf_map_attributes('maps');

my @map_collection;
my %map_collection;

# map_attr_refの各キー(マップ名)に対して処理を実行
for my $map (@$maps) {
Expand All @@ -318,7 +317,7 @@ sub load_bpf {
if ( $map_fd < 0 ) {
die "Failed to load BPF map: $map_name (FD: $map_fd})\n";
}
push @map_collection, [ $map_name, $map_instance ];
$map_collection{$map_name} = $map_instance;
}

# リロケーションを適用
Expand All @@ -328,14 +327,14 @@ sub load_bpf {
= find_symbol_table_from_name( $bpfelf->{sections}, $section_name );
if ( defined $reloc_section ) {
$self->apply_map_relocations( $prob_section, $reloc_section, $bpfelf,
\@map_collection );
\%map_collection );
}

# todo: bpfprobが複数あるケースにも対応する
# BPF プログラムをロード
my $prog_fd = $self->load_bpf_program_from_elf($section_name);

return ( \@map_collection, $prog_fd );
return ( \%map_collection, $prog_fd );
}

1;
9 changes: 6 additions & 3 deletions lib/Sys/Ebpf/Map.pm
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use Sys::Ebpf::Constants::BpfCmd qw(
BPF_MAP_UPDATE_ELEM
BPF_OBJ_PIN
);
use Sys::Ebpf::Constants::BpfMapType qw(:all);
use Sys::Ebpf::Constants::BpfMapUpdateFlags qw(:all);
use Sys::Ebpf::Constants::BpfMapCreateFlags qw(:all);
use Sys::Ebpf::Constants::BpfMapType qw( BPF_MAP_TYPE_UNSPEC );
use Sys::Ebpf::Constants::BpfMapUpdateFlags qw( BPF_ANY );
use Sys::Ebpf::Constants::BpfMapCreateFlags ();
use Sys::Ebpf::Syscall;

sub new {
Expand Down Expand Up @@ -203,6 +203,9 @@ sub update {

sub lookup {
my ( $self, $key ) = @_;
if ( !defined $key ) {
die "Key must be defined to use lookup method\n";
}
if ( !defined $self->{key_schema} || !defined $self->{value_schema} ) {
die "Key and value schema must be defined to use update method\n";
}
Expand Down
Loading

0 comments on commit 9860624

Please sign in to comment.