Skip to content

Commit 3577a54

Browse files
authored
Fix debugger ephemerals handling (#3685)
This is now safe for nested debugger log probes as well not leaking into subsequent requests when probes are removed during runtime. Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>
1 parent ea5672d commit 3577a54

6 files changed

Lines changed: 56 additions & 47 deletions

File tree

ext/ddtrace.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,6 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) {
16151615

16161616
ddtrace_sidecar_shutdown();
16171617

1618-
ddtrace_live_debugger_mshutdown();
16191618
ddtrace_process_tags_mshutdown();
16201619

16211620
#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100

ext/ddtrace.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ typedef struct ddtrace_span_event ddtrace_span_event;
4141
typedef struct ddtrace_exception_span_event ddtrace_exception_span_event;
4242
typedef struct ddtrace_git_metadata ddtrace_git_metadata;
4343

44+
typedef struct dd_refcounted_linked dd_refcounted_linked;
45+
46+
typedef struct {
47+
zend_arena *arena;
48+
dd_refcounted_linked *ephemerals;
49+
} dd_capture_arena;
50+
4451
extern datadog_php_sapi ddtrace_active_sapi;
4552

4653
extern ddog_CharSlice php_version_rt;
@@ -147,8 +154,7 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace)
147154
ddog_AgentRemoteConfigReader *agent_config_reader;
148155
ddog_RemoteConfigState *remote_config_state;
149156
ddog_AgentInfoReader *agent_info_reader;
150-
zend_arena *debugger_capture_arena;
151-
HashTable debugger_capture_ephemerals;
157+
dd_capture_arena debugger_capture_arena;
152158
ddog_Vec_DebuggerPayload exception_debugger_buffer;
153159
HashTable active_rc_hooks;
154160
HashTable *agent_rate_by_service;

ext/exception_serialize.c

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static void ddtrace_capture_string_value(zend_string *str, struct ddog_CaptureVa
9191
if (!value->not_captured_reason.len) {
9292
value->value = (ddog_CharSlice) {.ptr = ZSTR_VAL(str), .len = ZSTR_LEN(str)};
9393
if (value->value.len > config->max_length) {
94-
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20);
94+
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, 20);
9595
int len = sprintf(integer, "%" PRIuPTR, value->value.len);
9696
value->size = (ddog_CharSlice) {.ptr = integer, .len = len};
9797
value->value.len = config->max_length;
@@ -103,7 +103,7 @@ static void ddtrace_capture_string_value(zend_string *str, struct ddog_CaptureVa
103103
static void ddtrace_capture_long_value(zend_long num, struct ddog_CaptureValue *value) {
104104
value->type = DDOG_CHARSLICE_C("int");
105105
if (!value->not_captured_reason.len) {
106-
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20);
106+
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, 20);
107107
int len = sprintf(integer, ZEND_LONG_FMT, num);
108108
value->value = (ddog_CharSlice) {.ptr = integer, .len = len};
109109
}
@@ -133,7 +133,7 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
133133
case IS_DOUBLE: {
134134
value->type = DDOG_CHARSLICE_C("float");
135135
if (!value->not_captured_reason.len) {
136-
char *num = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20);
136+
char *num = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, 20);
137137
php_gcvt(Z_DVAL_P(zv), (int) EG(precision), '.', 'E', num);
138138
value->value = (ddog_CharSlice) {.ptr = num, .len = strlen(num)};
139139
}
@@ -239,7 +239,7 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
239239
} else if (ZSTR_VAL(key)[1] == '*') { // skip \0*\0
240240
fieldname = (ddog_CharSlice) {.ptr = ZSTR_VAL(key) + 3, .len = ZSTR_LEN(key) - 3};
241241
} else {
242-
char *name = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), ZSTR_LEN(key));
242+
char *name = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, ZSTR_LEN(key));
243243
int classname_len = strlen(ZSTR_VAL(key) + 1);
244244
memcpy(name, ZSTR_VAL(key) + 1, classname_len);
245245
name[classname_len++] = ':';
@@ -254,12 +254,14 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
254254
} ZEND_HASH_FOREACH_END();
255255
if (ce->type == ZEND_INTERNAL_CLASS) {
256256
#if PHP_VERSION_ID < 70400
257-
if (is_temp) {
258-
zend_hash_next_index_insert_ptr(&DDTRACE_G(debugger_capture_ephemerals), ht);
259-
}
260-
#else
261-
zend_hash_next_index_insert_ptr(&DDTRACE_G(debugger_capture_ephemerals), ht);
257+
if (is_temp)
262258
#endif
259+
{
260+
dd_refcounted_linked *node = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, sizeof(dd_refcounted_linked));
261+
node->value = (zend_refcounted *)ht;
262+
node->next = DDTRACE_G(debugger_capture_arena).ephemerals;
263+
DDTRACE_G(debugger_capture_arena).ephemerals = node;
264+
}
263265
}
264266
break;
265267
}
@@ -281,10 +283,10 @@ void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, con
281283
#define hash_len 16
282284

