Skip to content

Commit ebcc9bc

Browse files
authored
Go Dynamic Instrumentation: capture expressions, line probes and snapshots (#6592)
1 parent f3bcc49 commit ebcc9bc

12 files changed

Lines changed: 553 additions & 63 deletions

File tree

manifests/golang.yml

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,9 @@ manifest:
775775
tests/auto_inject/test_auto_inject_install.py::TestContainerAutoInjectInstallScriptAppsec: v2.0.0
776776
tests/auto_inject/test_auto_inject_install.py::TestHostAutoInjectInstallScriptAppsec: v2.0.0
777777
tests/auto_inject/test_auto_inject_install.py::TestSimpleInstallerAutoInjectManualAppsec: v2.0.0
778-
tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Line_Capture_Expressions: missing_feature (Not yet implemented)
779-
tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions: missing_feature (Not yet implemented)
778+
tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Line_Capture_Expressions: v2.2.3
779+
tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions: v2.2.3
780+
tests/debugger/test_debugger_capture_expressions.py::Test_Debugger_Method_Capture_Expressions::test_complex_capture_expressions: missing_feature (index expression not yet supported in Go system-probe)
780781
tests/debugger/test_debugger_code_origins.py::Test_Debugger_Code_Origins: missing_feature (feature not implemented)
781782
tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay: missing_feature (feature not implemented)
782783
tests/debugger/test_debugger_exception_replay.py::Test_Debugger_Exception_Replay::test_exception_replay_firsthit: missing_feature (Implemented only for dotnet)
@@ -791,12 +792,18 @@ manifest:
791792
tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction_Excluded_Identifiers: missing_feature (feature not implemented)
792793
tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets: missing_feature (feature not implemented)
793794
tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets::test_log_line_budgets: missing_feature (Not yet implemented)
794-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: missing_feature (feature not implemented)
795-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_process_tags_snapshot: missing_feature (Not yet implemented)
796-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_process_tags_snapshot_svc: missing_feature
797-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_span_decoration_line_snapshot: missing_feature (Not yet implemented)
798-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots_With_SCM: missing_feature (feature not implemented)
799-
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots_With_SCM::test_span_decoration_line_snapshot: missing_feature (Not yet implemented)
795+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots: v2.2.3
796+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_collection_size: missing_feature (no /debugger/snapshot/limits endpoint)
797+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_field_count: missing_feature (no /debugger/snapshot/limits endpoint)
798+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_length: missing_feature (no /debugger/snapshot/limits endpoint)
799+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_default_max_reference_depth: missing_feature (no /debugger/snapshot/limits endpoint)
800+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_log_line_snapshot_debug_track: missing_feature (Go system-probe does not route to debugger track endpoint)
801+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_log_line_snapshot_new_destination: missing_feature (Go system-probe does not route to debugger/v2/input endpoint)
802+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_process_tags_snapshot: missing_feature (Go system-probe does not emit process_tags)
803+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_process_tags_snapshot_svc: missing_feature (Go system-probe does not emit process_tags)
804+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots::test_span_decoration_line_snapshot: missing_feature (no span-decoration endpoint)
805+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots_With_SCM: v2.2.3
806+
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Line_Probe_Snaphots_With_SCM::test_span_decoration_line_snapshot: missing_feature (no span-decoration endpoint)
800807
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Method_Probe_Snaphots: v2.2.3
801808
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Method_Probe_Snaphots::test_mix_snapshot: missing_feature (Not yet implemented)
802809
tests/debugger/test_debugger_probe_snapshot.py::Test_Debugger_Method_Probe_Snaphots::test_span_decoration_method_snapshot: missing_feature (Not yet implemented)

tests/debugger/utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def method_and_language_to_line_number(self, method: str, language: str) -> list
154154
"""method_and_language_to_line_number returns the respective line number given the method and language"""
155155
definitions: dict[str, dict[str, list[int]]] = {
156156
"Budgets": {"java": [138], "dotnet": [136], "python": [142]},
157-
"Expression": {"java": [71], "dotnet": [74], "python": [72], "ruby": [82], "nodejs": [82]},
157+
"Expression": {"java": [71], "dotnet": [74], "python": [72], "ruby": [82], "nodejs": [82], "golang": [71]},
158158
# The `@exception` variable is not available in the context of line probes.
159159
"ExpressionException": {},
160160
"ExpressionOperators": {"java": [82], "dotnet": [90], "python": [87], "ruby": [102], "nodejs": [90]},
@@ -242,6 +242,11 @@ def __get_probe_type(probe_id: str):
242242
source_file = "debugger/index.ts"
243243
else:
244244
source_file = "debugger/index.js"
245+
elif language == "golang":
246+
variant = context.weblog_variant or "net-http"
247+
# Some variants share a build directory (e.g. uds-echo builds from echo/)
248+
go_build_dir = {"uds-echo": "echo"}.get(variant, variant)
249+
source_file = f"{go_build_dir}/debugger.go"
245250
elif language == "php":
246251
source_file = "debugger.php"
247252

@@ -254,6 +259,18 @@ def __get_probe_type(probe_id: str):
254259

255260
probe["where"]["sourceFile"] = source_file
256261

262+
# Go system-probe requires methodName for line probes to identify the function.
263+
# Other languages resolve this from sourceFile+line, but the eBPF-based
264+
# system-probe needs the fully qualified method name explicitly.
265+
if language == "golang" and probe["where"].get("lines"):
266+
golang_line_to_method = {
267+
"20": "main.(*DebuggerController).logProbe",
268+
"71": "main.(*DebuggerController).expression",
269+
}
270+
line = probe["where"]["lines"][0]
271+
if line in golang_line_to_method:
272+
probe["where"]["methodName"] = golang_line_to_method[line]
273+
257274
probe["type"] = __get_probe_type(probe["id"])
258275

259276
return probes
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"runtime"
7+
)
8+
9+
// The `debugger` feature allows attachment to specific lines of code.
10+
// Due to differences in line numbering between libraries,
11+
// 'dummy lines' are used to standardize this functionality.
12+
// Dummy line
13+
14+
type DebuggerController struct{}
15+
16+
// Dummy line
17+
// Dummy line
18+
func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) {
19+
// Dummy line
20+
w.Write([]byte("Log probe"))
21+
}
22+
23+
// Dummy line
24+
// Dummy line
25+
func (d *DebuggerController) mixProbe(w http.ResponseWriter, r *http.Request) {
26+
w.Write([]byte("Mix probe"))
27+
}
28+
29+
// Dummy line
30+
// Dummy line
31+
// Dummy line
32+
// Dummy line
33+
// Dummy line
34+
// Dummy line
35+
// Dummy line
36+
// Dummy line
37+
// Dummy line
38+
// Dummy line
39+
// Dummy line
40+
// Dummy line
41+
// Dummy line
42+
// Dummy line
43+
// Dummy line
44+
// Dummy line
45+
// Dummy line
46+
// Dummy line
47+
// Dummy line
48+
// Dummy line
49+
// Dummy line
50+
// Dummy line
51+
// Dummy line
52+
// Dummy line
53+
// Dummy line
54+
// Dummy line
55+
// Dummy line
56+
// Dummy line
57+
// Dummy line
58+
// Dummy line
59+
// Dummy line
60+
// Dummy line
61+
// Dummy line
62+
// Dummy line
63+
// Dummy line
64+
// Dummy line
65+
// Dummy line
66+
// Dummy line
67+
func (d *DebuggerController) expression(w http.ResponseWriter, r *http.Request) {
68+
inputValue := r.URL.Query().Get("inputValue")
69+
localValue := intLen(inputValue)
70+
testStruct := newExpressionTestStruct()
71+
expressionWrite(w, localValue, testStruct, inputValue)
72+
runtime.KeepAlive(testStruct)
73+
}
74+
75+
type ExpressionTestStruct struct {
76+
IntValue int
77+
DoubleValue float64
78+
StringValue string
79+
BoolValue bool
80+
Collection []string
81+
Dictionary map[string]int
82+
}
83+
84+
func newExpressionTestStruct() ExpressionTestStruct {
85+
return ExpressionTestStruct{
86+
IntValue: 1,
87+
DoubleValue: 1.1,
88+
StringValue: "one",
89+
BoolValue: true,
90+
Collection: []string{"one", "two", "three"},
91+
Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4},
92+
}
93+
}
94+
95+
//go:noinline
96+
func intLen(s string) int {
97+
return len(s)
98+
}
99+
100+
//go:noinline
101+
func expressionWrite(w http.ResponseWriter, localValue int, testStruct ExpressionTestStruct, inputValue string) {
102+
w.Write([]byte(fmt.Sprintf("Great success number %d %s %s", localValue, testStruct.StringValue, inputValue)))
103+
}

