vivarium 0.3.1 → 0.4.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/examples/dlopen_demo.rb +50 -0
- data/examples/drop_demo.rb +76 -0
- data/lib/vivarium/correlator.rb +11 -26
- data/lib/vivarium/tree_renderer.rb +58 -54
- data/lib/vivarium/version.rb +1 -1
- data/lib/vivarium.rb +199 -49
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6549be32807adc904ffe18fbefc055600cf14c85c68b106a3abfd7dce354e8b1
|
|
4
|
+
data.tar.gz: 1f9c961424d4712b2c43ad3df8d81ee679ea2190acaa8ea24e78a4f88b92f452
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebe88587a6328a37703899da2f0c8275f9321d7ea34e1af9806dccfaf929a6ee45bce68378356aeb2171708936b74ebb33545ada8a7eace2774aa60783b31b24
|
|
7
|
+
data.tar.gz: f707b190642345628ddf613038942a87cd95585877b45a264e13ffb1c243da0b7576712d6b5e5eb65e336dab06ff95a01f329b383bdb0007aec879629c30d2eb
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "fiddle"
|
|
5
|
+
require "vivarium"
|
|
6
|
+
|
|
7
|
+
FILTER = {
|
|
8
|
+
include_events: %w[dlopen mmap_exec]
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
# Usage:
|
|
12
|
+
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
13
|
+
# (dlopen uprobe is attached automatically when libc is found)
|
|
14
|
+
# 2) Run this script: bundle exec ruby examples/dlopen_demo.rb
|
|
15
|
+
#
|
|
16
|
+
# Expected output: "DL dlopen" and "DL mmap_exec" events for each
|
|
17
|
+
# library loaded via Fiddle.dlopen.
|
|
18
|
+
#
|
|
19
|
+
# You can disable the dlopen uprobe with `sudo vivariumd --no-dlopen-trace`
|
|
20
|
+
# or point at a specific libc with `sudo vivariumd --libc /lib/x86_64-linux-gnu/libc.so.6`.
|
|
21
|
+
|
|
22
|
+
Vivarium.observe(filter: FILTER) do
|
|
23
|
+
# libm: math functions — almost universally available
|
|
24
|
+
begin
|
|
25
|
+
libm = Fiddle.dlopen("libm.so.6")
|
|
26
|
+
sin_fn = Fiddle::Function.new(libm["sin"], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE)
|
|
27
|
+
puts "[dlopen_demo] sin(PI/4) = #{sin_fn.call(Math::PI / 4).round(6)}"
|
|
28
|
+
libm.close
|
|
29
|
+
rescue Fiddle::DLError => e
|
|
30
|
+
warn "[dlopen_demo] libm: #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# libsqlite3: a common library that may not be loaded at startup
|
|
34
|
+
begin
|
|
35
|
+
libsqlite3 = Fiddle.dlopen("libsqlite3.so.0")
|
|
36
|
+
puts "[dlopen_demo] libsqlite3 loaded: version = #{Fiddle::Function.new(libsqlite3["sqlite3_libversion"], [], Fiddle::TYPE_VOIDP).call}"
|
|
37
|
+
libsqlite3.close
|
|
38
|
+
rescue Fiddle::DLError => e
|
|
39
|
+
warn "[dlopen_demo] libsqlite3: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Spawn a child process that also calls dlopen — its events should
|
|
43
|
+
# appear under a PROC node in the tree (descendant PID tracking).
|
|
44
|
+
# Unbundle so Bundler does not spawn anything.
|
|
45
|
+
Bundler.with_unbundled_env do
|
|
46
|
+
system("ruby -e 'require \"fiddle\"; Fiddle.dlopen(\"libm.so.6\").close'")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts "[dlopen_demo] done"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Standalone demo: shows what DROP warning nodes look like in the TreeRenderer
|
|
5
|
+
# output. Does NOT require BPF or vivariumd — constructs RawEvent objects
|
|
6
|
+
# directly and feeds them to TreeRenderer.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ruby examples/drop_demo.rb
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.join(__dir__, "..", "lib")
|
|
12
|
+
|
|
13
|
+
require "vivarium"
|
|
14
|
+
require "vivarium/correlator"
|
|
15
|
+
require "vivarium/tree_renderer"
|
|
16
|
+
require "vivarium/display_filter"
|
|
17
|
+
|
|
18
|
+
t0 = 1_000_000_000 # base ktime_ns
|
|
19
|
+
pid = Process.pid
|
|
20
|
+
tid = Process.pid
|
|
21
|
+
|
|
22
|
+
# span_start payload: method_name (128B) + file_name (120B) + lineno (8B)
|
|
23
|
+
span_start_payload = "MyClass#my_method".ljust(Vivarium::SPAN_METHOD_SIZE, "\x00") +
|
|
24
|
+
"drop_demo.rb".ljust(Vivarium::SPAN_FILE_SIZE, "\x00") +
|
|
25
|
+
[10].pack("q<")
|
|
26
|
+
|
|
27
|
+
events = [
|
|
28
|
+
Vivarium::Correlator::RawEvent.new(
|
|
29
|
+
ktime_ns: t0,
|
|
30
|
+
pid: pid, tid: tid,
|
|
31
|
+
event_name: "span_start",
|
|
32
|
+
payload: span_start_payload,
|
|
33
|
+
dropped_since_last: 0
|
|
34
|
+
),
|
|
35
|
+
# This event carries drop info: 5 events were lost before it arrived
|
|
36
|
+
Vivarium::Correlator::RawEvent.new(
|
|
37
|
+
ktime_ns: t0 + 10_000_000,
|
|
38
|
+
pid: pid, tid: tid,
|
|
39
|
+
event_name: "path_open",
|
|
40
|
+
payload: "/etc/passwd\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
41
|
+
dropped_since_last: 5
|
|
42
|
+
),
|
|
43
|
+
Vivarium::Correlator::RawEvent.new(
|
|
44
|
+
ktime_ns: t0 + 20_000_000,
|
|
45
|
+
pid: pid, tid: tid,
|
|
46
|
+
event_name: "dns_req",
|
|
47
|
+
payload: "\x06google\x03com\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
48
|
+
dropped_since_last: 0
|
|
49
|
+
),
|
|
50
|
+
# Another burst: 12 events dropped just before this sock_connect
|
|
51
|
+
Vivarium::Correlator::RawEvent.new(
|
|
52
|
+
ktime_ns: t0 + 25_000_000,
|
|
53
|
+
pid: pid, tid: tid,
|
|
54
|
+
event_name: "sock_connect",
|
|
55
|
+
payload: [2, 443, 0x7f000001, 0].pack("S<nNN").ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
56
|
+
dropped_since_last: 12
|
|
57
|
+
),
|
|
58
|
+
Vivarium::Correlator::RawEvent.new(
|
|
59
|
+
ktime_ns: t0 + 30_000_000,
|
|
60
|
+
pid: pid, tid: tid,
|
|
61
|
+
event_name: "span_stop",
|
|
62
|
+
payload: "\x00" * Vivarium::EVENT_PAYLOAD_SIZE,
|
|
63
|
+
dropped_since_last: 0
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
Vivarium::TreeRenderer.new(
|
|
68
|
+
events: events,
|
|
69
|
+
observer_pid: pid,
|
|
70
|
+
main_tid: tid,
|
|
71
|
+
session_start_iso: "2026-06-02T00:00:00.000Z",
|
|
72
|
+
session_start_ktime: t0,
|
|
73
|
+
session_stop_iso: "2026-06-02T00:00:00.030Z",
|
|
74
|
+
session_stop_ktime: t0 + 30_000_000,
|
|
75
|
+
dest: $stdout
|
|
76
|
+
).render
|
data/lib/vivarium/correlator.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "time"
|
|
|
6
6
|
module Vivarium
|
|
7
7
|
class Correlator
|
|
8
8
|
RawEvent = Struct.new(
|
|
9
|
-
:ktime_ns, :pid, :tid, :event_name, :payload,
|
|
9
|
+
:ktime_ns, :pid, :tid, :event_name, :payload, :dropped_since_last,
|
|
10
10
|
keyword_init: true
|
|
11
11
|
)
|
|
12
12
|
|
|
@@ -17,22 +17,21 @@ module Vivarium
|
|
|
17
17
|
u32 tid;
|
|
18
18
|
char event_name[16];
|
|
19
19
|
char payload[256];
|
|
20
|
+
u64 dropped_since_last;
|
|
20
21
|
};
|
|
21
22
|
C
|
|
22
23
|
|
|
23
24
|
POLL_TIMEOUT_MS = 200
|
|
24
25
|
|
|
25
|
-
def initialize(pin_dir:, observer_pid:, main_tid:,
|
|
26
|
+
def initialize(pin_dir:, observer_pid:, main_tid:, filter: nil, dest: $stdout)
|
|
26
27
|
@pin_dir = pin_dir
|
|
27
28
|
@observer_pid = observer_pid
|
|
28
29
|
@main_tid = main_tid
|
|
29
|
-
@method_id_queue = method_id_queue
|
|
30
30
|
@filter = filter
|
|
31
31
|
@dest = dest
|
|
32
32
|
|
|
33
33
|
@events = []
|
|
34
34
|
@events_mutex = Mutex.new
|
|
35
|
-
@method_table = {}
|
|
36
35
|
@stop_flag = false
|
|
37
36
|
@started = false
|
|
38
37
|
|
|
@@ -65,15 +64,12 @@ module Vivarium
|
|
|
65
64
|
@session_stop_ktime = Vivarium.monotonic_ktime_ns
|
|
66
65
|
|
|
67
66
|
3.times { safe_poll(50) }
|
|
68
|
-
drain_method_id_queue
|
|
69
67
|
|
|
70
68
|
events_snapshot = @events_mutex.synchronize { @events.dup }
|
|
71
|
-
method_table_snapshot = @method_table.dup
|
|
72
69
|
@stopped = true
|
|
73
70
|
|
|
74
71
|
TreeRenderer.new(
|
|
75
72
|
events: events_snapshot,
|
|
76
|
-
method_table: method_table_snapshot,
|
|
77
73
|
observer_pid: @observer_pid,
|
|
78
74
|
main_tid: @main_tid,
|
|
79
75
|
session_start_iso: @session_start_iso,
|
|
@@ -90,7 +86,6 @@ module Vivarium
|
|
|
90
86
|
def run
|
|
91
87
|
until @stop_flag
|
|
92
88
|
safe_poll(POLL_TIMEOUT_MS)
|
|
93
|
-
drain_method_id_queue
|
|
94
89
|
end
|
|
95
90
|
end
|
|
96
91
|
|
|
@@ -104,11 +99,12 @@ module Vivarium
|
|
|
104
99
|
bytes = data[0, size].to_s.b
|
|
105
100
|
bytes = bytes.ljust(Vivarium::EVENT_STRUCT_SIZE, "\x00") if bytes.bytesize < Vivarium::EVENT_STRUCT_SIZE
|
|
106
101
|
|
|
107
|
-
ktime_ns
|
|
108
|
-
pid
|
|
109
|
-
tid
|
|
110
|
-
event_name
|
|
111
|
-
payload
|
|
102
|
+
ktime_ns = bytes[Vivarium::EVENT_TS_OFFSET, Vivarium::EVENT_TS_SIZE].unpack1("Q<")
|
|
103
|
+
pid = bytes[Vivarium::EVENT_PID_OFFSET, 4].unpack1("L<")
|
|
104
|
+
tid = bytes[Vivarium::EVENT_TID_OFFSET, 4].unpack1("L<")
|
|
105
|
+
event_name = Vivarium.c_string(bytes[Vivarium::EVENT_NAME_OFFSET, Vivarium::EVENT_NAME_SIZE])
|
|
106
|
+
payload = bytes[Vivarium::EVENT_PAYLOAD_OFFSET, Vivarium::EVENT_PAYLOAD_SIZE].to_s.b
|
|
107
|
+
dropped_since_last = bytes[Vivarium::EVENT_DROPPED_OFFSET, 8].unpack1("Q<")
|
|
112
108
|
|
|
113
109
|
@events_mutex.synchronize do
|
|
114
110
|
@events << RawEvent.new(
|
|
@@ -116,24 +112,13 @@ module Vivarium
|
|
|
116
112
|
pid: pid,
|
|
117
113
|
tid: tid,
|
|
118
114
|
event_name: event_name,
|
|
119
|
-
payload: payload
|
|
115
|
+
payload: payload,
|
|
116
|
+
dropped_since_last: dropped_since_last
|
|
120
117
|
)
|
|
121
118
|
end
|
|
122
119
|
rescue StandardError => e
|
|
123
120
|
warn "[vivarium correlator] capture error: #{e.class}: #{e.message}"
|
|
124
121
|
end
|
|
125
122
|
|
|
126
|
-
def drain_method_id_queue
|
|
127
|
-
loop do
|
|
128
|
-
msg = begin
|
|
129
|
-
@method_id_queue.pop(true)
|
|
130
|
-
rescue ThreadError
|
|
131
|
-
return
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
method_id, signature = msg
|
|
135
|
-
@method_table[method_id] = signature
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
123
|
end
|
|
139
124
|
end
|
|
@@ -22,12 +22,13 @@ module Vivarium
|
|
|
22
22
|
].to_set.freeze
|
|
23
23
|
|
|
24
24
|
UPROBE_EVENT_NAMES = %w[ssl_write].to_set.freeze
|
|
25
|
+
DL_EVENT_NAMES = %w[dlopen mmap_exec].to_set.freeze
|
|
25
26
|
|
|
26
27
|
SYNTHETIC_SPAN_NAME = "<no-span>"
|
|
27
28
|
UNRESOLVED_METHOD_PREFIX = "<method_id="
|
|
28
29
|
|
|
29
30
|
Span = Struct.new(
|
|
30
|
-
:tid, :
|
|
31
|
+
:tid, :method_name, :file_name, :lineno, :start_ktime, :stop_ktime, :exit_kind,
|
|
31
32
|
:events, :descendant_pids, :synthetic, :raised,
|
|
32
33
|
keyword_init: true
|
|
33
34
|
) do
|
|
@@ -51,11 +52,10 @@ module Vivarium
|
|
|
51
52
|
EventNode = Struct.new(:kind, :name, :target, :offset_ns, :child_proc, keyword_init: true)
|
|
52
53
|
ProcNode = Struct.new(:pid, :comm, :parent_pid, :children, keyword_init: true)
|
|
53
54
|
|
|
54
|
-
def initialize(events:,
|
|
55
|
+
def initialize(events:, observer_pid:, main_tid:,
|
|
55
56
|
session_start_iso:, session_start_ktime:,
|
|
56
57
|
session_stop_iso:, session_stop_ktime:, filter: nil, dest:)
|
|
57
58
|
@events = events
|
|
58
|
-
@method_table = method_table
|
|
59
59
|
@observer_pid = observer_pid
|
|
60
60
|
@main_tid = main_tid
|
|
61
61
|
@session_start_iso = session_start_iso
|
|
@@ -67,7 +67,6 @@ module Vivarium
|
|
|
67
67
|
|
|
68
68
|
@pid_comm = { observer_pid => "ruby" }
|
|
69
69
|
@pid_parent = {}
|
|
70
|
-
@unresolved_method_ids = []
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
def render
|
|
@@ -100,11 +99,11 @@ module Vivarium
|
|
|
100
99
|
events.each do |ev|
|
|
101
100
|
case ev.event_name
|
|
102
101
|
when "span_start"
|
|
103
|
-
|
|
102
|
+
method_name, file_name, lno = read_span_payload(ev.payload)
|
|
104
103
|
span = Span.new(
|
|
105
104
|
tid: ev.tid,
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
method_name: method_name,
|
|
106
|
+
file_name: file_name,
|
|
108
107
|
lineno: lno,
|
|
109
108
|
start_ktime: ev.ktime_ns,
|
|
110
109
|
stop_ktime: nil,
|
|
@@ -194,8 +193,8 @@ module Vivarium
|
|
|
194
193
|
def synthetic_span(start_ktime, stop_ktime)
|
|
195
194
|
Span.new(
|
|
196
195
|
tid: @main_tid,
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
method_name: nil,
|
|
197
|
+
file_name: nil,
|
|
199
198
|
lineno: nil,
|
|
200
199
|
start_ktime: start_ktime,
|
|
201
200
|
stop_ktime: stop_ktime,
|
|
@@ -261,9 +260,6 @@ module Vivarium
|
|
|
261
260
|
end
|
|
262
261
|
|
|
263
262
|
def print_warnings
|
|
264
|
-
@unresolved_method_ids.uniq.each do |mid|
|
|
265
|
-
@dest.puts format("# warning method_id=0x%016X unresolved at render time", mid & 0xFFFF_FFFF_FFFF_FFFF)
|
|
266
|
-
end
|
|
267
263
|
end
|
|
268
264
|
|
|
269
265
|
def print_observer_proc(spans)
|
|
@@ -300,30 +296,23 @@ module Vivarium
|
|
|
300
296
|
|
|
301
297
|
def span_file_info(span)
|
|
302
298
|
return "" if span.synthetic
|
|
303
|
-
return ""
|
|
304
|
-
|
|
305
|
-
file_name = Vivarium::Usdt.get_file_name(span.file_id)
|
|
306
|
-
return "" unless file_name
|
|
299
|
+
return "" if span.file_name.nil? || span.file_name.empty?
|
|
307
300
|
|
|
308
301
|
lno = span.lineno && span.lineno > 0 ? ":#{span.lineno}" : ""
|
|
309
|
-
" at=#{File.basename(file_name)}#{lno}"
|
|
302
|
+
" at=#{File.basename(span.file_name)}#{lno}"
|
|
310
303
|
end
|
|
311
304
|
|
|
312
305
|
def span_display_name(span)
|
|
313
306
|
return SYNTHETIC_SPAN_NAME if span.synthetic
|
|
314
|
-
return SYNTHETIC_SPAN_NAME if span.
|
|
315
|
-
|
|
316
|
-
name = @method_table[span.method_id]
|
|
317
|
-
name ||= Vivarium::Usdt.get_method_name(span.method_id)
|
|
318
|
-
return name if name
|
|
307
|
+
return SYNTHETIC_SPAN_NAME if span.method_name.nil? || span.method_name.empty?
|
|
319
308
|
|
|
320
|
-
|
|
321
|
-
format("#{UNRESOLVED_METHOD_PREFIX}0x%016X>", span.method_id & 0xFFFF_FFFF_FFFF_FFFF)
|
|
309
|
+
span.method_name
|
|
322
310
|
end
|
|
323
311
|
|
|
324
312
|
def build_span_children(span)
|
|
325
313
|
proc_node_by_pid = {}
|
|
326
314
|
root_children = []
|
|
315
|
+
prev_event_ktime = span.start_ktime
|
|
327
316
|
|
|
328
317
|
span.events.each do |ev|
|
|
329
318
|
target_text = nil
|
|
@@ -353,6 +342,7 @@ module Vivarium
|
|
|
353
342
|
)
|
|
354
343
|
|
|
355
344
|
parent_container = container_for_pid(ev.pid, span, proc_node_by_pid, root_children)
|
|
345
|
+
maybe_inject_drop_node(parent_container, ev, span, prev_event_ktime)
|
|
356
346
|
append_event(parent_container, ev_node)
|
|
357
347
|
else
|
|
358
348
|
ev_node = EventNode.new(
|
|
@@ -363,12 +353,11 @@ module Vivarium
|
|
|
363
353
|
child_proc: nil
|
|
364
354
|
)
|
|
365
355
|
|
|
366
|
-
if ev.pid == @observer_pid && ev.tid == span.tid
|
|
367
|
-
|
|
356
|
+
container = if ev.pid == @observer_pid && ev.tid == span.tid
|
|
357
|
+
root_children
|
|
368
358
|
elsif (node = proc_node_by_pid[ev.pid])
|
|
369
|
-
|
|
359
|
+
node.children
|
|
370
360
|
else
|
|
371
|
-
# event from a descendant pid we haven't materialized — synthesize a stub PROC node
|
|
372
361
|
stub = ProcNode.new(
|
|
373
362
|
pid: ev.pid,
|
|
374
363
|
comm: @pid_comm[ev.pid] || "?",
|
|
@@ -376,14 +365,19 @@ module Vivarium
|
|
|
376
365
|
children: []
|
|
377
366
|
)
|
|
378
367
|
proc_node_by_pid[ev.pid] = stub
|
|
379
|
-
append_event(stub.children, ev_node)
|
|
380
368
|
root_children << stub
|
|
369
|
+
stub.children
|
|
381
370
|
end
|
|
382
371
|
|
|
372
|
+
maybe_inject_drop_node(container, ev, span, prev_event_ktime)
|
|
373
|
+
append_event(container, ev_node)
|
|
374
|
+
|
|
383
375
|
if ev.event_name == EXEC_EVENT_NAME && (node = proc_node_by_pid[ev.pid])
|
|
384
376
|
node.comm = @pid_comm[ev.pid] || node.comm
|
|
385
377
|
end
|
|
386
378
|
end
|
|
379
|
+
|
|
380
|
+
prev_event_ktime = ev.ktime_ns
|
|
387
381
|
end
|
|
388
382
|
|
|
389
383
|
# Interleave child spans by start time among the event/proc nodes
|
|
@@ -429,6 +423,23 @@ module Vivarium
|
|
|
429
423
|
container << ev_node
|
|
430
424
|
end
|
|
431
425
|
|
|
426
|
+
def maybe_inject_drop_node(container, ev, span, prev_event_ktime = nil)
|
|
427
|
+
n = ev.dropped_since_last.to_i
|
|
428
|
+
return if n.zero?
|
|
429
|
+
|
|
430
|
+
# Show the start of the drop window (= time of last good event).
|
|
431
|
+
# The end is implicitly ev.ktime_ns (shown on the following event line).
|
|
432
|
+
drop_start_ns = prev_event_ktime ? (prev_event_ktime - span.start_ktime) : nil
|
|
433
|
+
|
|
434
|
+
container << EventNode.new(
|
|
435
|
+
kind: "DROP",
|
|
436
|
+
name: "dropped_events",
|
|
437
|
+
target: "#{n} event(s) lost (ringbuf overflow)",
|
|
438
|
+
offset_ns: drop_start_ns,
|
|
439
|
+
child_proc: nil
|
|
440
|
+
)
|
|
441
|
+
end
|
|
442
|
+
|
|
432
443
|
def print_nodes(nodes, prefix)
|
|
433
444
|
visible_nodes = nodes.select do |node|
|
|
434
445
|
!node.is_a?(Span) || span_visible?(node)
|
|
@@ -491,6 +502,7 @@ module Vivarium
|
|
|
491
502
|
return "EXCP" if ev.event_name == "span_raise"
|
|
492
503
|
return "USDT" if SPAN_EVENT_NAMES.include?(ev.event_name)
|
|
493
504
|
return "SSL" if ev.event_name == SSL_WRITE_EVENT_NAME
|
|
505
|
+
return "DL" if DL_EVENT_NAMES.include?(ev.event_name)
|
|
494
506
|
return "LSM" if LSM_EVENT_NAMES.include?(ev.event_name)
|
|
495
507
|
return "TP" if TP_EVENT_NAMES.include?(ev.event_name)
|
|
496
508
|
|
|
@@ -523,28 +535,20 @@ module Vivarium
|
|
|
523
535
|
|
|
524
536
|
def render_raise_target(ev)
|
|
525
537
|
bytes = ev.payload.to_s.b
|
|
526
|
-
return "-" if bytes.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if message_id != -1
|
|
539
|
-
msg = Vivarium::Usdt.get_message_name(message_id)
|
|
540
|
-
parts << "message=#{msg.inspect}" if msg
|
|
541
|
-
end
|
|
542
|
-
|
|
543
|
-
if file_id != -1 && (file_name = Vivarium::Usdt.get_file_name(file_id))
|
|
544
|
-
lno = lineno && lineno > 0 ? ":#{lineno}" : ""
|
|
538
|
+
return "-" if bytes.empty?
|
|
539
|
+
|
|
540
|
+
slot = Vivarium::SPAN_RAISE_SLOT_SIZE
|
|
541
|
+
error_name = Vivarium.c_string(bytes[0, slot])
|
|
542
|
+
message = Vivarium.c_string(bytes[slot, slot])
|
|
543
|
+
file_name = Vivarium.c_string(bytes[slot * 2, slot])
|
|
544
|
+
lineno = bytes.bytesize > Vivarium::SPAN_RAISE_LINENO_OFFSET ? bytes[Vivarium::SPAN_RAISE_LINENO_OFFSET, 8].unpack1("q<") : -1
|
|
545
|
+
|
|
546
|
+
parts = ["error=#{error_name.empty? ? '?' : error_name}"]
|
|
547
|
+
parts << "message=#{message.inspect}" unless message.empty?
|
|
548
|
+
unless file_name.empty?
|
|
549
|
+
lno = lineno > 0 ? ":#{lineno}" : ""
|
|
545
550
|
parts << "at=#{File.basename(file_name)}#{lno}"
|
|
546
551
|
end
|
|
547
|
-
|
|
548
552
|
parts.join(" ")
|
|
549
553
|
end
|
|
550
554
|
|
|
@@ -564,12 +568,12 @@ module Vivarium
|
|
|
564
568
|
|
|
565
569
|
def read_span_payload(payload)
|
|
566
570
|
bytes = payload.to_s.b
|
|
567
|
-
return [
|
|
571
|
+
return [nil, nil, -1] if bytes.empty?
|
|
568
572
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
lineno
|
|
572
|
-
[
|
|
573
|
+
method_name = Vivarium.c_string(bytes[0, Vivarium::SPAN_METHOD_SIZE])
|
|
574
|
+
file_name = Vivarium.c_string(bytes[Vivarium::SPAN_METHOD_SIZE, Vivarium::SPAN_FILE_SIZE])
|
|
575
|
+
lineno = bytes.bytesize > Vivarium::SPAN_LINENO_OFFSET ? bytes[Vivarium::SPAN_LINENO_OFFSET, 8].unpack1("q<") : -1
|
|
576
|
+
[method_name, file_name, lineno]
|
|
573
577
|
end
|
|
574
578
|
|
|
575
579
|
def read_proc_fork_child_pid(payload)
|
data/lib/vivarium/version.rb
CHANGED
data/lib/vivarium.rb
CHANGED
|
@@ -24,14 +24,24 @@ module Vivarium
|
|
|
24
24
|
EVENT_TS_SIZE = 8
|
|
25
25
|
PROC_EXEC_SLOT_SIZE = 64
|
|
26
26
|
PROC_EXEC_SLOT_COUNT = 4
|
|
27
|
-
EVENT_STRUCT_SIZE =
|
|
27
|
+
EVENT_STRUCT_SIZE = 296
|
|
28
28
|
EVENT_TS_OFFSET = 0
|
|
29
29
|
EVENT_PID_OFFSET = 8
|
|
30
30
|
EVENT_TID_OFFSET = 12
|
|
31
31
|
EVENT_NAME_OFFSET = 16
|
|
32
32
|
EVENT_PAYLOAD_OFFSET = 32
|
|
33
|
+
EVENT_DROPPED_OFFSET = 288
|
|
33
34
|
EVENTS_RINGBUF_PAGES = 256
|
|
34
35
|
|
|
36
|
+
SPAN_METHOD_SIZE = 128
|
|
37
|
+
SPAN_FILE_SIZE = 120
|
|
38
|
+
SPAN_LINENO_OFFSET = SPAN_METHOD_SIZE + SPAN_FILE_SIZE # 248
|
|
39
|
+
SPAN_FILE_ARG_MAX = SPAN_FILE_SIZE - 1
|
|
40
|
+
|
|
41
|
+
SPAN_RAISE_SLOT_SIZE = 80
|
|
42
|
+
SPAN_RAISE_LINENO_OFFSET = SPAN_RAISE_SLOT_SIZE * 3 # 240
|
|
43
|
+
SPAN_RAISE_FILE_ARG_MAX = SPAN_RAISE_SLOT_SIZE - 1
|
|
44
|
+
|
|
35
45
|
SSL_WRITE_PAYLOAD_DATA_LEN_OFFSET = 0
|
|
36
46
|
SSL_WRITE_PAYLOAD_CAP_LEN_OFFSET = 4
|
|
37
47
|
SSL_WRITE_PAYLOAD_DATA_OFFSET = 8
|
|
@@ -52,6 +62,16 @@ module Vivarium
|
|
|
52
62
|
"/usr/lib/libssl.so.1.1"
|
|
53
63
|
].freeze
|
|
54
64
|
|
|
65
|
+
LIBC_SEARCH_PATHS = [
|
|
66
|
+
"/lib/x86_64-linux-gnu/libc.so.6",
|
|
67
|
+
"/lib/aarch64-linux-gnu/libc.so.6",
|
|
68
|
+
"/usr/lib/x86_64-linux-gnu/libc.so.6",
|
|
69
|
+
"/usr/lib/aarch64-linux-gnu/libc.so.6",
|
|
70
|
+
"/lib64/libc.so.6",
|
|
71
|
+
"/usr/lib64/libc.so.6",
|
|
72
|
+
"/lib/libc.so.6",
|
|
73
|
+
].freeze
|
|
74
|
+
|
|
55
75
|
SPAN_ALLOWCLASSES = [
|
|
56
76
|
Socket,
|
|
57
77
|
BasicSocket,
|
|
@@ -79,6 +99,7 @@ module Vivarium
|
|
|
79
99
|
EVENT_SEVERITY_HIGH = %w[
|
|
80
100
|
capable_check bprm_creds setid_change task_kill
|
|
81
101
|
ptrace_check sb_mount kernel_read_file
|
|
102
|
+
dlopen
|
|
82
103
|
].freeze
|
|
83
104
|
|
|
84
105
|
CAPABILITY_NAMES = {
|
|
@@ -137,6 +158,16 @@ module Vivarium
|
|
|
137
158
|
str[0, nul]
|
|
138
159
|
end
|
|
139
160
|
|
|
161
|
+
def self.tail_fit_string(value, max_bytes, marker: "...")
|
|
162
|
+
str = value.to_s.b
|
|
163
|
+
return str if str.bytesize <= max_bytes
|
|
164
|
+
return str.byteslice(-max_bytes, max_bytes) || "" if max_bytes <= marker.bytesize
|
|
165
|
+
|
|
166
|
+
tail_size = max_bytes - marker.bytesize
|
|
167
|
+
tail = str.byteslice(-tail_size, tail_size) || ""
|
|
168
|
+
"#{marker}#{tail}"
|
|
169
|
+
end
|
|
170
|
+
|
|
140
171
|
def self.event_severity(event_name)
|
|
141
172
|
EVENT_SEVERITY_HIGH.include?(event_name.to_s) ? "high" : "medium"
|
|
142
173
|
end
|
|
@@ -463,6 +494,8 @@ module Vivarium
|
|
|
463
494
|
when "ssl_write"
|
|
464
495
|
decoded = decode_ssl_write_payload(event.payload)
|
|
465
496
|
"data_len=#{decoded[:data_len]} cap_len=#{decoded[:cap_len]}"
|
|
497
|
+
when "dlopen", "mmap_exec"
|
|
498
|
+
strip_to_first_null(event.payload).inspect
|
|
466
499
|
else
|
|
467
500
|
strip_to_first_null(event.payload).inspect
|
|
468
501
|
end
|
|
@@ -619,12 +652,14 @@ module Vivarium
|
|
|
619
652
|
u32 tid;
|
|
620
653
|
char event_name[16];
|
|
621
654
|
char payload[#{EVENT_PAYLOAD_SIZE}];
|
|
655
|
+
u64 dropped_since_last;
|
|
622
656
|
};
|
|
623
657
|
|
|
624
658
|
BPF_HASH(config_root_targets, u32, u8, 1024);
|
|
625
659
|
BPF_HASH(config_spawned_targets, u32, u8, 8192);
|
|
626
660
|
BPF_HASH(dns_connected_tids, u32, u8, 8192);
|
|
627
661
|
BPF_RINGBUF_OUTPUT(events, #{EVENTS_RINGBUF_PAGES});
|
|
662
|
+
BPF_ARRAY(drop_counter, u64, 1);
|
|
628
663
|
|
|
629
664
|
static __always_inline int target_enabled(u32 pid, u32 tid)
|
|
630
665
|
{
|
|
@@ -666,14 +701,27 @@ module Vivarium
|
|
|
666
701
|
|
|
667
702
|
static __always_inline void submit_event(struct event_t *src)
|
|
668
703
|
{
|
|
704
|
+
u32 key = 0;
|
|
705
|
+
u64 *cnt;
|
|
706
|
+
|
|
669
707
|
struct event_t *ev = events.ringbuf_reserve(sizeof(struct event_t));
|
|
670
708
|
if (!ev) {
|
|
709
|
+
cnt = drop_counter.lookup(&key);
|
|
710
|
+
if (cnt) {
|
|
711
|
+
__sync_fetch_and_add(cnt, 1);
|
|
712
|
+
}
|
|
671
713
|
return;
|
|
672
714
|
}
|
|
673
715
|
|
|
674
716
|
__builtin_memcpy(ev, src, sizeof(*ev));
|
|
675
717
|
ev->ktime_ns = bpf_ktime_get_ns();
|
|
676
718
|
ev->tid = (u32)bpf_get_current_pid_tgid();
|
|
719
|
+
ev->dropped_since_last = 0;
|
|
720
|
+
|
|
721
|
+
cnt = drop_counter.lookup(&key);
|
|
722
|
+
if (cnt && *cnt > 0) {
|
|
723
|
+
ev->dropped_since_last = __sync_lock_test_and_set(cnt, 0);
|
|
724
|
+
}
|
|
677
725
|
|
|
678
726
|
events.ringbuf_submit(ev, 0);
|
|
679
727
|
}
|
|
@@ -807,6 +855,39 @@ module Vivarium
|
|
|
807
855
|
return 0;
|
|
808
856
|
}
|
|
809
857
|
|
|
858
|
+
LSM_PROBE(mmap_file, struct file *file, unsigned long reqprot,
|
|
859
|
+
unsigned long prot, unsigned long flags)
|
|
860
|
+
{
|
|
861
|
+
if (!file) {
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
if (!((prot | reqprot) & 0x04)) { /* PROT_EXEC */
|
|
865
|
+
return 0;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
869
|
+
u32 pid = pid_tgid >> 32;
|
|
870
|
+
u32 tid = (u32)pid_tgid;
|
|
871
|
+
if (!target_enabled(pid, tid)) {
|
|
872
|
+
return 0;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
struct event_t ev = {};
|
|
876
|
+
int path_ret;
|
|
877
|
+
ev.pid = pid;
|
|
878
|
+
__builtin_memcpy(ev.event_name, "mmap_exec", 10);
|
|
879
|
+
|
|
880
|
+
path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
|
|
881
|
+
if (path_ret < 0) {
|
|
882
|
+
if (ev.payload[0] == 0) {
|
|
883
|
+
__builtin_memcpy(ev.payload, "<path_error>", 13);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
submit_event(&ev);
|
|
888
|
+
return 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
810
891
|
LSM_PROBE(socket_create, int family, int type, int protocol, int kern)
|
|
811
892
|
{
|
|
812
893
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
@@ -1332,19 +1413,19 @@ module Vivarium
|
|
|
1332
1413
|
return 0;
|
|
1333
1414
|
}
|
|
1334
1415
|
|
|
1335
|
-
u64
|
|
1336
|
-
u64
|
|
1337
|
-
|
|
1338
|
-
bpf_usdt_readarg(1, ctx, &
|
|
1339
|
-
bpf_usdt_readarg(2, ctx, &
|
|
1416
|
+
u64 method_str_ptr = 0;
|
|
1417
|
+
u64 file_str_ptr = 0;
|
|
1418
|
+
s64 lineno = 0;
|
|
1419
|
+
bpf_usdt_readarg(1, ctx, &method_str_ptr);
|
|
1420
|
+
bpf_usdt_readarg(2, ctx, &file_str_ptr);
|
|
1340
1421
|
bpf_usdt_readarg(3, ctx, &lineno);
|
|
1341
1422
|
|
|
1342
1423
|
struct event_t ev = {};
|
|
1343
1424
|
ev.pid = pid;
|
|
1344
1425
|
__builtin_memcpy(ev.event_name, "span_start", 11);
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
__builtin_memcpy(&ev.payload[
|
|
1426
|
+
bpf_probe_read_user_str(&ev.payload[0], #{SPAN_METHOD_SIZE}, (void*)method_str_ptr);
|
|
1427
|
+
bpf_probe_read_user_str(&ev.payload[#{SPAN_METHOD_SIZE}], #{SPAN_FILE_SIZE}, (void*)file_str_ptr);
|
|
1428
|
+
__builtin_memcpy(&ev.payload[#{SPAN_LINENO_OFFSET}], &lineno, sizeof(lineno));
|
|
1348
1429
|
submit_event(&ev);
|
|
1349
1430
|
return 0;
|
|
1350
1431
|
}
|
|
@@ -1359,19 +1440,19 @@ module Vivarium
|
|
|
1359
1440
|
return 0;
|
|
1360
1441
|
}
|
|
1361
1442
|
|
|
1362
|
-
u64
|
|
1363
|
-
u64
|
|
1364
|
-
|
|
1365
|
-
bpf_usdt_readarg(1, ctx, &
|
|
1366
|
-
bpf_usdt_readarg(2, ctx, &
|
|
1443
|
+
u64 method_str_ptr = 0;
|
|
1444
|
+
u64 file_str_ptr = 0;
|
|
1445
|
+
s64 lineno = 0;
|
|
1446
|
+
bpf_usdt_readarg(1, ctx, &method_str_ptr);
|
|
1447
|
+
bpf_usdt_readarg(2, ctx, &file_str_ptr);
|
|
1367
1448
|
bpf_usdt_readarg(3, ctx, &lineno);
|
|
1368
1449
|
|
|
1369
1450
|
struct event_t ev = {};
|
|
1370
1451
|
ev.pid = pid;
|
|
1371
1452
|
__builtin_memcpy(ev.event_name, "span_stop", 10);
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
__builtin_memcpy(&ev.payload[
|
|
1453
|
+
bpf_probe_read_user_str(&ev.payload[0], #{SPAN_METHOD_SIZE}, (void*)method_str_ptr);
|
|
1454
|
+
bpf_probe_read_user_str(&ev.payload[#{SPAN_METHOD_SIZE}], #{SPAN_FILE_SIZE}, (void*)file_str_ptr);
|
|
1455
|
+
__builtin_memcpy(&ev.payload[#{SPAN_LINENO_OFFSET}], &lineno, sizeof(lineno));
|
|
1375
1456
|
submit_event(&ev);
|
|
1376
1457
|
return 0;
|
|
1377
1458
|
}
|
|
@@ -1412,6 +1493,32 @@ module Vivarium
|
|
|
1412
1493
|
return 0;
|
|
1413
1494
|
}
|
|
1414
1495
|
|
|
1496
|
+
int on_dlopen(struct pt_regs *ctx)
|
|
1497
|
+
{
|
|
1498
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
1499
|
+
u32 pid = pid_tgid >> 32;
|
|
1500
|
+
u32 tid = (u32)pid_tgid;
|
|
1501
|
+
if (!target_enabled(pid, tid)) {
|
|
1502
|
+
return 0;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const char *filename = (const char *)PT_REGS_PARM1(ctx);
|
|
1506
|
+
if (!filename) {
|
|
1507
|
+
return 0;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
struct event_t ev = {};
|
|
1511
|
+
ev.pid = pid;
|
|
1512
|
+
__builtin_memcpy(ev.event_name, "dlopen", 7);
|
|
1513
|
+
|
|
1514
|
+
if (bpf_probe_read_user_str(ev.payload, sizeof(ev.payload), filename) < 0) {
|
|
1515
|
+
__builtin_memcpy(ev.payload, "<path_error>", 13);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
submit_event(&ev);
|
|
1519
|
+
return 0;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1415
1522
|
int on_span_raise(struct pt_regs *ctx)
|
|
1416
1523
|
{
|
|
1417
1524
|
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
@@ -1422,31 +1529,34 @@ module Vivarium
|
|
|
1422
1529
|
return 0;
|
|
1423
1530
|
}
|
|
1424
1531
|
|
|
1425
|
-
u64
|
|
1426
|
-
u64
|
|
1427
|
-
u64
|
|
1428
|
-
|
|
1429
|
-
bpf_usdt_readarg(1, ctx, &
|
|
1430
|
-
bpf_usdt_readarg(2, ctx, &
|
|
1431
|
-
bpf_usdt_readarg(3, ctx, &
|
|
1532
|
+
u64 error_str_ptr = 0;
|
|
1533
|
+
u64 message_str_ptr = 0;
|
|
1534
|
+
u64 file_str_ptr = 0;
|
|
1535
|
+
s64 lineno = 0;
|
|
1536
|
+
bpf_usdt_readarg(1, ctx, &error_str_ptr);
|
|
1537
|
+
bpf_usdt_readarg(2, ctx, &message_str_ptr);
|
|
1538
|
+
bpf_usdt_readarg(3, ctx, &file_str_ptr);
|
|
1432
1539
|
bpf_usdt_readarg(4, ctx, &lineno);
|
|
1433
1540
|
|
|
1434
1541
|
struct event_t ev = {};
|
|
1435
1542
|
ev.pid = pid;
|
|
1436
1543
|
__builtin_memcpy(ev.event_name, "span_raise", 11);
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
__builtin_memcpy(&ev.payload[
|
|
1544
|
+
bpf_probe_read_user_str(&ev.payload[0], #{SPAN_RAISE_SLOT_SIZE}, (void*)error_str_ptr);
|
|
1545
|
+
bpf_probe_read_user_str(&ev.payload[#{SPAN_RAISE_SLOT_SIZE}], #{SPAN_RAISE_SLOT_SIZE}, (void*)message_str_ptr);
|
|
1546
|
+
bpf_probe_read_user_str(&ev.payload[#{SPAN_RAISE_SLOT_SIZE * 2}], #{SPAN_RAISE_SLOT_SIZE}, (void*)file_str_ptr);
|
|
1547
|
+
__builtin_memcpy(&ev.payload[#{SPAN_RAISE_LINENO_OFFSET}], &lineno, sizeof(lineno));
|
|
1441
1548
|
submit_event(&ev);
|
|
1442
1549
|
return 0;
|
|
1443
1550
|
}
|
|
1444
1551
|
CLANG
|
|
1445
1552
|
|
|
1446
|
-
def initialize(pin_dir: Vivarium.bpf_pin_dir, ssl_trace: true, libssl_path: nil
|
|
1447
|
-
|
|
1448
|
-
@
|
|
1449
|
-
@
|
|
1553
|
+
def initialize(pin_dir: Vivarium.bpf_pin_dir, ssl_trace: true, libssl_path: nil,
|
|
1554
|
+
dlopen_trace: true, libc_path: nil)
|
|
1555
|
+
@pin_dir = pin_dir
|
|
1556
|
+
@ssl_trace = ssl_trace
|
|
1557
|
+
@libssl_path = libssl_path
|
|
1558
|
+
@dlopen_trace = dlopen_trace
|
|
1559
|
+
@libc_path = libc_path
|
|
1450
1560
|
end
|
|
1451
1561
|
|
|
1452
1562
|
def run
|
|
@@ -1459,6 +1569,7 @@ module Vivarium
|
|
|
1459
1569
|
.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
|
|
1460
1570
|
.gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
|
|
1461
1571
|
|
|
1572
|
+
require "vivarium_usdt"
|
|
1462
1573
|
usdt_so_path = ENV.fetch("VIVARIUM_USDT_SO_PATH") { Vivarium.locate_vivarium_usdt_so }
|
|
1463
1574
|
usdt = RbBCC::USDT.new(path: usdt_so_path)
|
|
1464
1575
|
usdt.enable_probe(probe: "start_probe", fn_name: "on_span_start")
|
|
@@ -1468,6 +1579,7 @@ module Vivarium
|
|
|
1468
1579
|
bpf = RbBCC::BCC.new(text: program, usdt_contexts: [usdt])
|
|
1469
1580
|
|
|
1470
1581
|
attach_ssl_write_uprobe(bpf) if @ssl_trace
|
|
1582
|
+
attach_dlopen_uprobe(bpf) if @dlopen_trace
|
|
1471
1583
|
|
|
1472
1584
|
config_root_targets = bpf["config_root_targets"]
|
|
1473
1585
|
config_spawned_targets = bpf["config_spawned_targets"]
|
|
@@ -1526,6 +1638,39 @@ module Vivarium
|
|
|
1526
1638
|
LIBSSL_SEARCH_PATHS.find { |p| File.exist?(p) }
|
|
1527
1639
|
end
|
|
1528
1640
|
|
|
1641
|
+
def attach_dlopen_uprobe(bpf)
|
|
1642
|
+
path = resolve_libc_path
|
|
1643
|
+
unless path
|
|
1644
|
+
warn "[vivariumd] libc not found; dlopen uprobe disabled " \
|
|
1645
|
+
"(set --libc PATH or VIVARIUM_LIBC_PATH to override)"
|
|
1646
|
+
return
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
bpf.attach_uprobe(name: path, sym: "dlopen", fn_name: "on_dlopen")
|
|
1650
|
+
puts "[vivariumd] dlopen uprobe attached via #{path}"
|
|
1651
|
+
rescue StandardError => e
|
|
1652
|
+
warn "[vivariumd] dlopen uprobe attach failed: #{e.class}: #{e.message}"
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
def resolve_libc_path
|
|
1656
|
+
if @libc_path
|
|
1657
|
+
return @libc_path if File.exist?(@libc_path)
|
|
1658
|
+
|
|
1659
|
+
warn "[vivariumd] --libc path does not exist: #{@libc_path}"
|
|
1660
|
+
return nil
|
|
1661
|
+
end
|
|
1662
|
+
|
|
1663
|
+
env_path = ENV["VIVARIUM_LIBC_PATH"]
|
|
1664
|
+
if env_path && !env_path.empty?
|
|
1665
|
+
return env_path if File.exist?(env_path)
|
|
1666
|
+
|
|
1667
|
+
warn "[vivariumd] VIVARIUM_LIBC_PATH does not exist: #{env_path}"
|
|
1668
|
+
return nil
|
|
1669
|
+
end
|
|
1670
|
+
|
|
1671
|
+
LIBC_SEARCH_PATHS.find { |p| File.exist?(p) }
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1529
1674
|
def ensure_root!
|
|
1530
1675
|
return if Process.uid.zero?
|
|
1531
1676
|
|
|
@@ -1680,20 +1825,18 @@ module Vivarium
|
|
|
1680
1825
|
pid = Process.pid
|
|
1681
1826
|
store.register_pid(pid)
|
|
1682
1827
|
|
|
1683
|
-
method_id_queue = Thread::Queue.new
|
|
1684
1828
|
main_tid = gettid
|
|
1685
1829
|
|
|
1686
1830
|
correlator = Correlator.new(
|
|
1687
1831
|
pin_dir: pin_dir,
|
|
1688
1832
|
observer_pid: pid,
|
|
1689
1833
|
main_tid: main_tid,
|
|
1690
|
-
method_id_queue: method_id_queue,
|
|
1691
1834
|
filter: filter,
|
|
1692
1835
|
dest: dest
|
|
1693
1836
|
)
|
|
1694
1837
|
correlator.start
|
|
1695
1838
|
|
|
1696
|
-
tracer = build_observe_tracepoint
|
|
1839
|
+
tracer = build_observe_tracepoint
|
|
1697
1840
|
tracer.enable
|
|
1698
1841
|
|
|
1699
1842
|
session = ObservationSession.new(
|
|
@@ -1710,20 +1853,18 @@ module Vivarium
|
|
|
1710
1853
|
pid = Process.pid
|
|
1711
1854
|
store.register_pid(pid)
|
|
1712
1855
|
|
|
1713
|
-
method_id_queue = Thread::Queue.new
|
|
1714
1856
|
main_tid = gettid
|
|
1715
1857
|
|
|
1716
1858
|
correlator = Correlator.new(
|
|
1717
1859
|
pin_dir: pin_dir,
|
|
1718
1860
|
observer_pid: pid,
|
|
1719
1861
|
main_tid: main_tid,
|
|
1720
|
-
method_id_queue: method_id_queue,
|
|
1721
1862
|
filter: filter,
|
|
1722
1863
|
dest: dest
|
|
1723
1864
|
)
|
|
1724
1865
|
correlator.start
|
|
1725
1866
|
|
|
1726
|
-
tracer = build_observe_tracepoint
|
|
1867
|
+
tracer = build_observe_tracepoint
|
|
1727
1868
|
tracer.enable
|
|
1728
1869
|
|
|
1729
1870
|
yield
|
|
@@ -1733,20 +1874,19 @@ module Vivarium
|
|
|
1733
1874
|
correlator&.stop
|
|
1734
1875
|
end
|
|
1735
1876
|
|
|
1736
|
-
def self.build_observe_tracepoint
|
|
1877
|
+
def self.build_observe_tracepoint
|
|
1737
1878
|
allow_classes = SPAN_ALLOWCLASSES
|
|
1738
1879
|
allowlist = SPAN_ALLOWLIST
|
|
1739
1880
|
TracePoint.new(:call, :c_call, :return, :c_return, :raise) do |tp|
|
|
1740
1881
|
if tp.event == :raise
|
|
1741
1882
|
# FIXME: handle threaded events in the future
|
|
1742
|
-
if tp.raised_exception.kind_of?(ThreadError)
|
|
1743
|
-
next
|
|
1744
|
-
end
|
|
1883
|
+
next if tp.raised_exception.kind_of?(ThreadError)
|
|
1745
1884
|
|
|
1885
|
+
file_arg = tail_fit_string(tp.path, SPAN_RAISE_FILE_ARG_MAX)
|
|
1746
1886
|
Vivarium::Usdt.raise(
|
|
1747
1887
|
tp.raised_exception.class.to_s,
|
|
1748
1888
|
tp.raised_exception.message.to_s,
|
|
1749
|
-
file:
|
|
1889
|
+
file: file_arg,
|
|
1750
1890
|
lineno: tp.lineno
|
|
1751
1891
|
)
|
|
1752
1892
|
next
|
|
@@ -1758,12 +1898,12 @@ module Vivarium
|
|
|
1758
1898
|
allow_classes.any? { |klass| tp.defined_class == klass.singleton_class }
|
|
1759
1899
|
next unless is_target
|
|
1760
1900
|
|
|
1901
|
+
file_arg = tail_fit_string(tp.path, SPAN_FILE_ARG_MAX)
|
|
1761
1902
|
case tp.event
|
|
1762
1903
|
when :call, :c_call
|
|
1763
|
-
|
|
1764
|
-
method_id_queue << [method_id, signature]
|
|
1904
|
+
Vivarium::Usdt.start(tp.defined_class.to_s, tp.method_id.to_s, file: file_arg, lineno: tp.lineno)
|
|
1765
1905
|
when :return, :c_return
|
|
1766
|
-
Vivarium::Usdt.stop(tp.defined_class.to_s, tp.method_id.to_s, file:
|
|
1906
|
+
Vivarium::Usdt.stop(tp.defined_class.to_s, tp.method_id.to_s, file: file_arg, lineno: tp.lineno)
|
|
1767
1907
|
end
|
|
1768
1908
|
end
|
|
1769
1909
|
end
|
|
@@ -1794,9 +1934,11 @@ module Vivarium
|
|
|
1794
1934
|
end
|
|
1795
1935
|
|
|
1796
1936
|
def self.run_daemon!(argv = ARGV)
|
|
1797
|
-
options = { pin_dir: bpf_pin_dir, ssl_trace: true, libssl_path: nil
|
|
1937
|
+
options = { pin_dir: bpf_pin_dir, ssl_trace: true, libssl_path: nil,
|
|
1938
|
+
dlopen_trace: true, libc_path: nil }
|
|
1798
1939
|
OptionParser.new do |opts|
|
|
1799
|
-
opts.banner = "Usage: vivariumd [--pin-dir PATH] [--no-ssl-trace] [--libssl PATH]"
|
|
1940
|
+
opts.banner = "Usage: vivariumd [--pin-dir PATH] [--no-ssl-trace] [--libssl PATH] " \
|
|
1941
|
+
"[--no-dlopen-trace] [--libc PATH]"
|
|
1800
1942
|
opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
|
|
1801
1943
|
opts.on("--[no-]ssl-trace", "Attach OpenSSL SSL_write uprobe (default: enabled)") do |v|
|
|
1802
1944
|
options[:ssl_trace] = v
|
|
@@ -1804,12 +1946,20 @@ module Vivarium
|
|
|
1804
1946
|
opts.on("--libssl PATH", "Path to libssl.so to attach SSL_write to") do |v|
|
|
1805
1947
|
options[:libssl_path] = v
|
|
1806
1948
|
end
|
|
1949
|
+
opts.on("--[no-]dlopen-trace", "Attach libc dlopen uprobe (default: enabled)") do |v|
|
|
1950
|
+
options[:dlopen_trace] = v
|
|
1951
|
+
end
|
|
1952
|
+
opts.on("--libc PATH", "Path to libc.so for dlopen uprobe") do |v|
|
|
1953
|
+
options[:libc_path] = v
|
|
1954
|
+
end
|
|
1807
1955
|
end.parse!(argv)
|
|
1808
1956
|
|
|
1809
1957
|
Daemon.new(
|
|
1810
1958
|
pin_dir: options[:pin_dir],
|
|
1811
1959
|
ssl_trace: options[:ssl_trace],
|
|
1812
|
-
libssl_path: options[:libssl_path]
|
|
1960
|
+
libssl_path: options[:libssl_path],
|
|
1961
|
+
dlopen_trace: options[:dlopen_trace],
|
|
1962
|
+
libc_path: options[:libc_path]
|
|
1813
1963
|
).run
|
|
1814
1964
|
end
|
|
1815
1965
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vivarium
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uchio Kondo
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.4.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.4.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: ostruct
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -64,6 +64,8 @@ files:
|
|
|
64
64
|
- CONTEXT.md
|
|
65
65
|
- README.md
|
|
66
66
|
- Rakefile
|
|
67
|
+
- examples/dlopen_demo.rb
|
|
68
|
+
- examples/drop_demo.rb
|
|
67
69
|
- examples/execve_demo.rb
|
|
68
70
|
- examples/file_operation_demo.rb
|
|
69
71
|
- examples/network_client_demo.rb
|