283285
static ddog_DebuggerCapture *dd_create_frame_and_collect_locals(char *exception_id, char *exception_hash, int frame_num, ddog_CharSlice class_slice, ddog_CharSlice func_slice, zval *locals, zend_string *service_name, const ddog_CaptureConfiguration *capture_config, uint64_t time, ddog_SpanBytes *span) {
284-
char *snapshot_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), uuid_len);
286+
char *snapshot_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, uuid_len);
285287
ddog_snapshot_format_new_uuid((uint8_t(*)[uuid_len])snapshot_id);
286288

287-
char *msg = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 40);
289+
char *msg = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, 40);
288290
int len = sprintf(msg, "_dd.debug.error.%d.snapshot_id", frame_num);
289291
ddog_add_span_meta(span, (ddog_CharSlice){.ptr = msg, .len = len}, (ddog_CharSlice){.ptr = snapshot_id, .len = uuid_len});
290292

@@ -368,14 +370,14 @@ static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_st
368370
zend_string *key_locals = zend_string_init(ZEND_STRL("locals"), 0);
369371
zval *locals = zai_exception_read_property(exception, key_locals);
370372

371-
bool has_arena = DDTRACE_G(debugger_capture_arena);
373+
bool has_arena = DDTRACE_G(debugger_capture_arena).arena;
372374
if (!has_arena) {
373-
DDTRACE_G(debugger_capture_arena) = zend_arena_create(65536);
375+
DDTRACE_G(debugger_capture_arena) = (dd_capture_arena){.arena = zend_arena_create(65536), .ephemerals = NULL};
374376
}
375377

376378
const ddog_CaptureConfiguration capture_config = ddog_capture_defaults();
377379

378-
char *exception_hash = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), hash_len);
380+
char *exception_hash = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, hash_len);
379381
zend_ulong exception_long_hash = ddtrace_compute_exception_hash(exception);
380382
php_hash_bin2hex(exception_hash, (unsigned char *)&exception_long_hash, sizeof(exception_long_hash));
381383

@@ -387,7 +389,7 @@ static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_st
387389
goto cleanup;
388390
}
389391

390-
char *exception_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), uuid_len);
392+
char *exception_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, uuid_len);
391393
ddog_snapshot_format_new_uuid((uint8_t(*)[uuid_len])exception_id);
392394

393395
ddog_add_str_span_meta_CharSlice(span, "_dd.debug.error.exception_capture_id", (ddog_CharSlice){.ptr = exception_id, .len = uuid_len});
@@ -439,11 +441,11 @@ static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_st
439441
zend_string *name = func->op_array.arg_info[idx].name;
440442
arg_name = (ddog_CharSlice) {.ptr = ZSTR_VAL(name), .len = ZSTR_LEN(name)};
441443
} else {
442-
const char *name = func->internal_function.arg_info[idx].name;
444+
const char *name = (const char*) func->internal_function.arg_info[idx].name;
443445
arg_name = (ddog_CharSlice) {.ptr = name, .len = strlen(name)};
444446
}
445447
} else {
446-
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 23);
448+
char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena).arena, 23);
447449
int len = sprintf(integer, "arg" ZEND_LONG_FMT, idx);
448450
arg_name = (ddog_CharSlice){ .ptr = integer, .len = len };
449451
}
@@ -470,8 +472,9 @@ static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_st
470472

471473
cleanup:
472474
if (!has_arena) {
473-
zend_arena_destroy(DDTRACE_G(debugger_capture_arena));
474-
DDTRACE_G(debugger_capture_arena) = NULL;
475+
dd_free_capture_ephemerals(DDTRACE_G(debugger_capture_arena).ephemerals);
476+
zend_arena_destroy(DDTRACE_G(debugger_capture_arena).arena);
477+
DDTRACE_G(debugger_capture_arena) = (dd_capture_arena){0};
475478
}
476479

