vivarium 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTEXT.md +535 -0
- data/README.md +2 -2
- data/examples/raise_demo.rb +42 -0
- data/examples/sudo_attempt_demo.rb +18 -0
- data/exe/vivarium +6 -0
- data/image.png +0 -0
- data/lib/vivarium/cli.rb +40 -0
- data/lib/vivarium/correlator.rb +137 -0
- data/lib/vivarium/tree_renderer.rb +543 -0
- data/lib/vivarium/version.rb +1 -1
- data/lib/vivarium.rb +314 -171
- data/logo-simple.png +0 -0
- metadata +28 -5
- data/lib/vivarium/logger.rb +0 -80
data/lib/vivarium.rb
CHANGED
|
@@ -7,7 +7,7 @@ require "pathname"
|
|
|
7
7
|
require "rbbcc"
|
|
8
8
|
require "socket"
|
|
9
9
|
require_relative "vivarium/version"
|
|
10
|
-
require_relative "vivarium/
|
|
10
|
+
require_relative "vivarium/cli"
|
|
11
11
|
|
|
12
12
|
module Vivarium
|
|
13
13
|
class Error < StandardError; end
|
|
@@ -16,8 +16,7 @@ module Vivarium
|
|
|
16
16
|
CONFIG_ROOT_TARGETS_PIN = File.join(PIN_DIR, "config_root_targets")
|
|
17
17
|
CONFIG_SPAWNED_TARGETS_PIN = File.join(PIN_DIR, "config_spawned_targets")
|
|
18
18
|
CONFIG_TARGETS_PIN = CONFIG_ROOT_TARGETS_PIN
|
|
19
|
-
|
|
20
|
-
EVENT_WRITE_POS_PIN = File.join(PIN_DIR, "event_write_pos")
|
|
19
|
+
EVENTS_PIN = File.join(PIN_DIR, "events")
|
|
21
20
|
|
|
22
21
|
EVENT_NAME_SIZE = 16
|
|
23
22
|
EVENT_PAYLOAD_SIZE = 256
|
|
@@ -27,9 +26,33 @@ module Vivarium
|
|
|
27
26
|
EVENT_STRUCT_SIZE = 288
|
|
28
27
|
EVENT_TS_OFFSET = 0
|
|
29
28
|
EVENT_PID_OFFSET = 8
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
EVENT_TID_OFFSET = 12
|
|
30
|
+
EVENT_NAME_OFFSET = 16
|
|
31
|
+
EVENT_PAYLOAD_OFFSET = 32
|
|
32
|
+
EVENTS_RINGBUF_PAGES = 256
|
|
33
|
+
SPAN_ALLOWCLASSES = [
|
|
34
|
+
Socket,
|
|
35
|
+
BasicSocket,
|
|
36
|
+
IPSocket,
|
|
37
|
+
TCPSocket,
|
|
38
|
+
UDPSocket,
|
|
39
|
+
UNIXSocket,
|
|
40
|
+
File,
|
|
41
|
+
Dir,
|
|
42
|
+
Signal,
|
|
43
|
+
Process,
|
|
44
|
+
Process::UID,
|
|
45
|
+
Process::GID,
|
|
46
|
+
]
|
|
47
|
+
SPAN_ALLOWLIST = [
|
|
48
|
+
"Kernel#system",
|
|
49
|
+
"Kernel#require",
|
|
50
|
+
"Kernel#require_relative",
|
|
51
|
+
"Kernel#load",
|
|
52
|
+
"Kernel#eval",
|
|
53
|
+
"Object#instance_eval",
|
|
54
|
+
"Object#instance_exec",
|
|
55
|
+
].freeze
|
|
33
56
|
EVENT_SEVERITY_HIGH = %w[
|
|
34
57
|
capable_check bprm_creds setid_change task_kill
|
|
35
58
|
ptrace_check sb_mount kernel_read_file
|
|
@@ -83,49 +106,12 @@ module Vivarium
|
|
|
83
106
|
end
|
|
84
107
|
end
|
|
85
108
|
|
|
86
|
-
Event = Struct.new(:ktime_ns, :pid, :event_name, :payload, keyword_init: true) do
|
|
87
|
-
def empty?
|
|
88
|
-
ktime_ns.to_i.zero? && pid.to_i.zero? && event_name.to_s.empty? && payload.to_s.empty?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def severity
|
|
92
|
-
Vivarium.event_severity(event_name)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def self.from_binary(raw)
|
|
96
|
-
bytes = raw.to_s.b
|
|
97
|
-
bytes = bytes.ljust(EVENT_STRUCT_SIZE, "\x00")
|
|
98
|
-
|
|
99
|
-
ktime_ns = bytes[EVENT_TS_OFFSET, EVENT_TS_SIZE].unpack1("Q<")
|
|
100
|
-
pid = bytes[EVENT_PID_OFFSET, 4].unpack1("L<")
|
|
101
|
-
event_name = c_string(bytes[EVENT_NAME_OFFSET, EVENT_NAME_SIZE])
|
|
102
|
-
raw_payload = bytes[EVENT_PAYLOAD_OFFSET, EVENT_PAYLOAD_SIZE]
|
|
103
|
-
raw_payload_events = %w[
|
|
104
|
-
dns_req sock_connect odd_socket proc_exec
|
|
105
|
-
file_symlink file_hardlink file_rename file_chmod file_getdents
|
|
106
|
-
ptrace_check sb_mount kernel_read_file task_kill
|
|
107
|
-
setid_change capable_check bprm_creds
|
|
108
|
-
]
|
|
109
|
-
payload = if raw_payload_events.include?(event_name)
|
|
110
|
-
raw_payload
|
|
111
|
-
else
|
|
112
|
-
c_string(raw_payload)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
new(ktime_ns: ktime_ns, pid: pid, event_name: event_name, payload: payload)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def self.c_string(bytes)
|
|
119
|
-
str = bytes.to_s.b
|
|
120
|
-
nul = str.index("\x00")
|
|
121
|
-
return str if nul.nil?
|
|
122
|
-
|
|
123
|
-
str[0, nul]
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
109
|
def self.c_string(bytes)
|
|
128
|
-
|
|
110
|
+
str = bytes.to_s.b
|
|
111
|
+
nul = str.index("\x00")
|
|
112
|
+
return str if nul.nil?
|
|
113
|
+
|
|
114
|
+
str[0, nul]
|
|
129
115
|
end
|
|
130
116
|
|
|
131
117
|
def self.event_severity(event_name)
|
|
@@ -330,6 +316,57 @@ module Vivarium
|
|
|
330
316
|
"has_file=#{has_file} file=#{path.inspect}"
|
|
331
317
|
end
|
|
332
318
|
|
|
319
|
+
def self.decode_proc_fork_payload(raw_payload)
|
|
320
|
+
bytes = raw_payload.to_s.b
|
|
321
|
+
return "" if bytes.bytesize < 8
|
|
322
|
+
|
|
323
|
+
child_pid = bytes[0, 4].unpack1("L<")
|
|
324
|
+
child_tid = bytes[4, 4].unpack1("L<")
|
|
325
|
+
"child_pid=#{child_pid} child_tid=#{child_tid}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def self.decode_span_payload(raw_payload)
|
|
329
|
+
bytes = raw_payload.to_s.b
|
|
330
|
+
return "" if bytes.bytesize < 8
|
|
331
|
+
|
|
332
|
+
method_id = bytes[0, 8].unpack1("q<")
|
|
333
|
+
result = format("method_id=0x%016X", method_id & 0xFFFF_FFFF_FFFF_FFFF)
|
|
334
|
+
|
|
335
|
+
if bytes.bytesize >= 24
|
|
336
|
+
file_id = bytes[8, 8].unpack1("q<")
|
|
337
|
+
lineno = bytes[16, 8].unpack1("q<")
|
|
338
|
+
result += format(" file_id=0x%016X", file_id & 0xFFFF_FFFF_FFFF_FFFF) if file_id != -1
|
|
339
|
+
result += " lineno=#{lineno}" if lineno > 0
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
result
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def self.decode_span_raise_payload(raw_payload)
|
|
346
|
+
bytes = raw_payload.to_s.b
|
|
347
|
+
return "" if bytes.bytesize < 8
|
|
348
|
+
|
|
349
|
+
error_id = bytes[0, 8].unpack1("q<")
|
|
350
|
+
result = format("error_id=0x%016X", error_id & 0xFFFF_FFFF_FFFF_FFFF)
|
|
351
|
+
|
|
352
|
+
if bytes.bytesize >= 16
|
|
353
|
+
message_id = bytes[8, 8].unpack1("q<")
|
|
354
|
+
result += format(" message_id=0x%016X", message_id & 0xFFFF_FFFF_FFFF_FFFF)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if bytes.bytesize >= 24
|
|
358
|
+
file_id = bytes[16, 8].unpack1("q<")
|
|
359
|
+
result += format(" file_id=0x%016X", file_id & 0xFFFF_FFFF_FFFF_FFFF) if file_id != -1
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
if bytes.bytesize >= 32
|
|
363
|
+
lineno = bytes[24, 8].unpack1("q<")
|
|
364
|
+
result += " lineno=#{lineno}" if lineno > 0
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
result
|
|
368
|
+
end
|
|
369
|
+
|
|
333
370
|
def self.render_event_payload(event)
|
|
334
371
|
case event.event_name
|
|
335
372
|
when "dns_req"
|
|
@@ -365,6 +402,15 @@ module Vivarium
|
|
|
365
402
|
when "bprm_creds"
|
|
366
403
|
decoded = decode_bprm_creds_payload(event.payload)
|
|
367
404
|
decoded.empty? ? event.payload.inspect : decoded
|
|
405
|
+
when "proc_fork"
|
|
406
|
+
decoded = decode_proc_fork_payload(event.payload)
|
|
407
|
+
decoded.empty? ? event.payload.inspect : decoded
|
|
408
|
+
when "span_start", "span_stop"
|
|
409
|
+
decoded = decode_span_payload(event.payload)
|
|
410
|
+
decoded.empty? ? event.payload.inspect : decoded
|
|
411
|
+
when "span_raise"
|
|
412
|
+
decoded = decode_span_raise_payload(event.payload)
|
|
413
|
+
decoded.empty? ? event.payload.inspect : decoded
|
|
368
414
|
when "file_symlink"
|
|
369
415
|
decoded = decode_file_symlink_payload(event.payload)
|
|
370
416
|
decoded.empty? ? event.payload.inspect : decoded
|
|
@@ -381,10 +427,17 @@ module Vivarium
|
|
|
381
427
|
decoded = decode_file_getdents_payload(event.payload)
|
|
382
428
|
decoded.empty? ? event.payload.inspect : decoded
|
|
383
429
|
else
|
|
384
|
-
event.payload.inspect
|
|
430
|
+
strip_to_first_null(event.payload).inspect
|
|
385
431
|
end
|
|
386
432
|
end
|
|
387
433
|
|
|
434
|
+
def self.strip_to_first_null(bytes)
|
|
435
|
+
nul = bytes.index("\x00")
|
|
436
|
+
return bytes if nul.nil?
|
|
437
|
+
|
|
438
|
+
bytes[0, nul]
|
|
439
|
+
end
|
|
440
|
+
|
|
388
441
|
class MapStore
|
|
389
442
|
def initialize(pin_dir: Vivarium.bpf_pin_dir)
|
|
390
443
|
@pin_dir = pin_dir
|
|
@@ -402,20 +455,6 @@ module Vivarium
|
|
|
402
455
|
keysize: 4,
|
|
403
456
|
leafsize: 1
|
|
404
457
|
)
|
|
405
|
-
@event_invoked = RbBCC::ArrayTable.from_pin(
|
|
406
|
-
File.join(@pin_dir, "event_invoked"),
|
|
407
|
-
"unsigned int",
|
|
408
|
-
"char[#{EVENT_STRUCT_SIZE}]",
|
|
409
|
-
keysize: 4,
|
|
410
|
-
leafsize: EVENT_STRUCT_SIZE
|
|
411
|
-
)
|
|
412
|
-
@event_write_pos = RbBCC::ArrayTable.from_pin(
|
|
413
|
-
File.join(@pin_dir, "event_write_pos"),
|
|
414
|
-
"unsigned int",
|
|
415
|
-
"unsigned int",
|
|
416
|
-
keysize: 4,
|
|
417
|
-
leafsize: 4
|
|
418
|
-
)
|
|
419
458
|
rescue StandardError => e
|
|
420
459
|
raise Error, "failed to open pinned maps under #{@pin_dir}: #{e.class}: #{e.message}"
|
|
421
460
|
end
|
|
@@ -430,31 +469,6 @@ module Vivarium
|
|
|
430
469
|
rescue KeyError
|
|
431
470
|
nil
|
|
432
471
|
end
|
|
433
|
-
|
|
434
|
-
def drain_events
|
|
435
|
-
events = []
|
|
436
|
-
EVENT_CAPACITY.times do |idx|
|
|
437
|
-
ptr = @event_invoked[idx]
|
|
438
|
-
next unless ptr
|
|
439
|
-
|
|
440
|
-
event = Event.from_binary(ptr[0, EVENT_STRUCT_SIZE])
|
|
441
|
-
next if event.empty?
|
|
442
|
-
|
|
443
|
-
events << event
|
|
444
|
-
@event_invoked[idx] = zeroed_event_ptr
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
@event_write_pos[0] = 0
|
|
448
|
-
events
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
private
|
|
452
|
-
|
|
453
|
-
def zeroed_event_ptr
|
|
454
|
-
ptr = Fiddle::Pointer.malloc(EVENT_STRUCT_SIZE)
|
|
455
|
-
ptr[0, EVENT_STRUCT_SIZE] = "\x00" * EVENT_STRUCT_SIZE
|
|
456
|
-
ptr
|
|
457
|
-
end
|
|
458
472
|
end
|
|
459
473
|
|
|
460
474
|
class Daemon
|
|
@@ -565,6 +579,7 @@ module Vivarium
|
|
|
565
579
|
struct event_t {
|
|
566
580
|
u64 ktime_ns;
|
|
567
581
|
u32 pid;
|
|
582
|
+
u32 tid;
|
|
568
583
|
char event_name[16];
|
|
569
584
|
char payload[#{EVENT_PAYLOAD_SIZE}];
|
|
570
585
|
};
|
|
@@ -572,8 +587,7 @@ module Vivarium
|
|
|
572
587
|
BPF_HASH(config_root_targets, u32, u8, 1024);
|
|
573
588
|
BPF_HASH(config_spawned_targets, u32, u8, 8192);
|
|
574
589
|
BPF_HASH(dns_connected_tids, u32, u8, 8192);
|
|
575
|
-
|
|
576
|
-
BPF_ARRAY(event_write_pos, u32, 1);
|
|
590
|
+
BPF_RINGBUF_OUTPUT(events, #{EVENTS_RINGBUF_PAGES});
|
|
577
591
|
|
|
578
592
|
static __always_inline int target_enabled(u32 pid, u32 tid)
|
|
579
593
|
{
|
|
@@ -613,19 +627,18 @@ module Vivarium
|
|
|
613
627
|
}
|
|
614
628
|
}
|
|
615
629
|
|
|
616
|
-
static __always_inline void submit_event(struct event_t *
|
|
630
|
+
static __always_inline void submit_event(struct event_t *src)
|
|
617
631
|
{
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (!write_pos) {
|
|
632
|
+
struct event_t *ev = events.ringbuf_reserve(sizeof(struct event_t));
|
|
633
|
+
if (!ev) {
|
|
621
634
|
return;
|
|
622
635
|
}
|
|
623
636
|
|
|
637
|
+
__builtin_memcpy(ev, src, sizeof(*ev));
|
|
624
638
|
ev->ktime_ns = bpf_ktime_get_ns();
|
|
639
|
+
ev->tid = (u32)bpf_get_current_pid_tgid();
|
|
625
640
|
|
|
626
|
-
|
|
627
|
-
__sync_fetch_and_add(write_pos, 1);
|
|
628
|
-
event_invoked.update(&idx, ev);
|
|
641
|
+
events.ringbuf_submit(ev, 0);
|
|
629
642
|
}
|
|
630
643
|
|
|
631
644
|
static __always_inline int is_dns_destination(void *addr)
|
|
@@ -696,16 +709,28 @@ module Vivarium
|
|
|
696
709
|
u32 parent = args->parent_pid;
|
|
697
710
|
u32 child = args->child_pid;
|
|
698
711
|
u8 one = 1;
|
|
712
|
+
int is_target = 0;
|
|
699
713
|
|
|
700
714
|
u8 *enabled_root = config_root_targets.lookup(&parent);
|
|
701
715
|
if (enabled_root && *enabled_root == 1) {
|
|
716
|
+
is_target = 1;
|
|
702
717
|
config_spawned_targets.update(&child, &one);
|
|
703
|
-
|
|
718
|
+
} else {
|
|
719
|
+
u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
|
|
720
|
+
if (enabled_spawned && *enabled_spawned == 1) {
|
|
721
|
+
is_target = 1;
|
|
722
|
+
config_spawned_targets.update(&child, &one);
|
|
723
|
+
}
|
|
704
724
|
}
|
|
705
725
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
726
|
+
if (is_target) {
|
|
727
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
728
|
+
struct event_t ev = {};
|
|
729
|
+
ev.pid = pid_tgid >> 32;
|
|
730
|
+
__builtin_memcpy(ev.event_name, "proc_fork", 10);
|
|
731
|
+
__builtin_memcpy(&ev.payload[0], &child, sizeof(child));
|
|
732
|
+
__builtin_memcpy(&ev.payload[4], &child, sizeof(child));
|
|
733
|
+
submit_event(&ev);
|
|
709
734
|
}
|
|
710
735
|
|
|
711
736
|
return 0;
|
|
@@ -724,7 +749,6 @@ module Vivarium
|
|
|
724
749
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
725
750
|
u32 pid = pid_tgid >> 32;
|
|
726
751
|
u32 tid = (u32)pid_tgid;
|
|
727
|
-
bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
|
|
728
752
|
if (!target_enabled(pid, tid)) {
|
|
729
753
|
return 0;
|
|
730
754
|
}
|
|
@@ -738,11 +762,9 @@ module Vivarium
|
|
|
738
762
|
if (path_ret < 0) {
|
|
739
763
|
if (ev.payload[0] == 0) {
|
|
740
764
|
__builtin_memcpy(ev.payload, "<path_error>", 13);
|
|
741
|
-
bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
|
|
742
765
|
}
|
|
743
766
|
}
|
|
744
767
|
|
|
745
|
-
bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
|
|
746
768
|
submit_event(&ev);
|
|
747
769
|
|
|
748
770
|
return 0;
|
|
@@ -1262,6 +1284,90 @@ module Vivarium
|
|
|
1262
1284
|
submit_event(&ev);
|
|
1263
1285
|
return 0;
|
|
1264
1286
|
}
|
|
1287
|
+
|
|
1288
|
+
int on_span_start(struct pt_regs *ctx)
|
|
1289
|
+
{
|
|
1290
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
1291
|
+
u32 pid = pid_tgid >> 32;
|
|
1292
|
+
u32 tid = (u32)pid_tgid;
|
|
1293
|
+
|
|
1294
|
+
if (!target_enabled(pid, tid)) {
|
|
1295
|
+
return 0;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
u64 method_id = 0;
|
|
1299
|
+
u64 file_id = 0;
|
|
1300
|
+
u64 lineno = 0;
|
|
1301
|
+
bpf_usdt_readarg(1, ctx, &method_id);
|
|
1302
|
+
bpf_usdt_readarg(2, ctx, &file_id);
|
|
1303
|
+
bpf_usdt_readarg(3, ctx, &lineno);
|
|
1304
|
+
|
|
1305
|
+
struct event_t ev = {};
|
|
1306
|
+
ev.pid = pid;
|
|
1307
|
+
__builtin_memcpy(ev.event_name, "span_start", 11);
|
|
1308
|
+
__builtin_memcpy(&ev.payload[0], &method_id, sizeof(method_id));
|
|
1309
|
+
__builtin_memcpy(&ev.payload[8], &file_id, sizeof(file_id));
|
|
1310
|
+
__builtin_memcpy(&ev.payload[16], &lineno, sizeof(lineno));
|
|
1311
|
+
submit_event(&ev);
|
|
1312
|
+
return 0;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
int on_span_stop(struct pt_regs *ctx)
|
|
1316
|
+
{
|
|
1317
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
1318
|
+
u32 pid = pid_tgid >> 32;
|
|
1319
|
+
u32 tid = (u32)pid_tgid;
|
|
1320
|
+
|
|
1321
|
+
if (!target_enabled(pid, tid)) {
|
|
1322
|
+
return 0;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
u64 method_id = 0;
|
|
1326
|
+
u64 file_id = 0;
|
|
1327
|
+
u64 lineno = 0;
|
|
1328
|
+
bpf_usdt_readarg(1, ctx, &method_id);
|
|
1329
|
+
bpf_usdt_readarg(2, ctx, &file_id);
|
|
1330
|
+
bpf_usdt_readarg(3, ctx, &lineno);
|
|
1331
|
+
|
|
1332
|
+
struct event_t ev = {};
|
|
1333
|
+
ev.pid = pid;
|
|
1334
|
+
__builtin_memcpy(ev.event_name, "span_stop", 10);
|
|
1335
|
+
__builtin_memcpy(&ev.payload[0], &method_id, sizeof(method_id));
|
|
1336
|
+
__builtin_memcpy(&ev.payload[8], &file_id, sizeof(file_id));
|
|
1337
|
+
__builtin_memcpy(&ev.payload[16], &lineno, sizeof(lineno));
|
|
1338
|
+
submit_event(&ev);
|
|
1339
|
+
return 0;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
int on_span_raise(struct pt_regs *ctx)
|
|
1343
|
+
{
|
|
1344
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
1345
|
+
u32 pid = pid_tgid >> 32;
|
|
1346
|
+
u32 tid = (u32)pid_tgid;
|
|
1347
|
+
|
|
1348
|
+
if (!target_enabled(pid, tid)) {
|
|
1349
|
+
return 0;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
u64 error_id = 0;
|
|
1353
|
+
u64 message_id = 0;
|
|
1354
|
+
u64 file_id = 0;
|
|
1355
|
+
u64 lineno = 0;
|
|
1356
|
+
bpf_usdt_readarg(1, ctx, &error_id);
|
|
1357
|
+
bpf_usdt_readarg(2, ctx, &message_id);
|
|
1358
|
+
bpf_usdt_readarg(3, ctx, &file_id);
|
|
1359
|
+
bpf_usdt_readarg(4, ctx, &lineno);
|
|
1360
|
+
|
|
1361
|
+
struct event_t ev = {};
|
|
1362
|
+
ev.pid = pid;
|
|
1363
|
+
__builtin_memcpy(ev.event_name, "span_raise", 11);
|
|
1364
|
+
__builtin_memcpy(&ev.payload[0], &error_id, sizeof(error_id));
|
|
1365
|
+
__builtin_memcpy(&ev.payload[8], &message_id, sizeof(message_id));
|
|
1366
|
+
__builtin_memcpy(&ev.payload[16], &file_id, sizeof(file_id));
|
|
1367
|
+
__builtin_memcpy(&ev.payload[24], &lineno, sizeof(lineno));
|
|
1368
|
+
submit_event(&ev);
|
|
1369
|
+
return 0;
|
|
1370
|
+
}
|
|
1265
1371
|
CLANG
|
|
1266
1372
|
|
|
1267
1373
|
def initialize(pin_dir: Vivarium.bpf_pin_dir)
|
|
@@ -1278,38 +1384,34 @@ module Vivarium
|
|
|
1278
1384
|
.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
|
|
1279
1385
|
.gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
|
|
1280
1386
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1387
|
+
usdt_so_path = ENV.fetch("VIVARIUM_USDT_SO_PATH") { Vivarium.locate_vivarium_usdt_so }
|
|
1388
|
+
usdt = RbBCC::USDT.new(path: usdt_so_path)
|
|
1389
|
+
usdt.enable_probe(probe: "start_probe", fn_name: "on_span_start")
|
|
1390
|
+
usdt.enable_probe(probe: "stop_probe", fn_name: "on_span_stop")
|
|
1391
|
+
usdt.enable_probe(probe: "raise_probe", fn_name: "on_span_raise")
|
|
1392
|
+
|
|
1393
|
+
bpf = RbBCC::BCC.new(text: program, usdt_contexts: [usdt])
|
|
1283
1394
|
|
|
1284
1395
|
config_root_targets = bpf["config_root_targets"]
|
|
1285
1396
|
config_spawned_targets = bpf["config_spawned_targets"]
|
|
1286
|
-
|
|
1287
|
-
event_write_pos = bpf["event_write_pos"]
|
|
1397
|
+
events_ringbuf = bpf["events"]
|
|
1288
1398
|
|
|
1289
|
-
clear_event_slots(event_invoked)
|
|
1290
|
-
event_write_pos[0] = 0
|
|
1291
1399
|
config_spawned_targets.clear
|
|
1292
1400
|
|
|
1293
1401
|
pin_map(config_root_targets, File.join(@pin_dir, "config_root_targets"))
|
|
1294
1402
|
pin_map(config_spawned_targets, File.join(@pin_dir, "config_spawned_targets"))
|
|
1295
|
-
pin_map(
|
|
1296
|
-
pin_map(event_write_pos, File.join(@pin_dir, "event_write_pos"))
|
|
1403
|
+
pin_map(events_ringbuf, File.join(@pin_dir, "events"))
|
|
1297
1404
|
|
|
1298
1405
|
puts "[vivariumd] started"
|
|
1299
1406
|
puts "[vivariumd] pinned maps in #{@pin_dir}"
|
|
1300
1407
|
puts "[vivariumd] watching LSM file_open (f_path offset=#{f_path_offset})"
|
|
1301
|
-
puts "[vivariumd]
|
|
1408
|
+
puts "[vivariumd] USDT attached via #{usdt_so_path}"
|
|
1302
1409
|
|
|
1303
1410
|
loop do
|
|
1304
1411
|
sleep 1
|
|
1305
1412
|
end
|
|
1306
1413
|
rescue Interrupt
|
|
1307
1414
|
puts "\n[vivariumd] stopping"
|
|
1308
|
-
ensure
|
|
1309
|
-
if kprint_thread
|
|
1310
|
-
kprint_thread.kill
|
|
1311
|
-
kprint_thread.join(0.2)
|
|
1312
|
-
end
|
|
1313
1415
|
end
|
|
1314
1416
|
|
|
1315
1417
|
private
|
|
@@ -1325,34 +1427,6 @@ module Vivarium
|
|
|
1325
1427
|
RbBCC::BCC.pin!(table.map_fd, path)
|
|
1326
1428
|
end
|
|
1327
1429
|
|
|
1328
|
-
def clear_event_slots(table)
|
|
1329
|
-
ptr = Fiddle::Pointer.malloc(EVENT_STRUCT_SIZE)
|
|
1330
|
-
ptr[0, EVENT_STRUCT_SIZE] = "\x00" * EVENT_STRUCT_SIZE
|
|
1331
|
-
EVENT_CAPACITY.times do |idx|
|
|
1332
|
-
table[idx] = ptr
|
|
1333
|
-
end
|
|
1334
|
-
end
|
|
1335
|
-
|
|
1336
|
-
def start_kprint_logger(bpf)
|
|
1337
|
-
Thread.new do
|
|
1338
|
-
begin
|
|
1339
|
-
bpf.trace_fields do |_task, pid, _cpu, _flags, ts, msg|
|
|
1340
|
-
line = msg.to_s.strip
|
|
1341
|
-
next unless line.start_with?("vivarium:")
|
|
1342
|
-
|
|
1343
|
-
puts "[vivariumd:kprint #{ts} pid=#{pid}] #{line}"
|
|
1344
|
-
end
|
|
1345
|
-
rescue IOError, Errno::EINTR
|
|
1346
|
-
nil
|
|
1347
|
-
rescue StandardError => e
|
|
1348
|
-
warn "[vivariumd] kprint stream stopped: #{e.class}: #{e.message}"
|
|
1349
|
-
end
|
|
1350
|
-
end
|
|
1351
|
-
rescue StandardError => e
|
|
1352
|
-
warn "[vivariumd] failed to start kprint logger: #{e.class}: #{e.message}"
|
|
1353
|
-
nil
|
|
1354
|
-
end
|
|
1355
|
-
|
|
1356
1430
|
def detect_f_path_offset
|
|
1357
1431
|
env_offset = ENV["VIVARIUM_FILE_F_PATH_OFFSET"]
|
|
1358
1432
|
return Integer(env_offset, 10) if env_offset
|
|
@@ -1465,75 +1539,141 @@ module Vivarium
|
|
|
1465
1539
|
end
|
|
1466
1540
|
|
|
1467
1541
|
class ObservationSession
|
|
1468
|
-
def initialize(store:, pid:, tracer:)
|
|
1542
|
+
def initialize(store:, pid:, tracer:, correlator:)
|
|
1469
1543
|
@store = store
|
|
1470
1544
|
@pid = pid
|
|
1471
1545
|
@tracer = tracer
|
|
1546
|
+
@correlator = correlator
|
|
1472
1547
|
@stopped = false
|
|
1473
1548
|
end
|
|
1474
1549
|
|
|
1475
1550
|
def stop
|
|
1476
1551
|
return if @stopped
|
|
1477
1552
|
|
|
1553
|
+
@stopped = true
|
|
1478
1554
|
@tracer.disable
|
|
1479
1555
|
@store.unregister_pid(@pid)
|
|
1480
|
-
@
|
|
1556
|
+
@correlator.stop
|
|
1481
1557
|
end
|
|
1482
1558
|
end
|
|
1483
1559
|
|
|
1484
|
-
def self.observe(pin_dir: bpf_pin_dir,
|
|
1485
|
-
return scoped_observe(pin_dir: pin_dir,
|
|
1560
|
+
def self.observe(pin_dir: bpf_pin_dir, dest: $stdout, &block)
|
|
1561
|
+
return scoped_observe(pin_dir: pin_dir, dest: dest, &block) if block_given?
|
|
1486
1562
|
|
|
1487
|
-
top_observe(pin_dir: pin_dir,
|
|
1563
|
+
top_observe(pin_dir: pin_dir, dest: dest)
|
|
1488
1564
|
end
|
|
1489
1565
|
|
|
1490
|
-
def self.top_observe(pin_dir: bpf_pin_dir,
|
|
1491
|
-
|
|
1566
|
+
def self.top_observe(pin_dir: bpf_pin_dir, dest: $stdout)
|
|
1567
|
+
require "vivarium_usdt"
|
|
1568
|
+
|
|
1492
1569
|
store = MapStore.new(pin_dir: pin_dir)
|
|
1493
1570
|
pid = Process.pid
|
|
1494
1571
|
store.register_pid(pid)
|
|
1495
|
-
logger.info("top-level observing with pid=#{pid}")
|
|
1496
1572
|
|
|
1497
|
-
|
|
1573
|
+
method_id_queue = Thread::Queue.new
|
|
1574
|
+
main_tid = gettid
|
|
1575
|
+
|
|
1576
|
+
correlator = Correlator.new(
|
|
1577
|
+
pin_dir: pin_dir,
|
|
1578
|
+
observer_pid: pid,
|
|
1579
|
+
main_tid: main_tid,
|
|
1580
|
+
method_id_queue: method_id_queue,
|
|
1581
|
+
dest: dest
|
|
1582
|
+
)
|
|
1583
|
+
correlator.start
|
|
1584
|
+
|
|
1585
|
+
tracer = build_observe_tracepoint(method_id_queue)
|
|
1498
1586
|
tracer.enable
|
|
1499
1587
|
|
|
1500
|
-
session = ObservationSession.new(
|
|
1588
|
+
session = ObservationSession.new(
|
|
1589
|
+
store: store, pid: pid, tracer: tracer, correlator: correlator
|
|
1590
|
+
)
|
|
1501
1591
|
at_exit { session.stop }
|
|
1502
1592
|
session
|
|
1503
1593
|
end
|
|
1504
1594
|
|
|
1505
|
-
def self.scoped_observe(pin_dir:,
|
|
1506
|
-
|
|
1595
|
+
def self.scoped_observe(pin_dir:, dest:)
|
|
1596
|
+
require "vivarium_usdt"
|
|
1597
|
+
|
|
1507
1598
|
store = MapStore.new(pin_dir: pin_dir)
|
|
1508
1599
|
pid = Process.pid
|
|
1509
1600
|
store.register_pid(pid)
|
|
1510
|
-
logger.info("scoped observing with pid=#{pid}")
|
|
1511
1601
|
|
|
1512
|
-
|
|
1602
|
+
method_id_queue = Thread::Queue.new
|
|
1603
|
+
main_tid = gettid
|
|
1604
|
+
|
|
1605
|
+
correlator = Correlator.new(
|
|
1606
|
+
pin_dir: pin_dir,
|
|
1607
|
+
observer_pid: pid,
|
|
1608
|
+
main_tid: main_tid,
|
|
1609
|
+
method_id_queue: method_id_queue,
|
|
1610
|
+
dest: dest
|
|
1611
|
+
)
|
|
1612
|
+
correlator.start
|
|
1613
|
+
|
|
1614
|
+
tracer = build_observe_tracepoint(method_id_queue)
|
|
1513
1615
|
tracer.enable
|
|
1514
1616
|
|
|
1515
1617
|
yield
|
|
1516
1618
|
ensure
|
|
1517
1619
|
tracer&.disable
|
|
1518
1620
|
store&.unregister_pid(pid)
|
|
1621
|
+
correlator&.stop
|
|
1519
1622
|
end
|
|
1520
1623
|
|
|
1521
|
-
def self.build_observe_tracepoint(
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1624
|
+
def self.build_observe_tracepoint(method_id_queue)
|
|
1625
|
+
allow_classes = SPAN_ALLOWCLASSES
|
|
1626
|
+
allowlist = SPAN_ALLOWLIST
|
|
1627
|
+
TracePoint.new(:call, :c_call, :return, :c_return, :raise) do |tp|
|
|
1628
|
+
if tp.event == :raise
|
|
1629
|
+
Vivarium::Usdt.raise(
|
|
1630
|
+
tp.raised_exception.class.to_s,
|
|
1631
|
+
tp.raised_exception.message.to_s,
|
|
1632
|
+
file: tp.path,
|
|
1633
|
+
lineno: tp.lineno
|
|
1634
|
+
)
|
|
1635
|
+
next
|
|
1636
|
+
end
|
|
1637
|
+
|
|
1638
|
+
signature = "#{tp.defined_class}##{tp.method_id}"
|
|
1639
|
+
is_target = allowlist.include?(signature) || \
|
|
1640
|
+
allow_classes.any? { |klass| tp.defined_class == klass } || \
|
|
1641
|
+
allow_classes.any? { |klass| tp.defined_class == klass.singleton_class }
|
|
1642
|
+
next unless is_target
|
|
1643
|
+
|
|
1644
|
+
case tp.event
|
|
1645
|
+
when :call, :c_call
|
|
1646
|
+
method_id = Vivarium::Usdt.start(tp.defined_class.to_s, tp.method_id.to_s, file: tp.path, lineno: tp.lineno)
|
|
1647
|
+
method_id_queue << [method_id, signature]
|
|
1648
|
+
when :return, :c_return
|
|
1649
|
+
Vivarium::Usdt.stop(tp.defined_class.to_s, tp.method_id.to_s, file: tp.path, lineno: tp.lineno)
|
|
1650
|
+
end
|
|
1651
|
+
end
|
|
1652
|
+
end
|
|
1525
1653
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1654
|
+
def self.gettid
|
|
1655
|
+
@gettid_func ||= begin
|
|
1656
|
+
libc = Fiddle.dlopen("libc.so.6")
|
|
1657
|
+
Fiddle::Function.new(libc["gettid"], [], Fiddle::TYPE_INT)
|
|
1658
|
+
rescue Fiddle::DLError
|
|
1659
|
+
libc = Fiddle.dlopen(nil)
|
|
1660
|
+
Fiddle::Function.new(libc["gettid"], [], Fiddle::TYPE_INT)
|
|
1529
1661
|
end
|
|
1662
|
+
@gettid_func.call
|
|
1663
|
+
end
|
|
1664
|
+
|
|
1665
|
+
def self.monotonic_ktime_ns
|
|
1666
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
1530
1667
|
end
|
|
1531
1668
|
|
|
1532
|
-
def self.
|
|
1533
|
-
|
|
1534
|
-
|
|
1669
|
+
def self.locate_vivarium_usdt_so
|
|
1670
|
+
require "vivarium_usdt/vivarium_usdt"
|
|
1671
|
+
so = $LOADED_FEATURES.find { |p| p =~ %r{vivarium_usdt/vivarium_usdt\.(so|bundle|dylib)\z} }
|
|
1672
|
+
raise Error, "vivarium_usdt native extension not found in $LOADED_FEATURES" unless so
|
|
1535
1673
|
|
|
1536
|
-
|
|
1674
|
+
File.realpath(so)
|
|
1675
|
+
rescue LoadError => e
|
|
1676
|
+
raise Error, "failed to load vivarium_usdt: #{e.message}"
|
|
1537
1677
|
end
|
|
1538
1678
|
|
|
1539
1679
|
def self.run_daemon!(argv = ARGV)
|
|
@@ -1546,3 +1686,6 @@ module Vivarium
|
|
|
1546
1686
|
Daemon.new(pin_dir: options[:pin_dir]).run
|
|
1547
1687
|
end
|
|
1548
1688
|
end
|
|
1689
|
+
|
|
1690
|
+
require_relative "vivarium/correlator"
|
|
1691
|
+
require_relative "vivarium/tree_renderer"
|
data/logo-simple.png
ADDED
|
Binary file
|