vivarium 0.0.1 → 0.1.1
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/README.md +26 -4
- data/lib/vivarium/logger.rb +68 -0
- data/lib/vivarium/version.rb +1 -1
- data/lib/vivarium.rb +182 -38
- data/sig/vivarium.rbs +12 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bc495e3c2b8f9d99a2b1c84706b5e00913cc08831b3103b0460db0d4027c77b
|
|
4
|
+
data.tar.gz: 1ee086243fdc153da5bac6a2bec8d0afe8e644ceac2fef2308b7723b89533338
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a203dd1cbaeff9fe0f4bf464a01ff49f131a104ca9e1eb14b6c08dd4371f49acf31c493119eee70114cafacbc6aa8a42976a9e6c230b1d828fe442e738c57c54
|
|
7
|
+
data.tar.gz: a31a64e20f2f7d0956626658b5d6f91e0cd5333f97854ad3388c41cc0d3b0b8d233850c3bdaaf60663c9152d6cf35e4034355e37c50fc913687ad05295dffaba
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Vivarium
|
|
2
2
|
|
|
3
|
+
[](https://rubygems.org/gems/vivarium)
|
|
4
|
+
|
|
5
|
+
RubyGems: https://rubygems.org/gems/vivarium
|
|
6
|
+
|
|
3
7
|
Vivarium is an observation and sandbox helper for Ruby.
|
|
4
8
|
|
|
5
9
|
It combines:
|
|
@@ -15,11 +19,13 @@ Implemented in this repository:
|
|
|
15
19
|
|
|
16
20
|
- BPF LSM hook on `file_open`
|
|
17
21
|
- Shared pinned maps on bpffs
|
|
18
|
-
- `
|
|
22
|
+
- `config_root_targets` (root PID -> 0/1)
|
|
23
|
+
- `config_spawned_targets` (spawned TID -> 0/1)
|
|
19
24
|
- `event_invoked` (array length 64 with `event_t` records)
|
|
20
25
|
- `event_write_pos` (cursor for appending into `event_invoked`)
|
|
21
26
|
- Ruby API `Vivarium.observe do ... end`
|
|
22
|
-
- Registers current PID to `
|
|
27
|
+
- Registers current PID to `config_root_targets`
|
|
28
|
+
- eBPF tracks spawned descendants into `config_spawned_targets` via `sched_process_fork`
|
|
23
29
|
- On each `:return` / `:c_return`, drains `event_invoked`
|
|
24
30
|
- Prints stack trace + events
|
|
25
31
|
- Clears event slots and cursor
|
|
@@ -71,19 +77,34 @@ sudo bundle exec vivariumd
|
|
|
71
77
|
require "vivarium"
|
|
72
78
|
|
|
73
79
|
Vivarium.observe do
|
|
74
|
-
|
|
80
|
+
File.read("/etc/passwd")
|
|
75
81
|
end
|
|
76
82
|
```
|
|
77
83
|
|
|
84
|
+
You can also start top-level observation without a block (it keeps observing until process exit):
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
require "vivarium"
|
|
88
|
+
|
|
89
|
+
observer = Vivarium.top_observe
|
|
90
|
+
# or: Vivarium.observe
|
|
91
|
+
# do anything ...
|
|
92
|
+
observer.stop
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
By default, Vivarium excludes its own internal frames from stack output. Set `VIVARIUM_FILTER_INTERNAL_FRAMES=0` to disable this filter.
|
|
96
|
+
|
|
78
97
|
You can override pin directory via `VIVARIUM_BPF_PIN_DIR` on both sides:
|
|
79
98
|
|
|
80
99
|
```bash
|
|
81
100
|
VIVARIUM_BPF_PIN_DIR=/sys/fs/bpf/vivarium bundle exec vivariumd
|
|
82
101
|
```
|
|
83
102
|
|
|
103
|
+
Use `Vivarium.bpf_pin_dir = "/sys/fs/bpf/..."` in Ruby code to set it programmatically.
|
|
104
|
+
|
|
84
105
|
```ruby
|
|
85
|
-
ENV["VIVARIUM_BPF_PIN_DIR"] = "/sys/fs/bpf/vivarium"
|
|
86
106
|
require "vivarium"
|
|
107
|
+
Vivarium.bpf_pin_dir = "/sys/fs/bpf/vivarium"
|
|
87
108
|
```
|
|
88
109
|
|
|
89
110
|
## Development
|
|
@@ -108,6 +129,7 @@ bundle exec vivariumd --pin-dir /sys/fs/bpf/vivarium
|
|
|
108
129
|
- Current output format is textual and intended for iteration.
|
|
109
130
|
- `vivariumd` resolves `struct file::f_path` offset from `/sys/kernel/btf/vmlinux` at startup.
|
|
110
131
|
- You can override offset manually with `VIVARIUM_FILE_F_PATH_OFFSET` if auto-detection fails.
|
|
132
|
+
- `vivariumd` also prints `bpf_trace_printk` lines (`vivarium: pid=... path=...`) to its own logs.
|
|
111
133
|
|
|
112
134
|
## Contributing
|
|
113
135
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Vivarium
|
|
6
|
+
class Logger
|
|
7
|
+
FORMATS = %i[human json].freeze
|
|
8
|
+
|
|
9
|
+
# dest: IO object or file path string
|
|
10
|
+
# format: :human or :json
|
|
11
|
+
# TODO: support flushing in bulk for performance
|
|
12
|
+
def initialize(dest: $stdout, format: :human)
|
|
13
|
+
@format = format.to_sym
|
|
14
|
+
raise ArgumentError, "unknown format: #{@format}; choose from #{FORMATS.join(', ')}" unless FORMATS.include?(@format)
|
|
15
|
+
|
|
16
|
+
if dest.is_a?(String)
|
|
17
|
+
@io = File.open(dest, "a")
|
|
18
|
+
@owned = true
|
|
19
|
+
else
|
|
20
|
+
@io = dest
|
|
21
|
+
@owned = false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def log(events, tp, stack)
|
|
26
|
+
case @format
|
|
27
|
+
when :human then log_human(events, tp, stack)
|
|
28
|
+
when :json then log_json(events, tp, stack)
|
|
29
|
+
end
|
|
30
|
+
@io.flush
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def info(message)
|
|
34
|
+
@io.puts("[vivarium] #{message}")
|
|
35
|
+
@io.flush
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def close
|
|
39
|
+
@io.close if @owned
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def log_human(events, tp, stack)
|
|
45
|
+
@io.puts "[vivarium] #{events.size} event(s) at #{tp.defined_class}##{tp.method_id} (#{tp.event})"
|
|
46
|
+
@io.puts " location: #{tp.path}:#{tp.lineno}"
|
|
47
|
+
events.each do |event|
|
|
48
|
+
@io.puts " pid=#{event.pid} #{event.event_name} payload=#{event.payload.inspect}"
|
|
49
|
+
end
|
|
50
|
+
@io.puts " stack:"
|
|
51
|
+
stack.each do |loc|
|
|
52
|
+
@io.puts " #{loc.path}:#{loc.lineno}:in #{loc.base_label}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def log_json(events, tp, stack)
|
|
57
|
+
entry = {
|
|
58
|
+
at: "#{tp.defined_class}##{tp.method_id}",
|
|
59
|
+
event: tp.event.to_s,
|
|
60
|
+
path: tp.path,
|
|
61
|
+
lineno: tp.lineno,
|
|
62
|
+
events: events.map { |e| { pid: e.pid, event_name: e.event_name, payload: e.payload } },
|
|
63
|
+
stack: stack.map { |loc| "#{loc.path}:#{loc.lineno}:in #{loc.base_label}" }
|
|
64
|
+
}
|
|
65
|
+
@io.puts JSON.generate(entry)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/vivarium/version.rb
CHANGED
data/lib/vivarium.rb
CHANGED
|
@@ -6,20 +6,33 @@ require "optparse"
|
|
|
6
6
|
require "pathname"
|
|
7
7
|
require "rbbcc"
|
|
8
8
|
require_relative "vivarium/version"
|
|
9
|
+
require_relative "vivarium/logger"
|
|
9
10
|
|
|
10
11
|
module Vivarium
|
|
11
12
|
class Error < StandardError; end
|
|
12
13
|
|
|
13
14
|
PIN_DIR = ENV.fetch("VIVARIUM_BPF_PIN_DIR", "/sys/fs/bpf/vivarium")
|
|
14
|
-
|
|
15
|
+
CONFIG_ROOT_TARGETS_PIN = File.join(PIN_DIR, "config_root_targets")
|
|
16
|
+
CONFIG_SPAWNED_TARGETS_PIN = File.join(PIN_DIR, "config_spawned_targets")
|
|
17
|
+
CONFIG_TARGETS_PIN = CONFIG_ROOT_TARGETS_PIN
|
|
15
18
|
EVENT_INVOKED_PIN = File.join(PIN_DIR, "event_invoked")
|
|
16
19
|
EVENT_WRITE_POS_PIN = File.join(PIN_DIR, "event_write_pos")
|
|
17
20
|
|
|
18
21
|
EVENT_NAME_SIZE = 16
|
|
19
|
-
EVENT_PAYLOAD_SIZE =
|
|
22
|
+
EVENT_PAYLOAD_SIZE = 256
|
|
20
23
|
EVENT_STRUCT_SIZE = 4 + EVENT_NAME_SIZE + EVENT_PAYLOAD_SIZE
|
|
21
24
|
EVENT_CAPACITY = 64
|
|
22
25
|
|
|
26
|
+
@bpf_pin_dir = PIN_DIR
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
attr_writer :bpf_pin_dir
|
|
30
|
+
|
|
31
|
+
def bpf_pin_dir
|
|
32
|
+
@bpf_pin_dir || PIN_DIR
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
Event = Struct.new(:pid, :event_name, :payload, keyword_init: true) do
|
|
24
37
|
def empty?
|
|
25
38
|
pid.to_i.zero? && event_name.to_s.empty? && payload.to_s.empty?
|
|
@@ -30,18 +43,33 @@ module Vivarium
|
|
|
30
43
|
bytes = bytes.ljust(EVENT_STRUCT_SIZE, "\x00")
|
|
31
44
|
|
|
32
45
|
pid = bytes[0, 4].unpack1("L<")
|
|
33
|
-
event_name = bytes[4, EVENT_NAME_SIZE]
|
|
34
|
-
payload = bytes[4 + EVENT_NAME_SIZE, EVENT_PAYLOAD_SIZE]
|
|
46
|
+
event_name = c_string(bytes[4, EVENT_NAME_SIZE])
|
|
47
|
+
payload = c_string(bytes[4 + EVENT_NAME_SIZE, EVENT_PAYLOAD_SIZE])
|
|
35
48
|
|
|
36
49
|
new(pid: pid, event_name: event_name, payload: payload)
|
|
37
50
|
end
|
|
51
|
+
|
|
52
|
+
def self.c_string(bytes)
|
|
53
|
+
str = bytes.to_s.b
|
|
54
|
+
nul = str.index("\x00")
|
|
55
|
+
return str if nul.nil?
|
|
56
|
+
|
|
57
|
+
str[0, nul]
|
|
58
|
+
end
|
|
38
59
|
end
|
|
39
60
|
|
|
40
61
|
class MapStore
|
|
41
|
-
def initialize(pin_dir:
|
|
62
|
+
def initialize(pin_dir: Vivarium.bpf_pin_dir)
|
|
42
63
|
@pin_dir = pin_dir
|
|
43
|
-
@
|
|
44
|
-
File.join(@pin_dir, "
|
|
64
|
+
@config_root_targets = RbBCC::HashTable.from_pin(
|
|
65
|
+
File.join(@pin_dir, "config_root_targets"),
|
|
66
|
+
"unsigned int",
|
|
67
|
+
"unsigned char",
|
|
68
|
+
keysize: 4,
|
|
69
|
+
leafsize: 1
|
|
70
|
+
)
|
|
71
|
+
@config_spawned_targets = RbBCC::HashTable.from_pin(
|
|
72
|
+
File.join(@pin_dir, "config_spawned_targets"),
|
|
45
73
|
"unsigned int",
|
|
46
74
|
"unsigned char",
|
|
47
75
|
keysize: 4,
|
|
@@ -66,11 +94,12 @@ module Vivarium
|
|
|
66
94
|
end
|
|
67
95
|
|
|
68
96
|
def register_pid(pid)
|
|
69
|
-
@
|
|
97
|
+
@config_root_targets[pid] = 1
|
|
70
98
|
end
|
|
71
99
|
|
|
72
100
|
def unregister_pid(pid)
|
|
73
|
-
@
|
|
101
|
+
@config_root_targets.delete(pid)
|
|
102
|
+
@config_spawned_targets.clear
|
|
74
103
|
rescue KeyError
|
|
75
104
|
nil
|
|
76
105
|
end
|
|
@@ -115,26 +144,63 @@ module Vivarium
|
|
|
115
144
|
struct event_t {
|
|
116
145
|
u32 pid;
|
|
117
146
|
char event_name[16];
|
|
118
|
-
char payload[
|
|
147
|
+
char payload[#{EVENT_PAYLOAD_SIZE}];
|
|
119
148
|
};
|
|
120
149
|
|
|
121
|
-
BPF_HASH(
|
|
122
|
-
|
|
150
|
+
BPF_HASH(config_root_targets, u32, u8, 1024);
|
|
151
|
+
BPF_HASH(config_spawned_targets, u32, u8, 8192);
|
|
152
|
+
BPF_ARRAY(event_invoked, struct event_t, 1024);
|
|
123
153
|
BPF_ARRAY(event_write_pos, u32, 1);
|
|
124
154
|
|
|
125
|
-
static __always_inline int target_enabled(u32 pid)
|
|
155
|
+
static __always_inline int target_enabled(u32 pid, u32 tid)
|
|
126
156
|
{
|
|
127
|
-
|
|
128
|
-
if (
|
|
157
|
+
u8 *enabled_root = config_root_targets.lookup(&pid);
|
|
158
|
+
if (enabled_root && *enabled_root == 1) {
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
u8 *enabled_spawned = config_spawned_targets.lookup(&tid);
|
|
163
|
+
if (enabled_spawned && *enabled_spawned == 1) {
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
TRACEPOINT_PROBE(sched, sched_process_fork)
|
|
171
|
+
{
|
|
172
|
+
u32 parent = args->parent_pid;
|
|
173
|
+
u32 child = args->child_pid;
|
|
174
|
+
u8 one = 1;
|
|
175
|
+
|
|
176
|
+
u8 *enabled_root = config_root_targets.lookup(&parent);
|
|
177
|
+
if (enabled_root && *enabled_root == 1) {
|
|
178
|
+
config_spawned_targets.update(&child, &one);
|
|
129
179
|
return 0;
|
|
130
180
|
}
|
|
131
|
-
|
|
181
|
+
|
|
182
|
+
u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
|
|
183
|
+
if (enabled_spawned && *enabled_spawned == 1) {
|
|
184
|
+
config_spawned_targets.update(&child, &one);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
TRACEPOINT_PROBE(sched, sched_process_exit)
|
|
191
|
+
{
|
|
192
|
+
u32 tid = (u32)bpf_get_current_pid_tgid();
|
|
193
|
+
config_spawned_targets.delete(&tid);
|
|
194
|
+
return 0;
|
|
132
195
|
}
|
|
133
196
|
|
|
134
197
|
LSM_PROBE(file_open, struct file *file)
|
|
135
198
|
{
|
|
136
|
-
|
|
137
|
-
|
|
199
|
+
u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
200
|
+
u32 pid = pid_tgid >> 32;
|
|
201
|
+
u32 tid = (u32)pid_tgid;
|
|
202
|
+
bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
|
|
203
|
+
if (!target_enabled(pid, tid)) {
|
|
138
204
|
return 0;
|
|
139
205
|
}
|
|
140
206
|
|
|
@@ -144,7 +210,7 @@ module Vivarium
|
|
|
144
210
|
return 0;
|
|
145
211
|
}
|
|
146
212
|
|
|
147
|
-
u32 idx = *write_pos &
|
|
213
|
+
u32 idx = *write_pos & 1023;
|
|
148
214
|
__sync_fetch_and_add(write_pos, 1);
|
|
149
215
|
struct event_t ev = {};
|
|
150
216
|
int path_ret;
|
|
@@ -153,16 +219,20 @@ module Vivarium
|
|
|
153
219
|
|
|
154
220
|
path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
|
|
155
221
|
if (path_ret < 0) {
|
|
156
|
-
|
|
222
|
+
if (ev.payload[0] == 0) {
|
|
223
|
+
__builtin_memcpy(ev.payload, "<path_error>", 13);
|
|
224
|
+
bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
|
|
225
|
+
}
|
|
157
226
|
}
|
|
158
227
|
|
|
228
|
+
bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
|
|
159
229
|
event_invoked.update(&idx, &ev);
|
|
160
230
|
|
|
161
231
|
return 0;
|
|
162
232
|
}
|
|
163
233
|
CLANG
|
|
164
234
|
|
|
165
|
-
def initialize(pin_dir:
|
|
235
|
+
def initialize(pin_dir: Vivarium.bpf_pin_dir)
|
|
166
236
|
@pin_dir = pin_dir
|
|
167
237
|
end
|
|
168
238
|
|
|
@@ -174,27 +244,37 @@ module Vivarium
|
|
|
174
244
|
program = BPF_PROGRAM_TEMPLATE.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
|
|
175
245
|
|
|
176
246
|
bpf = RbBCC::BCC.new(text: program)
|
|
247
|
+
kprint_thread = start_kprint_logger(bpf)
|
|
177
248
|
|
|
178
|
-
|
|
249
|
+
config_root_targets = bpf["config_root_targets"]
|
|
250
|
+
config_spawned_targets = bpf["config_spawned_targets"]
|
|
179
251
|
event_invoked = bpf["event_invoked"]
|
|
180
252
|
event_write_pos = bpf["event_write_pos"]
|
|
181
253
|
|
|
182
254
|
clear_event_slots(event_invoked)
|
|
183
255
|
event_write_pos[0] = 0
|
|
256
|
+
config_spawned_targets.clear
|
|
184
257
|
|
|
185
|
-
pin_map(
|
|
258
|
+
pin_map(config_root_targets, File.join(@pin_dir, "config_root_targets"))
|
|
259
|
+
pin_map(config_spawned_targets, File.join(@pin_dir, "config_spawned_targets"))
|
|
186
260
|
pin_map(event_invoked, File.join(@pin_dir, "event_invoked"))
|
|
187
261
|
pin_map(event_write_pos, File.join(@pin_dir, "event_write_pos"))
|
|
188
262
|
|
|
189
263
|
puts "[vivariumd] started"
|
|
190
264
|
puts "[vivariumd] pinned maps in #{@pin_dir}"
|
|
191
265
|
puts "[vivariumd] watching LSM file_open (f_path offset=#{f_path_offset})"
|
|
266
|
+
puts "[vivariumd] kprint logger enabled"
|
|
192
267
|
|
|
193
268
|
loop do
|
|
194
269
|
sleep 1
|
|
195
270
|
end
|
|
196
271
|
rescue Interrupt
|
|
197
272
|
puts "\n[vivariumd] stopping"
|
|
273
|
+
ensure
|
|
274
|
+
if kprint_thread
|
|
275
|
+
kprint_thread.kill
|
|
276
|
+
kprint_thread.join(0.2)
|
|
277
|
+
end
|
|
198
278
|
end
|
|
199
279
|
|
|
200
280
|
private
|
|
@@ -218,6 +298,26 @@ module Vivarium
|
|
|
218
298
|
end
|
|
219
299
|
end
|
|
220
300
|
|
|
301
|
+
def start_kprint_logger(bpf)
|
|
302
|
+
Thread.new do
|
|
303
|
+
begin
|
|
304
|
+
bpf.trace_fields do |_task, pid, _cpu, _flags, ts, msg|
|
|
305
|
+
line = msg.to_s.strip
|
|
306
|
+
next unless line.start_with?("vivarium:")
|
|
307
|
+
|
|
308
|
+
puts "[vivariumd:kprint #{ts} pid=#{pid}] #{line}"
|
|
309
|
+
end
|
|
310
|
+
rescue IOError, Errno::EINTR
|
|
311
|
+
nil
|
|
312
|
+
rescue StandardError => e
|
|
313
|
+
warn "[vivariumd] kprint stream stopped: #{e.class}: #{e.message}"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
rescue StandardError => e
|
|
317
|
+
warn "[vivariumd] failed to start kprint logger: #{e.class}: #{e.message}"
|
|
318
|
+
nil
|
|
319
|
+
end
|
|
320
|
+
|
|
221
321
|
def detect_f_path_offset
|
|
222
322
|
env_offset = ENV["VIVARIUM_FILE_F_PATH_OFFSET"]
|
|
223
323
|
return Integer(env_offset, 10) if env_offset
|
|
@@ -279,36 +379,80 @@ module Vivarium
|
|
|
279
379
|
end
|
|
280
380
|
end
|
|
281
381
|
|
|
282
|
-
|
|
283
|
-
|
|
382
|
+
class ObservationSession
|
|
383
|
+
def initialize(store:, pid:, tracer:)
|
|
384
|
+
@store = store
|
|
385
|
+
@pid = pid
|
|
386
|
+
@tracer = tracer
|
|
387
|
+
@stopped = false
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def stop
|
|
391
|
+
return if @stopped
|
|
392
|
+
|
|
393
|
+
@tracer.disable
|
|
394
|
+
@store.unregister_pid(@pid)
|
|
395
|
+
@stopped = true
|
|
396
|
+
end
|
|
397
|
+
end
|
|
284
398
|
|
|
399
|
+
def self.observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
|
|
400
|
+
return scoped_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format) { yield } if block_given?
|
|
401
|
+
|
|
402
|
+
top_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def self.top_observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
|
|
406
|
+
logger ||= Logger.new(dest: dest, format: format)
|
|
285
407
|
store = MapStore.new(pin_dir: pin_dir)
|
|
286
408
|
pid = Process.pid
|
|
287
409
|
store.register_pid(pid)
|
|
410
|
+
logger.info("top-level observing with pid=#{pid}")
|
|
288
411
|
|
|
289
|
-
tracer =
|
|
290
|
-
|
|
291
|
-
next if events.empty?
|
|
412
|
+
tracer = build_observe_tracepoint(store, logger)
|
|
413
|
+
tracer.enable
|
|
292
414
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
415
|
+
session = ObservationSession.new(store: store, pid: pid, tracer: tracer)
|
|
416
|
+
at_exit { session.stop }
|
|
417
|
+
session
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def self.scoped_observe(pin_dir:, logger:, dest:, format:)
|
|
421
|
+
logger ||= Logger.new(dest: dest, format: format)
|
|
422
|
+
store = MapStore.new(pin_dir: pin_dir)
|
|
423
|
+
pid = Process.pid
|
|
424
|
+
store.register_pid(pid)
|
|
425
|
+
logger.info("scoped observing with pid=#{pid}")
|
|
302
426
|
|
|
427
|
+
tracer = build_observe_tracepoint(store, logger)
|
|
303
428
|
tracer.enable
|
|
429
|
+
|
|
304
430
|
yield
|
|
305
431
|
ensure
|
|
306
432
|
tracer&.disable
|
|
307
433
|
store&.unregister_pid(pid)
|
|
308
434
|
end
|
|
309
435
|
|
|
436
|
+
def self.build_observe_tracepoint(store, logger)
|
|
437
|
+
TracePoint.new(:return, :c_return) do |tp|
|
|
438
|
+
events = store.drain_events
|
|
439
|
+
next if events.empty?
|
|
440
|
+
|
|
441
|
+
stack = caller_locations(2, 16)
|
|
442
|
+
stack = stack.reject { |loc| loc.path.to_s.include?("vivarium") } if filter_internal_frames?
|
|
443
|
+
logger.log(events, tp, stack)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def self.filter_internal_frames?
|
|
448
|
+
value = ENV["VIVARIUM_FILTER_INTERNAL_FRAMES"]
|
|
449
|
+
return true if value.nil?
|
|
450
|
+
|
|
451
|
+
!%w[0 false off no].include?(value.strip.downcase)
|
|
452
|
+
end
|
|
453
|
+
|
|
310
454
|
def self.run_daemon!(argv = ARGV)
|
|
311
|
-
options = { pin_dir:
|
|
455
|
+
options = { pin_dir: bpf_pin_dir }
|
|
312
456
|
OptionParser.new do |opts|
|
|
313
457
|
opts.banner = "Usage: vivariumd [--pin-dir PATH]"
|
|
314
458
|
opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
|
data/sig/vivarium.rbs
CHANGED
|
@@ -21,12 +21,18 @@ module Vivarium
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
class Daemon
|
|
24
|
-
|
|
24
|
+
BPF_PROGRAM_TEMPLATE: String
|
|
25
25
|
def initialize: (?pin_dir: String pin_dir) -> void
|
|
26
26
|
def run: () -> void
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
class ObservationSession
|
|
30
|
+
def stop: () -> void
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
PIN_DIR: String
|
|
34
|
+
CONFIG_ROOT_TARGETS_PIN: String
|
|
35
|
+
CONFIG_SPAWNED_TARGETS_PIN: String
|
|
30
36
|
CONFIG_TARGETS_PIN: String
|
|
31
37
|
EVENT_INVOKED_PIN: String
|
|
32
38
|
EVENT_WRITE_POS_PIN: String
|
|
@@ -36,6 +42,10 @@ module Vivarium
|
|
|
36
42
|
EVENT_STRUCT_SIZE: Integer
|
|
37
43
|
EVENT_CAPACITY: Integer
|
|
38
44
|
|
|
39
|
-
def self.
|
|
45
|
+
def self.bpf_pin_dir: () -> String
|
|
46
|
+
def self.bpf_pin_dir=: (String dir) -> String
|
|
47
|
+
def self.observe: (?pin_dir: String pin_dir, ?logger: untyped logger, ?dest: untyped dest, ?format: Symbol format) { () -> untyped } -> untyped
|
|
48
|
+
def self.top_observe: (?pin_dir: String pin_dir, ?logger: untyped logger, ?dest: untyped dest, ?format: Symbol format) -> ObservationSession
|
|
49
|
+
def self.filter_internal_frames?: () -> bool
|
|
40
50
|
def self.run_daemon!: (?Array[String] argv) -> void
|
|
41
51
|
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.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uchio Kondo
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.11.
|
|
18
|
+
version: 0.11.3
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.11.
|
|
25
|
+
version: 0.11.3
|
|
26
26
|
description: Vivarium visualizes low-level events such as file open paths and relates
|
|
27
27
|
them to Ruby method boundaries by combining RbBCC (eBPF LSM) and TracePoint.
|
|
28
28
|
email:
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- Rakefile
|
|
37
37
|
- exe/vivariumd
|
|
38
38
|
- lib/vivarium.rb
|
|
39
|
+
- lib/vivarium/logger.rb
|
|
39
40
|
- lib/vivarium/version.rb
|
|
40
41
|
- sig/vivarium.rbs
|
|
41
42
|
homepage: https://github.com/udzura/vivarium
|