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
36 changes: 36 additions & 0 deletions e2e-tests/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,40 @@ lazy_static! {
pub static ref FIXTURES: TestFixtures = TestFixtures::new();
}

#[allow(dead_code)]
pub fn skip_if_nested_t_mode_unsupported() -> bool {
use std::env;

let target_mode = match env::var("E2E_TARGET_MODE") {
Ok(value) => value,
Err(env::VarError::NotPresent) => return false,
Err(err) => {
eprintln!("continuing nested -t test despite unreadable E2E_TARGET_MODE: {err}");
return false;
}
};
let nested_child_container = matches!(
target_mode.trim().to_ascii_lowercase().as_str(),
"child-container" | "child" | "nested" | "descendant"
);
if !nested_child_container {
return false;
}

// Nested child-container globals `-t` is intentionally unsupported for now.
// The current `-t` implementation relies on target-path / sysmon lifecycle
// maintenance anchored in GhostScope's current `/proc` view, while nested
// child-container targets introduce a second PID namespace below that view.
// These globals tests need a stable runtime-pid -> outer `/proc` pid mapping.
// Backtrace has separate alias setup and retains its nested coverage.
eprintln!(
"skipping nested child-container -t test: nested target-path mode is \
currently unsupported because proc offsets and runtime lifecycle \
maintenance stay anchored in the outer container /proc pid view"
);
true
}

