Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 74 additions & 5 deletions e2e-tests/tests/backtrace_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,10 @@ async fn test_hot_backtrace_compact_unwind_rows_cover_call_sites() -> anyhow::Re
Ok(())
}

// Keep verifier-heavy backtrace loads out of the same kernel and shared
// container sandbox at the same time. Pure DWARF analysis tests stay parallel.
#[tokio::test]
#[serial(backtrace_execution)]
async fn test_special_stack_and_program_counter_registers_are_printable() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -610,6 +613,7 @@ trace hot_bt_probe {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_full_unwinds_complete_user_stack() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -653,6 +657,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_unwinds_register_return_address_rule() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -698,6 +703,7 @@ trace register_ra_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_preserves_same_value_frame_pointer_rule() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -743,6 +749,7 @@ trace same_value_rbp_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_undefined_return_address_completes() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -814,6 +821,7 @@ trace undefined_ra_tail_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_preserves_undefined_frame_pointer_state() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -887,6 +895,7 @@ trace undefined_rbp_tail_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_rejects_undefined_return_address_register() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -926,6 +935,51 @@ trace undefined_rbp_ra_inline_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_accepts_available_zero_return_address_register() -> anyhow::Result<()> {
init();

let script = r#"
trace zero_rbp_tail_leaf {
print "ZERO_RBP_TAIL_STACK";
bt full;
}
"#;
let (count, stdout, stderr) = run_hot_backtrace_with_depth(script, 8).await?;
if count == 0 && stderr.contains("BPF_PROG_LOAD") {
return Ok(());
}
let block = matching_backtrace_block_with_ordered_patterns_after(
&stdout,
&stderr,
"ZERO_RBP_TAIL_STACK",
8,
"a tail-call stack whose return address uses an available zero-valued register",
&[
"#0 zero_rbp_tail_leaf",
"#1 zero_rbp_tail_level_1",
"#2 zero_rbp_tail_level_2",
"#3 zero_rbp_tail_level_3",
"#4 zero_rbp_tail_level_4",
"#5 zero_rbp_tail_boundary",
],
)?;
assert!(
block.contains("backtrace: complete, 6 frames (max 8)"),
"an available zero-valued return-address register should terminate cleanly\nBLOCK:\n{block}\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}"
);
assert!(
!block.contains("#6 ")
&& !block.contains("stopped:")
&& !block.contains("required-register-unavailable"),
"zero must not be confused with an unavailable register\nBLOCK:\n{block}"
);

Ok(())
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_runtime_unsupported_cfi_is_reported() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1029,6 +1083,7 @@ trace unsupported_cfi_tail_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_defaults_to_max_depth_128() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1060,6 +1115,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_depth_from_config_file() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1106,6 +1162,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_depth_one_stops_after_current_frame() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1135,6 +1192,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_non_power_of_two_depth_keeps_frame_slots_ordered() -> anyhow::Result<()>
{
init();
Expand Down Expand Up @@ -1177,6 +1235,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_multiple_deep_backtrace_statements_use_tail_calls() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1221,6 +1280,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_conditional_tail_call_backtrace_ignores_skipped_slots() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1284,6 +1344,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_default_bt_and_backtrace_alias_render_distinct_modes() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1331,6 +1392,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_inline_and_noinline_bt_modes_control_inline_frames() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1377,6 +1439,7 @@ trace inline_callsite_program.c:43 {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_non_pie_pid_backtrace_uses_load_bias_for_runtime_cfi_rows() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1416,6 +1479,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_cross_module_backtrace_resolves_so_and_exe_frames() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1482,7 +1546,7 @@ trace cross_module_lib_leaf {
}

#[tokio::test]
#[serial(backtrace_t_mode)]
#[serial(backtrace_execution)]
async fn test_t_mode_cross_module_backtrace_resolves_so_and_exe_frames() -> anyhow::Result<()> {
init();
if skip_if_nested_t_mode_unsupported() {
Expand Down Expand Up @@ -1549,7 +1613,7 @@ trace cross_module_lib_leaf {
}

#[tokio::test]
#[serial(backtrace_t_mode)]
#[serial(backtrace_execution)]
async fn test_t_mode_multiple_backtrace_traces_share_cross_module_cfi() -> anyhow::Result<()> {
init();
if skip_if_nested_t_mode_unsupported() {
Expand Down Expand Up @@ -1647,7 +1711,7 @@ trace cross_module_lib_leaf {
}

#[tokio::test]
#[serial(backtrace_dlopen)]
#[serial(backtrace_execution)]
async fn test_pid_backtrace_reports_frame_from_library_loaded_by_dlopen() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1731,7 +1795,7 @@ trace dlopen_main_callback {
}

#[tokio::test]
#[serial(backtrace_t_mode, backtrace_dlopen)]
#[serial(backtrace_execution)]
async fn test_t_mode_backtrace_unwinds_library_loaded_by_dlopen() -> anyhow::Result<()> {
init();
if skip_if_nested_t_mode_unsupported() {
Expand Down Expand Up @@ -1802,7 +1866,7 @@ trace dlopen_main_callback {
}

#[tokio::test]
#[serial(backtrace_t_mode, backtrace_dlopen)]
#[serial(backtrace_execution)]
async fn test_t_mode_backtrace_unwinds_shared_library_loaded_by_dlopen() -> anyhow::Result<()> {
init();
if skip_if_nested_t_mode_unsupported() {
Expand Down Expand Up @@ -1883,6 +1947,7 @@ trace dlopen_lib_leaf {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_full_renders_function_parameters() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1943,6 +2008,7 @@ async fn test_function_parameters_use_signature_lookup_path() -> anyhow::Result<
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_raw_renders_debug_metadata() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -1972,6 +2038,7 @@ trace dummy_touch {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_hot_backtrace_full_keeps_up_with_raw() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -2012,6 +2079,7 @@ trace hot_bt_probe {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_backtrace_depth_128_loads_with_tail_calls() -> anyhow::Result<()> {
init();

Expand Down Expand Up @@ -2055,6 +2123,7 @@ trace hot_bt_probe {
}

#[tokio::test]
#[serial(backtrace_execution)]
async fn test_deep_full_backtrace_stays_warm_under_event_load() -> anyhow::Result<()> {
init();

Expand Down
8 changes: 6 additions & 2 deletions e2e-tests/tests/fixtures/backtrace_hot_program/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ REGISTER_RA_OBJ ?= backtrace_register_ra.o
SAME_VALUE_RBP_OBJ ?= backtrace_same_value_rbp.o
UNDEFINED_RA_OBJ ?= backtrace_undefined_ra.o
UNDEFINED_RBP_OBJ ?= backtrace_undefined_rbp.o
ZERO_RBP_OBJ ?= backtrace_zero_rbp.o
UNSUPPORTED_CFI_OBJ ?= backtrace_unsupported_cfi.o

all: $(BINARY)

$(BINARY): $(OBJ) $(REGISTER_RA_OBJ) $(SAME_VALUE_RBP_OBJ) $(UNDEFINED_RA_OBJ) $(UNDEFINED_RBP_OBJ) $(UNSUPPORTED_CFI_OBJ)
$(BINARY): $(OBJ) $(REGISTER_RA_OBJ) $(SAME_VALUE_RBP_OBJ) $(UNDEFINED_RA_OBJ) $(UNDEFINED_RBP_OBJ) $(ZERO_RBP_OBJ) $(UNSUPPORTED_CFI_OBJ)
$(CC) $(CFLAGS) -o $@ $^

$(OBJ): backtrace_hot_program.c
Expand All @@ -29,10 +30,13 @@ $(UNDEFINED_RA_OBJ): backtrace_undefined_ra.S
$(UNDEFINED_RBP_OBJ): backtrace_undefined_rbp.S
$(CC) $(CFLAGS) -c -o $@ $<

$(ZERO_RBP_OBJ): backtrace_zero_rbp.S
$(CC) $(CFLAGS) -c -o $@ $<

$(UNSUPPORTED_CFI_OBJ): backtrace_unsupported_cfi.S
$(CC) $(CFLAGS) -c -o $@ $<

backtrace_hot_program_nopie: backtrace_hot_program_nopie.o $(REGISTER_RA_OBJ) $(SAME_VALUE_RBP_OBJ) $(UNDEFINED_RA_OBJ) $(UNDEFINED_RBP_OBJ) $(UNSUPPORTED_CFI_OBJ)
backtrace_hot_program_nopie: backtrace_hot_program_nopie.o $(REGISTER_RA_OBJ) $(SAME_VALUE_RBP_OBJ) $(UNDEFINED_RA_OBJ) $(UNDEFINED_RBP_OBJ) $(ZERO_RBP_OBJ) $(UNSUPPORTED_CFI_OBJ)
$(CC) $(BASE_CFLAGS) -no-pie -o $@ $^

backtrace_hot_program_nopie.o: backtrace_hot_program.c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,38 @@ __attribute__((noinline)) void undefined_rbp_tail_level_2(uint64_t value)

extern void undefined_rbp_tail_outer(uint64_t value);

__attribute__((noinline)) void zero_rbp_tail_leaf(uint64_t value)
{
hot_sink += value;
asm volatile("" ::: "memory");
}

__attribute__((noinline)) void zero_rbp_tail_level_1(uint64_t value)
{
zero_rbp_tail_leaf(value + 1);
asm volatile("" ::: "memory");
}

__attribute__((noinline)) void zero_rbp_tail_level_2(uint64_t value)
{
zero_rbp_tail_level_1(value + 1);
asm volatile("" ::: "memory");
}

__attribute__((noinline)) void zero_rbp_tail_level_3(uint64_t value)
{
zero_rbp_tail_level_2(value + 1);
asm volatile("" ::: "memory");
}

__attribute__((noinline)) void zero_rbp_tail_level_4(uint64_t value)
{
zero_rbp_tail_level_3(value + 1);
asm volatile("" ::: "memory");
}

extern void zero_rbp_tail_boundary(uint64_t value);

__attribute__((noinline)) void unsupported_cfi_inline_leaf(uint64_t value)
{
hot_sink += value;
Expand Down Expand Up @@ -243,6 +275,7 @@ int main(void)
undefined_rbp_inline_outer(i);
undefined_rbp_ra_inline_outer(i);
undefined_rbp_tail_outer(i);
zero_rbp_tail_boundary(i);
unsupported_cfi_inline_caller(i);
unsupported_cfi_tail_caller(i);
usleep(1000);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.text
.globl zero_rbp_tail_boundary
.type zero_rbp_tail_boundary, @function
zero_rbp_tail_boundary:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
# RBP is available even though its value is zero. The return-address rule
# uses it to terminate this synthetic stack cleanly at a tail-call boundary.
xorl %ebp, %ebp
.cfi_register %rip, %rbp
call zero_rbp_tail_level_4
.cfi_restore %rip
popq %rbp
.cfi_restore %rbp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.size zero_rbp_tail_boundary, .-zero_rbp_tail_boundary

.section .note.GNU-stack,"",@progbits
Loading
Loading