vivarium 0.4.1 → 0.4.2
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 +8 -0
- data/examples/box_demo.rb +59 -0
- data/lib/vivarium/box.rb +136 -0
- data/lib/vivarium/version.rb +1 -1
- data/lib/vivarium.rb +9 -6
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0a0f8b6dfc29af71d39ff5fe1ebceca9040cf62a2c6147dba5c2a074b392cdc1
|
|
4
|
+
data.tar.gz: c9f10129e5b42fd51653dcda0a02ed0e7be8b4c2ad7daef4f48a9fa5c03dcf41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9530187d826e63976bd7e1c86872345f5cad281608b2ee06f0dbe20e6211455d8d050a18013822f6baecf4c0af18e9e01b58fec7fe13174d9631181df8660569
|
|
7
|
+
data.tar.gz: c3ee802cf168d5ca917c6d9de5cce25c9441ba46a35995bc5e5f78ce6c78055c1a4d8141bff7dd4e7763205d65c1a41344a789185dbea8fc65f4589ef8e90586
|
data/README.md
CHANGED
|
@@ -155,6 +155,14 @@ bundle exec ruby examples/env_access_external_demo.rb
|
|
|
155
155
|
|
|
156
156
|
This demo spawns an external process that directly calls libc `getenv`, `setenv`, `unsetenv`, `putenv`, and `clearenv`, intended to trigger `env_caccess` eBPF events.
|
|
157
157
|
|
|
158
|
+
10) Box context demo:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bundle exec ruby examples/box_demo.rb
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This demo shows how to use `Vivarium::Box` to isolate Ruby code evaluation and trace method calls within that isolated context.
|
|
165
|
+
|
|
158
166
|
You can also start top-level observation without a block (it keeps observing until process exit):
|
|
159
167
|
|
|
160
168
|
```ruby
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "vivarium"
|
|
5
|
+
|
|
6
|
+
# Usage:
|
|
7
|
+
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
8
|
+
# 2) Run this script: bundle exec ruby examples/box_demo.rb
|
|
9
|
+
#
|
|
10
|
+
# This demo demonstrates Vivarium::Box usage for automatically tracing
|
|
11
|
+
# method calls within an isolated eval context.
|
|
12
|
+
|
|
13
|
+
FILTER = {
|
|
14
|
+
include_events: %w[span_start span_stop]
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
# Create a box and define a class within it
|
|
18
|
+
box = Vivarium::Box.new
|
|
19
|
+
|
|
20
|
+
box.eval(<<~RUBY)
|
|
21
|
+
require "net/http"
|
|
22
|
+
class Calculator
|
|
23
|
+
def add(a, b)
|
|
24
|
+
a + b
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def multiply(a, b)
|
|
28
|
+
a * b
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Greeter
|
|
33
|
+
def greet(name)
|
|
34
|
+
system "echo Hello \#{name}!"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
system "ping -c 1 example.com"
|
|
39
|
+
RUBY
|
|
40
|
+
|
|
41
|
+
box.done_load!
|
|
42
|
+
|
|
43
|
+
# Enable observation - all Box method calls will be automatically traced
|
|
44
|
+
puts "[box-demo] calling box methods with automatic tracing"
|
|
45
|
+
|
|
46
|
+
# Access classes defined in the box through const_missing
|
|
47
|
+
calc = box::Calculator.new
|
|
48
|
+
result1 = calc.add(10, 20)
|
|
49
|
+
puts "[box-demo] calc.add(10, 20) = #{result1}"
|
|
50
|
+
|
|
51
|
+
result2 = calc.multiply(3, 4)
|
|
52
|
+
puts "[box-demo] calc.multiply(3, 4) = #{result2}"
|
|
53
|
+
|
|
54
|
+
greeter = box::Greeter.new
|
|
55
|
+
greeting = greeter.greet("World")
|
|
56
|
+
puts "[box-demo] greeter.greet('World') = #{greeting}"
|
|
57
|
+
|
|
58
|
+
puts "[box-demo] done"
|
|
59
|
+
|
data/lib/vivarium/box.rb
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Vivarium
|
|
6
|
+
# Box provides an isolated execution context where method calls are automatically traced
|
|
7
|
+
# through Vivarium's observation system.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# box = Vivarium::Box.new
|
|
11
|
+
# box.eval('class MyClass; def foo; "result"; end; end')
|
|
12
|
+
# result = box::MyClass.new.foo # automatically traced if Vivarium.observe is active
|
|
13
|
+
#
|
|
14
|
+
class Box < Module
|
|
15
|
+
DEFAULT_FILTER = {
|
|
16
|
+
include_events: %w[
|
|
17
|
+
proc_fork proc_exec span_start span_stop
|
|
18
|
+
sock_connect dns_req odd_socket
|
|
19
|
+
ssl_write
|
|
20
|
+
dlopen mmap_exec
|
|
21
|
+
task_kill
|
|
22
|
+
setid_change capable_check bprm_creds
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def initialize(pin_dir: Vivarium.bpf_pin_dir, dest: $stdout, filter: DEFAULT_FILTER)
|
|
27
|
+
super()
|
|
28
|
+
@inner_box = Ruby::Box.new
|
|
29
|
+
@pin_dir = pin_dir
|
|
30
|
+
@dest = dest
|
|
31
|
+
@filter = filter
|
|
32
|
+
@session = nil
|
|
33
|
+
|
|
34
|
+
@tracing_level = [0]
|
|
35
|
+
# Set up TracePoint to automatically trace method calls within this box
|
|
36
|
+
@tracer = TracePoint.new(:call, :return) do |tp|
|
|
37
|
+
handle_trace_event(tp, @tracing_level, @inner_box)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
attr_reader :inner_box, :tracer
|
|
41
|
+
|
|
42
|
+
# Evaluate code within the box context
|
|
43
|
+
def eval(code)
|
|
44
|
+
result = nil
|
|
45
|
+
Vivarium.observe(filter: @filter) do
|
|
46
|
+
result = @inner_box.eval(code)
|
|
47
|
+
end
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Require a file within the box context
|
|
52
|
+
# Automatically traced if Vivarium.observe is active
|
|
53
|
+
def require(path)
|
|
54
|
+
result = nil
|
|
55
|
+
Vivarium.observe(filter: @filter) do
|
|
56
|
+
result = @inner_box.require(path)
|
|
57
|
+
end
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Require a file relative to the current file within the box context
|
|
62
|
+
# Automatically traced if Vivarium.observe is active
|
|
63
|
+
def require_relative(path)
|
|
64
|
+
result = nil
|
|
65
|
+
Vivarium.observe(filter: @filter) do
|
|
66
|
+
result = @inner_box.require_relative(path)
|
|
67
|
+
end
|
|
68
|
+
result
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Load a file within the box context (executed every time, unlike require)
|
|
72
|
+
# Automatically traced if Vivarium.observe is active
|
|
73
|
+
def load(path, wrap = false)
|
|
74
|
+
result = nil
|
|
75
|
+
Vivarium.observe(filter: @filter) do
|
|
76
|
+
result = @inner_box.load(path, wrap)
|
|
77
|
+
end
|
|
78
|
+
result
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Intercept constant access to resolve from the box's evaluated context
|
|
82
|
+
def const_missing(name)
|
|
83
|
+
@inner_box.const_get(name)
|
|
84
|
+
rescue NameError => e
|
|
85
|
+
raise NameError, "#{name} not found in box: #{e.message}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def done_load!
|
|
89
|
+
@tracer.enable
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def handle_trace_event(tp, tracing_level, target_box)
|
|
95
|
+
begin
|
|
96
|
+
if should_trace_call?(tp, target_box)
|
|
97
|
+
case tp.event
|
|
98
|
+
when :call
|
|
99
|
+
if tracing_level[0].zero?
|
|
100
|
+
start_vivarium_observation
|
|
101
|
+
end
|
|
102
|
+
tracing_level[0] += 1
|
|
103
|
+
file_arg = Vivarium.tail_fit_string(tp.path, Vivarium::SPAN_FILE_ARG_MAX)
|
|
104
|
+
root = Ruby::Box.root
|
|
105
|
+
root::Vivarium::Usdt.start("#{tp.defined_class}", tp.method_id.to_s, file: file_arg, lineno: tp.lineno)
|
|
106
|
+
when :return
|
|
107
|
+
tracing_level[0] -= 1
|
|
108
|
+
file_arg = Vivarium.tail_fit_string(tp.path, Vivarium::SPAN_FILE_ARG_MAX)
|
|
109
|
+
root = Ruby::Box.root
|
|
110
|
+
root::Vivarium::Usdt.stop("#{tp.defined_class}", tp.method_id.to_s, file: file_arg, lineno: tp.lineno)
|
|
111
|
+
if tracing_level[0].zero?
|
|
112
|
+
stop_vivarium_observation
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
rescue StandardError
|
|
117
|
+
# Silently ignore tracing errors to avoid breaking user code
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def should_trace_call?(tp, target_box)
|
|
122
|
+
tp.binding.eval("Ruby::Box.current") == target_box
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def start_vivarium_observation
|
|
126
|
+
puts "[debug] Starting Vivarium observation for Box method calls"
|
|
127
|
+
@session = Vivarium.top_observe(pin_dir: @pin_dir, dest: @dest, filter: @filter)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def stop_vivarium_observation
|
|
131
|
+
puts "[debug] Stopping Vivarium observation for Box method calls"
|
|
132
|
+
@session&.stop
|
|
133
|
+
@session = nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
data/lib/vivarium/version.rb
CHANGED
data/lib/vivarium.rb
CHANGED
|
@@ -7,6 +7,12 @@ require "optparse"
|
|
|
7
7
|
require "pathname"
|
|
8
8
|
require "rbbcc"
|
|
9
9
|
require "socket"
|
|
10
|
+
if defined?(Ruby) && defined?(Ruby::Box) && Ruby::Box.enabled?
|
|
11
|
+
Ruby::Box.root.require "vivarium_usdt"
|
|
12
|
+
else
|
|
13
|
+
require "vivarium_usdt"
|
|
14
|
+
end
|
|
15
|
+
|
|
10
16
|
require_relative "vivarium/version"
|
|
11
17
|
require_relative "vivarium/cli"
|
|
12
18
|
|
|
@@ -1694,7 +1700,6 @@ module Vivarium
|
|
|
1694
1700
|
.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
|
|
1695
1701
|
.gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
|
|
1696
1702
|
|
|
1697
|
-
require "vivarium_usdt"
|
|
1698
1703
|
usdt_so_path = ENV.fetch("VIVARIUM_USDT_SO_PATH") { Vivarium.locate_vivarium_usdt_so }
|
|
1699
1704
|
usdt = RbBCC::USDT.new(path: usdt_so_path)
|
|
1700
1705
|
usdt.enable_probe(probe: "start_probe", fn_name: "on_span_start")
|
|
@@ -1969,8 +1974,6 @@ module Vivarium
|
|
|
1969
1974
|
end
|
|
1970
1975
|
|
|
1971
1976
|
def self.top_observe(pin_dir: bpf_pin_dir, dest: $stdout, filter: nil)
|
|
1972
|
-
require "vivarium_usdt"
|
|
1973
|
-
|
|
1974
1977
|
store = MapStore.new(pin_dir: pin_dir)
|
|
1975
1978
|
pid = Process.pid
|
|
1976
1979
|
store.register_pid(pid)
|
|
@@ -1997,8 +2000,6 @@ module Vivarium
|
|
|
1997
2000
|
end
|
|
1998
2001
|
|
|
1999
2002
|
def self.scoped_observe(pin_dir:, dest:, filter: nil)
|
|
2000
|
-
require "vivarium_usdt"
|
|
2001
|
-
|
|
2002
2003
|
store = MapStore.new(pin_dir: pin_dir)
|
|
2003
2004
|
pid = Process.pid
|
|
2004
2005
|
store.register_pid(pid)
|
|
@@ -2079,7 +2080,6 @@ module Vivarium
|
|
|
2079
2080
|
end
|
|
2080
2081
|
|
|
2081
2082
|
def self.locate_vivarium_usdt_so
|
|
2082
|
-
require "vivarium_usdt/vivarium_usdt"
|
|
2083
2083
|
so = $LOADED_FEATURES.find { |p| p =~ %r{vivarium_usdt/vivarium_usdt\.(so|bundle|dylib)\z} }
|
|
2084
2084
|
raise Error, "vivarium_usdt native extension not found in $LOADED_FEATURES" unless so
|
|
2085
2085
|
|
|
@@ -2127,3 +2127,6 @@ end
|
|
|
2127
2127
|
require_relative "vivarium/correlator"
|
|
2128
2128
|
require_relative "vivarium/display_filter"
|
|
2129
2129
|
require_relative "vivarium/tree_renderer"
|
|
2130
|
+
if defined?(Ruby) && defined?(Ruby::Box) && Ruby::Box.enabled?
|
|
2131
|
+
require_relative "vivarium/box"
|
|
2132
|
+
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.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Uchio Kondo
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- CONTEXT.md
|
|
65
65
|
- README.md
|
|
66
66
|
- Rakefile
|
|
67
|
+
- examples/box_demo.rb
|
|
67
68
|
- examples/dlopen_demo.rb
|
|
68
69
|
- examples/drop_demo.rb
|
|
69
70
|
- examples/env_access_external_demo.rb
|
|
@@ -80,6 +81,7 @@ files:
|
|
|
80
81
|
- exe/vivariumd
|
|
81
82
|
- image.png
|
|
82
83
|
- lib/vivarium.rb
|
|
84
|
+
- lib/vivarium/box.rb
|
|
83
85
|
- lib/vivarium/cli.rb
|
|
84
86
|
- lib/vivarium/correlator.rb
|
|
85
87
|
- lib/vivarium/display_filter.rb
|