477480
zend_string_release(key_locals);

ext/live_debugger.c

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ typedef struct {
366366
bool rejected;
367367
ddog_DebuggerPayload *payload;
368368
zend_string *service;
369-
zend_arena *capture_arena;
369+
dd_capture_arena capture_arena;
370370
} dd_log_probe_dyn;
371371

372372
static bool dd_log_probe_eval_condition(dd_log_probe_def *def, zend_execute_data *execute_data, zval *retval) {
@@ -554,8 +554,9 @@ static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_d
554554
if (dyn->payload) {
555555
ddog_drop_debugger_payload(dyn->payload);
556556
zend_string_release(dyn->service);
557-
if (dyn->capture_arena) {
558-
zend_arena_destroy(dyn->capture_arena);
557+
if (dyn->capture_arena.arena) {
558+
dd_free_capture_ephemerals(dyn->capture_arena.ephemerals);
559+
zend_arena_destroy(dyn->capture_arena.arena);
559560
}
560561
}
561562
return;
@@ -574,8 +575,8 @@ static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_d
574575
dd_log_probe_ensure_payload(dyn, def, &result_msg);
575576

576577
if (def->parent.probe.probe.log.capture_snapshot) {
577-
bool already_snapshotted = dyn->capture_arena;
578-
DDTRACE_G(debugger_capture_arena) = dyn->capture_arena ? dyn->capture_arena : zend_arena_create(65536);
578+
bool already_snapshotted = dyn->capture_arena.arena;
579+
DDTRACE_G(debugger_capture_arena) = already_snapshotted ? dyn->capture_arena : (dd_capture_arena){.arena = zend_arena_create(65536), .ephemerals = NULL};
579580
ddog_DebuggerCapture *capture = ddog_snapshot_exit(dyn->payload);
580581
if (!already_snapshotted) {
581582
dd_probe_capture_stack(dyn->payload, execute_data);
@@ -587,9 +588,10 @@ static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_d
587588
ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, DDOG_CHARSLICE_C("@return"), capture_value);
588589
}
589590
ddtrace_sidecar_send_debugger_datum(dyn->payload);
590-
if (DDTRACE_G(debugger_capture_arena)) {
591-
zend_arena_destroy(DDTRACE_G(debugger_capture_arena));
592-
DDTRACE_G(debugger_capture_arena) = NULL;
591+
if (DDTRACE_G(debugger_capture_arena).arena) {
592+
dd_free_capture_ephemerals(DDTRACE_G(debugger_capture_arena).ephemerals);
593+
zend_arena_destroy(DDTRACE_G(debugger_capture_arena).arena);
594+
DDTRACE_G(debugger_capture_arena) = (dd_capture_arena){0};
593595
}
594596
zend_string_release(result);
595597
zend_string_release(dyn->service);
@@ -607,17 +609,17 @@ static bool dd_log_probe_begin(zend_ulong invocation, zend_execute_data *execute
607609

608610
dyn->payload = NULL;
609611
dyn->rejected = def->parent.probe.evaluate_at == DDOG_EVALUATE_AT_ENTRY && !dd_log_probe_eval_condition(def, execute_data, &retval);
610-
dyn->capture_arena = NULL;
612+
dyn->capture_arena = (dd_capture_arena){0};
611613

612614
if (!dyn->rejected && def->parent.probe.evaluate_at == DDOG_EVALUATE_AT_ENTRY) {
613615
dd_log_probe_ensure_payload(dyn, def, NULL);
614616
if (def->parent.probe.probe.log.capture_snapshot) {
615617
ddog_DebuggerCapture *capture = ddog_snapshot_entry(dyn->payload);
616-
DDTRACE_G(debugger_capture_arena) = zend_arena_create(65536);
618+
DDTRACE_G(debugger_capture_arena) = (dd_capture_arena){.arena = zend_arena_create(65536), .ephemerals = NULL};
617619
dd_probe_capture_stack(dyn->payload, execute_data);
618620
dd_log_probe_capture_snapshot(capture, def, execute_data);
619621
dyn->capture_arena = DDTRACE_G(debugger_capture_arena);
620-
DDTRACE_G(debugger_capture_arena) = NULL;
622+
DDTRACE_G(debugger_capture_arena) = (dd_capture_arena){0};
621623
}
622624
}
623625

@@ -1438,19 +1440,19 @@ ddog_LiveDebuggerSetup ddtrace_live_debugger_setup = {
14381440
.evaluator = &dd_evaluator,
14391441
};
14401442

1441-
static void dd_ht_ephemerals_dtor(void *pData) {
1442-
HashTable *ht = *((HashTable **)pData);
1443-
1443+
void dd_free_capture_ephemerals(dd_refcounted_linked *ephemerals) {
1444+
while (ephemerals) {
1445+
HashTable *ht = (HashTable *)ephemerals->value;
14441446
#if PHP_VERSION_ID < 70400
1445-
zend_array_release(ht);
1447+
zend_array_release(ht);
14461448
#else
1447-
zend_release_properties(ht);
1449+
zend_release_properties(ht);
14481450
#endif
1451+
ephemerals = ephemerals->next;
1452+
}
14491453
}
14501454

14511455
void ddtrace_live_debugger_minit(void) {
1452-
zend_hash_init(&DDTRACE_G(debugger_capture_ephemerals), 8, NULL, (dtor_func_t)dd_ht_ephemerals_dtor, 1);
1453-
14541456
zend_string *value;
14551457
ZEND_HASH_FOREACH_STR_KEY(get_global_DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS(), value) {
14561458
ddog_snapshot_add_redacted_name(dd_zend_string_to_CharSlice(value));
@@ -1460,10 +1462,6 @@ void ddtrace_live_debugger_minit(void) {
14601462
} ZEND_HASH_FOREACH_END();
14611463
}
14621464

1463-
void ddtrace_live_debugger_mshutdown(void) {
1464-
zend_hash_destroy(&DDTRACE_G(debugger_capture_ephemerals));
1465-
}
1466-
14671465
bool ddtrace_alter_dynamic_instrumentation_config(zval *old_value, zval *new_value, zend_string *new_str) {
14681466
UNUSED(old_value, new_str);
14691467
if (DDTRACE_G(remote_config_state) && !ddog_remote_config_alter_dynamic_config(DDTRACE_G(remote_config_state), DDOG_CHARSLICE_C("datadog.dynamic_instrumentation.enabled"), zend_string_copy(new_str))) {

ext/live_debugger.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
extern ddog_LiveDebuggerSetup ddtrace_live_debugger_setup;
88

99
void ddtrace_live_debugger_minit(void);
10-
void ddtrace_live_debugger_mshutdown(void);
1110
bool ddtrace_alter_dynamic_instrumentation_config(zval *old_value, zval *new_value, zend_string *new_str);
1211
ddog_DynamicInstrumentationConfigState ddtrace_dynamic_instrumentation_state(void);
1312

@@ -17,4 +16,10 @@ static inline void ddtrace_snapshot_redacted_name(ddog_CaptureValue *capture_val
1716
}
1817
}
1918

19+
struct dd_refcounted_linked {
20+
struct dd_refcounted_linked *next;
21+
zend_refcounted *value;
22+
};
23+
void dd_free_capture_ephemerals(struct dd_refcounted_linked *ephemerals);
24+
2025
#endif // DD_LIVE_DEBUGGER_H

ext/sidecar.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -582,13 +582,11 @@ void ddtrace_sidecar_submit_root_span_data(void) {
582582
void ddtrace_sidecar_send_debugger_data(ddog_Vec_DebuggerPayload payloads) {
583583
LOGEV(DEBUG, UNUSED(log); ddog_log_debugger_data(&payloads););
584584
ddog_sidecar_send_debugger_data(&ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), payloads);
585-
zend_hash_clean(&DDTRACE_G(debugger_capture_ephemerals));
586585
}
587586

588587
void ddtrace_sidecar_send_debugger_datum(ddog_DebuggerPayload *payload) {
589588
LOGEV(DEBUG, UNUSED(log); ddog_log_debugger_datum(payload););
590589
ddog_sidecar_send_debugger_datum(&ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), payload);
591-
zend_hash_clean(&DDTRACE_G(debugger_capture_ephemerals));
592590
}
593591

594592
void ddtrace_sidecar_activate(void) {

0 commit comments

Comments
 (0)