// Re-export the shared runner for convenience in tests
pub mod runner;
pub mod rust_toolchain;
Expand Down Expand Up @@ -1549,6 +1583,8 @@ fn ensure_backtrace_dlopen_program_compiled() -> anyhow::Result<()> {
&[
base.join("backtrace_dlopen_program"),
base.join("libbacktrace_dlopen_target.so"),
base.join("load_instance_program"),
base.join("libload_instance.so"),
],
) {
return result;
Expand Down
10 changes: 8 additions & 2 deletions e2e-tests/tests/fixtures/backtrace_dlopen_program/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ OBJ ?= $(BINARY).o
SHARED_LIB ?= libbacktrace_dlopen_target.so
SHARED_OBJ ?= backtrace_dlopen_lib.o

all: $(BINARY) $(SHARED_LIB)
all: $(BINARY) $(SHARED_LIB) load_instance_program libload_instance.so

load_instance_program: load_instance_program.c
$(CC) $(BASE_CFLAGS) -O0 -o $@ $< -ldl

libload_instance.so: load_instance_lib.c
$(CC) $(BASE_CFLAGS) -O0 -fPIC -shared -Wl,--build-id -o $@ $<

$(BINARY): $(OBJ)
$(CC) $(BASE_CFLAGS) -O0 -fomit-frame-pointer -o $@ $(OBJ) -ldl
Expand All @@ -22,6 +28,6 @@ $(SHARED_OBJ): backtrace_dlopen_lib.c backtrace_dlopen_lib.h

clean:
rm -f *.o backtrace_dlopen_program libbacktrace_dlopen_target.so dlopen.trigger \
dlopen.trigger.pending dlopen.after_limit.trigger
dlopen.trigger.pending dlopen.after_limit.trigger load_instance_program libload_instance.so

.PHONY: all clean
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
volatile int instance_marker = 0;

__attribute__((noinline)) int instance_tick(int expected)
{
asm volatile("" ::: "memory");
return instance_marker == expected;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#define _GNU_SOURCE
#include <dlfcn.h>
#include <fcntl.h>
#include <link.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

static volatile sig_atomic_t running = 1;

static void stop(int signo)
{
(void)signo;
running = 0;
}

static void *load_instance(const char *path, int marker, int (**tick)(int))
{
void *handle = dlmopen(LM_ID_NEWLM, path, RTLD_NOW | RTLD_LOCAL);
if (!handle) {
fprintf(stderr, "dlmopen: %s\n", dlerror());
exit(2);
}
volatile int *value = dlsym(handle, "instance_marker");
*tick = dlsym(handle, "instance_tick");
if (!value || !*tick) {
fprintf(stderr, "missing instance symbols\n");
exit(2);
}
*value = marker;
return handle;
}

static void mark_ready(int count)
{
FILE *file = fopen("instance.ready", "w");
if (!file) {
perror("instance.ready");
exit(2);
}
fprintf(file, "%d\n", count);
fclose(file);
}

static void *map_read_only_copy(int (*first)(int), size_t page_size)
{
int fd = open("./libload_instance.so", O_RDONLY);
if (fd < 0) {
perror("open library");
exit(2);
}
uintptr_t page = (uintptr_t)first & ~(uintptr_t)(page_size - 1);
void *mapping = MAP_FAILED;
for (uintptr_t gap = 16U << 20; gap <= 256U << 20; gap += 16U << 20) {
mapping = mmap((void *)(page - gap), page_size, PROT_READ,
MAP_PRIVATE | MAP_FIXED_NOREPLACE, fd, 0);
if (mapping != MAP_FAILED) {
break;
}
}
close(fd);
if (mapping == MAP_FAILED) {
perror("mmap read-only library");
exit(2);
}
return mapping;
}

int main(void)
{
signal(SIGTERM, stop);
signal(SIGINT, stop);
int (*first)(int) = NULL;
int (*second)(int) = NULL;
void *one = load_instance("./libload_instance.so", 11, &first);
void *two = NULL;
size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
void *read_only = NULL;
if (access("instance.readonly", F_OK) == 0) {
read_only = map_read_only_copy(first, page_size);
}
mark_ready(1);
while (running) {
if (!two && access("instance.trigger", F_OK) == 0) {
const char *path = access("instance.copy", F_OK) == 0
? "./libload_instance_copy.so" : "./libload_instance.so";
two = load_instance(path, 22, &second);
FILE *pc_file = fopen("instance.second_pc", "w");
if (!pc_file) {
perror("instance.second_pc");
return 2;
}
fprintf(pc_file, "%llu\n", (unsigned long long)(uintptr_t)second);
fclose(pc_file);
mark_ready(2);
}
if (!first(11) || (second && !second(22))) {
fprintf(stderr, "native instance value mismatch\n");
return 3;
}
usleep(5000);
}
if (two) {
dlclose(two);
}
dlclose(one);
if (read_only) {
munmap(read_only, page_size);
}
return 0;
}
35 changes: 1 addition & 34 deletions e2e-tests/tests/globals_target_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

mod common;

use common::{init, FIXTURES};
use common::{init, skip_if_nested_t_mode_unsupported, FIXTURES};
use regex::Regex;
use serial_test::serial;
use std::env;
use std::os::unix::fs as unix_fs;
use std::path::{Path, PathBuf};
use std::sync::{
Expand Down Expand Up @@ -124,38 +123,6 @@ fn workspace_root() -> anyhow::Result<PathBuf> {
.ok_or_else(|| anyhow::anyhow!("failed to resolve workspace root"))
}

fn skip_if_nested_t_mode_unsupported() -> bool {
let target_mode = match env::var("E2E_TARGET_MODE") {
Ok(value) => value,
Err(env::VarError::NotPresent) => return false,
Err(err) => {
eprintln!("continuing nested -t test despite unreadable E2E_TARGET_MODE: {err}");
return false;
}
};
let nested_child_container = matches!(
target_mode.trim().to_ascii_lowercase().as_str(),
"child-container" | "child" | "nested" | "descendant"
);
if !nested_child_container {
return false;
}

// Nested child-container `-t` is intentionally unsupported for now.
// The current `-t` implementation relies on target-path / sysmon lifecycle
// maintenance anchored in GhostScope's current `/proc` view, while nested
// child-container targets introduce a second PID namespace below that view.
// Without a stable, shared runtime-pid -> outer `/proc` pid mapping source,
// these tests are not a reliable correctness signal, so skip the entire
// nested `-t` suite instead of depending on CLI-only heuristics.
eprintln!(
"skipping nested child-container -t test: nested target-path mode is \
currently unsupported because proc offsets and runtime lifecycle \
maintenance stay anchored in the outer container /proc pid view"
);
true
}

// Late-start helper: run GhostScope first, wait until the CLI reports it has
// finished compile/load/attach, then start the target process.
async fn run_ghostscope_then_start_target_after_ready(
Expand Down
Loading
Loading