utils/build/docker/golang/app/chi/main.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ func main() {
407407
var d DebuggerController
408408
mux.HandleFunc("/debugger/log", d.logProbe)
409409
mux.HandleFunc("/debugger/mix", d.mixProbe)
410+
mux.HandleFunc("/debugger/expression", d.expression)
410411

411412
srv := &http.Server{
412413
Addr: ":7777",
@@ -439,13 +440,3 @@ func headers(w http.ResponseWriter, r *http.Request) {
439440
w.Header().Set("Content-Language", "en-US")
440441
w.Write([]byte("Hello, headers!"))
441442
}
442-
443-
type DebuggerController struct{}
444-
445-
func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) {
446-
w.Write([]byte("Log probe"))
447-
}
448-
449-
func (d *DebuggerController) mixProbe(w http.ResponseWriter, r *http.Request) {
450-
w.Write([]byte("Mix probe"))
451-
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"runtime"
7+
)
8+
9+
// The `debugger` feature allows attachment to specific lines of code.
10+
// Due to differences in line numbering between libraries,
11+
// 'dummy lines' are used to standardize this functionality.
12+
// Dummy line
13+
14+
type DebuggerController struct{}
15+
16+
// Dummy line
17+
// Dummy line
18+
func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) {
19+
// Dummy line
20+
w.Write([]byte("Log probe"))
21+
}
22+
23+
// Dummy line
24+
// Dummy line
25+
func (d *DebuggerController) mixProbe(w http.ResponseWriter, r *http.Request) {
26+
w.Write([]byte("Mix probe"))
27+
}
28+
29+
// Dummy line
30+
// Dummy line
31+
// Dummy line
32+
// Dummy line
33+
// Dummy line
34+
// Dummy line
35+
// Dummy line
36+
// Dummy line
37+
// Dummy line
38+
// Dummy line
39+
// Dummy line
40+
// Dummy line
41+
// Dummy line
42+
// Dummy line
43+
// Dummy line
44+
// Dummy line
45+
// Dummy line
46+
// Dummy line
47+
// Dummy line
48+
// Dummy line
49+
// Dummy line
50+
// Dummy line
51+
// Dummy line
52+
// Dummy line
53+
// Dummy line
54+
// Dummy line
55+
// Dummy line
56+
// Dummy line
57+
// Dummy line
58+
// Dummy line
59+
// Dummy line
60+
// Dummy line
61+
// Dummy line
62+
// Dummy line
63+
// Dummy line
64+
// Dummy line
65+
// Dummy line
66+
// Dummy line
67+
func (d *DebuggerController) expression(w http.ResponseWriter, r *http.Request) {
68+
inputValue := r.URL.Query().Get("inputValue")
69+
localValue := intLen(inputValue)
70+
testStruct := newExpressionTestStruct()
71+
expressionWrite(w, localValue, testStruct, inputValue)
72+
runtime.KeepAlive(testStruct)
73+
}
74+
75+
type ExpressionTestStruct struct {
76+
IntValue int
77+
DoubleValue float64
78+
StringValue string
79+
BoolValue bool
80+
Collection []string
81+
Dictionary map[string]int
82+
}
83+
84+
func newExpressionTestStruct() ExpressionTestStruct {
85+
return ExpressionTestStruct{
86+
IntValue: 1,
87+
DoubleValue: 1.1,
88+
StringValue: "one",
89+
BoolValue: true,
90+
Collection: []string{"one", "two", "three"},
91+
Dictionary: map[string]int{"one": 1, "two": 2, "three": 3, "four": 4},
92+
}
93+
}
94+
95+
//go:noinline
96+
func intLen(s string) int {
97+
return len(s)
98+
}
99+
100+
//go:noinline
101+
func expressionWrite(w http.ResponseWriter, localValue int, testStruct ExpressionTestStruct, inputValue string) {
102+
w.Write([]byte(fmt.Sprintf("Great success number %d %s %s", localValue, testStruct.StringValue, inputValue)))
103+
}

utils/build/docker/golang/app/echo/main.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ func main() {
380380
var d DebuggerController
381381
r.Any("/debugger/log", echoHandleFunc(d.logProbe))
382382
r.Any("/debugger/mix", echoHandleFunc(d.mixProbe))
383+
r.Any("/debugger/expression", echoHandleFunc(d.expression))
383384

384385
common.InitDatadog()
385386
go grpc.ListenAndServe()
@@ -425,13 +426,3 @@ func waf(c echo.Context) error {
425426
}
426427
return c.String(http.StatusOK, "Hello, WAF!\n")
427428
}
428-
429-
type DebuggerController struct{}
430-
431-
func (d *DebuggerController) logProbe(w http.ResponseWriter, r *http.Request) {
432-
w.Write([]byte("Log probe"))
433-
}
434-
435-
func (d *DebuggerController) mixProbe(w http.ResponseWriter, r *http.Request) {
436-
w.Write([]byte("Mix probe"))
437-
}

0 commit comments

Comments
 (0)