Skip to content

Commit dc8ab47

Browse files
authored
Capture the stack for log probes (#3367)
* Capture the stack for log probes This probably was an oversight in the initial implementation. Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> # Conflicts: # libdatadog * Adjust symfony web tests to not fail Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> * Adjust lineno on PHP 7 for first instruction Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com> * One more symfony web test fix --------- Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>
1 parent b574d45 commit dc8ab47

12 files changed

Lines changed: 171 additions & 19 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components-rs/live-debugger.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,19 @@ void ddog_capture_value_add_field(struct ddog_CaptureValue *value,
9494

9595
void ddog_snapshot_format_new_uuid(uint8_t (*buf)[36]);
9696

97+
void ddog_snapshot_push_stack_frame(struct ddog_DebuggerPayload *payload,
98+
ddog_CharSlice file_name,
99+
ddog_CharSlice function_name,
100+
ddog_CharSlice type_name,
101+
int64_t line_number);
102+
103+
void ddog_snapshot_push_stack_frame_with_column(struct ddog_DebuggerPayload *payload,
104+
ddog_CharSlice file_name,
105+
ddog_CharSlice function_name,
106+
ddog_CharSlice type_name,
107+
int64_t line_number,
108+
int64_t column_number);
109+
97110
ddog_CharSlice ddog_evaluation_error_first_msg(const struct ddog_Vec_SnapshotEvaluationError *vec);
98111

99112
void ddog_evaluation_error_drop(struct ddog_Vec_SnapshotEvaluationError*);

ext/code_origins.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "code_origins.h"
22
#include "configuration.h"
3+
#include "zend_generators.h"
34
#include <Zend/zend_API.h>
45

56
void ddtrace_add_code_origin_information(ddtrace_span_data *span, int skip_frames) {
@@ -9,6 +10,9 @@ void ddtrace_add_code_origin_information(ddtrace_span_data *span, int skip_frame
910
zend_long max_frames = get_DD_CODE_ORIGIN_MAX_USER_FRAMES();
1011
int current_frame = 0;
1112
while (execute_data && current_frame < max_frames) {
13+
if (UNEXPECTED(!EX(func))) {
14+
execute_data = zend_generator_check_placeholder_frame(execute_data);
15+
}
1216
if (EX(func) && ZEND_USER_CODE(EX(func)->type) && EX(func)->op_array.filename) {
1317
if (skip_frames > 0) {
1418
--skip_frames;

ext/live_debugger.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "zend_interfaces.h"
1313
#include "zend_hrtime.h"
1414
#include "components-rs/common.h"
15+
#include "zend_generators.h"
1516

1617
ZEND_EXTERN_MODULE_GLOBALS(ddtrace);
1718

@@ -428,6 +429,111 @@ static void dd_log_probe_capture_snapshot(ddog_DebuggerCapture *capture, dd_log_
428429
}
429430
}
430431

432+
static void dd_probe_capture_stack(ddog_DebuggerPayload *payload, zend_execute_data *execute_data) {
433+
int remaining_depth = 128;
434+
zend_execute_data *call = execute_data, *last_call = NULL;
435+
while (call && --remaining_depth > 0) {
436+
if (UNEXPECTED(!call->func)) {
437+
/* This is the fake frame inserted for nested generators. Normally,
438+
* this frame is preceded by the actual generator frame and then
439+
* replaced by zend_generator_check_placeholder_frame() below.
440+
* However, the frame is popped before cleaning the stack frame,
441+
* which is observable by destructors. */
442+
call = zend_generator_check_placeholder_frame(call);
443+
}
444+
445+
zend_execute_data *prev = call->prev_execute_data;
446+
ddog_CharSlice filename = DDOG_CHARSLICE_C_BARE("[internal function]");
447+
ddog_CharSlice type_name = DDOG_CHARSLICE_C_BARE("");
448+
uint32_t lineno = 0;
449+
450+
#if PHP_VERSION_ID >= 80400
451+
if (ZEND_USER_CODE(call->func->type)) {
452+
/* For frameless calls we add an additional frame for the call itself. */
453+
const zend_op *opline = call->opline;
454+
if (!ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
455+
goto not_frameless_call;
456+
}
457+
int num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
458+
/* Check if any args were already freed. Skip the frame in that case. */
459+
if (num_args >= 1) {
460+
zval *arg = zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, call);
461+
if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call;
462+
}
463+
if (num_args >= 2) {
464+
zval *arg = zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, call);
465+
if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call;
466+
}
467+
if (num_args >= 3) {
468+
const zend_op *op_data = opline + 1;
469+
zval *arg = zend_get_zval_ptr(op_data, op_data->op1_type, &op_data->op1, call);
470+
if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call;
471+
}
472+
zend_function *func = ZEND_FLF_FUNC(opline);
473+
if (last_call && last_call->func == func) {
474+
goto not_frameless_call;
475+
}
476+
477+
zend_string *name = func->common.function_name;
478+
ddog_snapshot_push_stack_frame(payload, filename, dd_zend_string_to_CharSlice(name), type_name, 0);
479+
}
480+
not_frameless_call: ;
481+
#endif
482+
483+
if (call->func && ZEND_USER_CODE(call->func->common.type)) {
484+
filename = dd_zend_string_to_CharSlice(call->func->op_array.filename);
485+
if (call->opline->opcode == ZEND_HANDLE_EXCEPTION) {
486+
if (EG(opline_before_exception)) {
487+
lineno = EG(opline_before_exception)->lineno;
488+
} else {
489+
lineno = call->func->op_array.line_end;
490+
}
491+
} else {
492+
lineno = call->opline->lineno;
493+
}
494+
}
495+
496+
ddog_CharSlice function_name;
497+
if (call->func && call->func->common.function_name) {
498+
function_name = dd_zend_string_to_CharSlice(call->func->common.function_name);
499+
500+
if (call->func->common.scope) {
501+
type_name = dd_zend_string_to_CharSlice(call->func->common.scope->name);
502+
}
503+
} else {
504+
if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type) && prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) {
505+
switch (prev->opline->extended_value) {
506+
case ZEND_EVAL:
507+
function_name = dd_zend_string_to_CharSlice(ZSTR_KNOWN(ZEND_STR_EVAL));
508+
break;
509+
case ZEND_INCLUDE:
510+
function_name = dd_zend_string_to_CharSlice(ZSTR_KNOWN(ZEND_STR_INCLUDE));
511+
break;
512+
case ZEND_REQUIRE:
513+
function_name = dd_zend_string_to_CharSlice(ZSTR_KNOWN(ZEND_STR_REQUIRE));
514+
break;
515+
case ZEND_INCLUDE_ONCE:
516+
function_name = dd_zend_string_to_CharSlice(ZSTR_KNOWN(ZEND_STR_INCLUDE_ONCE));
517+
break;
518+
case ZEND_REQUIRE_ONCE:
519+
function_name = dd_zend_string_to_CharSlice(ZSTR_KNOWN(ZEND_STR_REQUIRE_ONCE));
520+
break;
521+
default:
522+
goto next_frame;
523+
}
524+
} else {
525+
function_name = DDOG_CHARSLICE_C("");
526+
}
527+
}
528+
529+
ddog_snapshot_push_stack_frame(payload, filename, function_name, type_name, lineno);
530+
531+
next_frame: ;
532+
last_call = call;
533+
call = prev;
534+
}
535+
}
536+
431537
static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_data, zval *retval, void *auxiliary, void *dynamic) {
432538
dd_log_probe_dyn *dyn = dynamic;
433539
dd_log_probe_def *def = auxiliary;
@@ -461,8 +567,12 @@ static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_d
461567
dd_log_probe_ensure_payload(dyn, def, &result_msg);
462568

463569
if (def->parent.probe.probe.log.capture_snapshot) {
570+
bool already_snapshotted = dyn->capture_arena;
464571
DDTRACE_G(debugger_capture_arena) = dyn->capture_arena ? dyn->capture_arena : zend_arena_create(65536);
465572
ddog_DebuggerCapture *capture = ddog_snapshot_exit(dyn->payload);
573+
if (!already_snapshotted) {
574+
dd_probe_capture_stack(dyn->payload, execute_data);
575+
}
466576
dd_log_probe_capture_snapshot(capture, def, execute_data);
467577
const ddog_CaptureConfiguration *capture_config = def->parent.probe.probe.log.capture;
468578
struct ddog_CaptureValue capture_value = {0};
@@ -497,6 +607,7 @@ static bool dd_log_probe_begin(zend_ulong invocation, zend_execute_data *execute
497607
if (def->parent.probe.probe.log.capture_snapshot) {
498608
ddog_DebuggerCapture *capture = ddog_snapshot_entry(dyn->payload);
499609
DDTRACE_G(debugger_capture_arena) = zend_arena_create(65536);
610+
dd_probe_capture_stack(dyn->payload, execute_data);
500611
dd_log_probe_capture_snapshot(capture, def, execute_data);
501612
dyn->capture_arena = DDTRACE_G(debugger_capture_arena);
502613
DDTRACE_G(debugger_capture_arena) = NULL;

tests/ext/live-debugger/debugger_log_probe.phpt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ array(5) {
7272
["debugger"]=>
7373
array(1) {
7474
["snapshot"]=>
75-
array(5) {
75+
array(6) {
7676
["language"]=>
7777
string(3) "php"
7878
["id"]=>
@@ -219,6 +219,27 @@ array(5) {
219219
string(3) "Bar"
220220
}
221221
}
222+
["stack"]=>
223+
array(2) {
224+
[0]=>
225+
array(3) {
226+
["fileName"]=>
227+
string(%d) "%sdebugger_log_probe.php"
228+
["function"]=>
229+
string(8) "foo::Bar"
230+
["lineNumber"]=>
231+
int(11)
232+
}
233+
[1]=>
234+
array(3) {
235+
["fileName"]=>
236+
string(%d) "%sdebugger_log_probe.php"
237+
["function"]=>
238+
string(0) ""
239+
["lineNumber"]=>
240+
int(34)
241+
}
242+
}
222243
}
223244
}
224245
["message"]=>

tests/snapshots/tests.integrations.symfony.latest.messenger_test.test_async_failure.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@
618618
"_dd.code_origin.frames.6.method": "executeInsert",
619619
"_dd.code_origin.frames.6.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
620620
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
621-
"_dd.code_origin.frames.7.line": "129",
621+
"_dd.code_origin.frames.7.line": "132",
622622
"_dd.code_origin.frames.7.method": "send",
623623
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
624624
"_dd.code_origin.type": "exit",
@@ -673,7 +673,7 @@
673673
"_dd.code_origin.frames.6.method": "executeInsert",
674674
"_dd.code_origin.frames.6.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
675675
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
676-
"_dd.code_origin.frames.7.line": "129",
676+
"_dd.code_origin.frames.7.line": "132",
677677
"_dd.code_origin.frames.7.method": "send",
678678
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
679679
"_dd.code_origin.type": "exit",

tests/snapshots/tests.integrations.symfony.latest.messenger_test.test_async_failure_consumer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@
543543
"_dd.code_origin.frames.6.method": "update",
544544
"_dd.code_origin.frames.6.type": "Doctrine\\DBAL\\Connection",
545545
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
546-
"_dd.code_origin.frames.7.line": "275",
546+
"_dd.code_origin.frames.7.line": "278",
547547
"_dd.code_origin.frames.7.method": "reject",
548548
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
549549
"_dd.code_origin.type": "exit",
@@ -598,7 +598,7 @@
598598
"_dd.code_origin.frames.6.method": "update",
599599
"_dd.code_origin.frames.6.type": "Doctrine\\DBAL\\Connection",
600600
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
601-
"_dd.code_origin.frames.7.line": "275",
601+
"_dd.code_origin.frames.7.line": "278",
602602
"_dd.code_origin.frames.7.method": "reject",
603603
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
604604
"_dd.code_origin.type": "exit",

tests/snapshots/tests.integrations.symfony.latest.messenger_test.test_async_success.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,11 @@
614614
"_dd.code_origin.frames.5.method": "executeStatement",
615615
"_dd.code_origin.frames.5.type": "Doctrine\\DBAL\\Connection",
616616
"_dd.code_origin.frames.6.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
617-
"_dd.code_origin.frames.6.line": "468",
617+
"_dd.code_origin.frames.6.line": "471",
618618
"_dd.code_origin.frames.6.method": "executeInsert",
619619
"_dd.code_origin.frames.6.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
620620
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
621-
"_dd.code_origin.frames.7.line": "129",
621+
"_dd.code_origin.frames.7.line": "132",
622622
"_dd.code_origin.frames.7.method": "send",
623623
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
624624
"_dd.code_origin.type": "exit",
@@ -669,11 +669,11 @@
669669
"_dd.code_origin.frames.5.method": "executeStatement",
670670
"_dd.code_origin.frames.5.type": "Doctrine\\DBAL\\Connection",
671671
"_dd.code_origin.frames.6.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
672-
"_dd.code_origin.frames.6.line": "468",
672+
"_dd.code_origin.frames.6.line": "471",
673673
"_dd.code_origin.frames.6.method": "executeInsert",
674674
"_dd.code_origin.frames.6.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
675675
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
676-
"_dd.code_origin.frames.7.line": "129",
676+
"_dd.code_origin.frames.7.line": "132",
677677
"_dd.code_origin.frames.7.method": "send",
678678
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
679679
"_dd.code_origin.type": "exit",
@@ -731,7 +731,7 @@
731731
"_dd.code_origin.frames.6.method": "commit",
732732
"_dd.code_origin.frames.6.type": "Doctrine\\DBAL\\Connection",
733733
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
734-
"_dd.code_origin.frames.7.line": "468",
734+
"_dd.code_origin.frames.7.line": "471",
735735
"_dd.code_origin.frames.7.method": "executeInsert",
736736
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
737737
"_dd.code_origin.type": "exit",

tests/snapshots/tests.integrations.symfony.latest.messenger_test.test_async_success_consumer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@
321321
"_dd.code_origin.frames.6.method": "update",
322322
"_dd.code_origin.frames.6.type": "Doctrine\\DBAL\\Connection",
323323
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
324-
"_dd.code_origin.frames.7.line": "258",
324+
"_dd.code_origin.frames.7.line": "261",
325325
"_dd.code_origin.frames.7.method": "ack",
326326
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
327327
"_dd.code_origin.type": "exit",
@@ -376,7 +376,7 @@
376376
"_dd.code_origin.frames.6.method": "update",
377377
"_dd.code_origin.frames.6.type": "Doctrine\\DBAL\\Connection",
378378
"_dd.code_origin.frames.7.file": "{path}/tests/Frameworks/Symfony/Latest/vendor/symfony/doctrine-messenger/Transport/Connection.php",
379-
"_dd.code_origin.frames.7.line": "258",
379+
"_dd.code_origin.frames.7.line": "261",
380380
"_dd.code_origin.frames.7.method": "ack",
381381
"_dd.code_origin.frames.7.type": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\Connection",
382382
"_dd.code_origin.type": "exit",

0 commit comments

Comments
 (0)