From 0000504e23aa34455aeb503dd3ae9d92162f2a59 Mon Sep 17 00:00:00 2001 From: Jyun Kai Yang Date: Tue, 7 Jul 2026 20:52:18 +0800 Subject: [PATCH 1/4] Add conditional breakpoint framework for GDB Change-Id: Id3cdd80fabd6290f81f3789a37739883349ccdd7 --- scripts/gdb/kbox-events.py | 87 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 scripts/gdb/kbox-events.py diff --git a/scripts/gdb/kbox-events.py b/scripts/gdb/kbox-events.py new file mode 100644 index 0000000..e06951d --- /dev/null +++ b/scripts/gdb/kbox-events.py @@ -0,0 +1,87 @@ +# scripts/gdb/kbox-events.py +# 事件式條件斷點:用 Python 的 stop() 取代 GDB 原生條件, +# 支援原生條件做不到的判斷(字串比對、走訪 task 結構)。 +# 載入方式:GDB 內 source scripts/gdb/kbox-events.py + +import gdb + + +def _safe_eval(expr): + """求值失敗(prologue 中、符號不可見)時回 None,不丟例外。""" + try: + return gdb.parse_and_eval(expr) + except gdb.error: + return None + + +class KboxBreakOnPid(gdb.Breakpoint): + """在 FUNC 停,但只有當 TASK_EXPR->pid == 目標 PID 時才真的中斷。""" + def __init__(self, func, pid, task_expr): + super().__init__(func) + self.target_pid = int(pid) + self.task_expr = task_expr # 例如 __schedule 內的 "prev" + + def stop(self): + task = _safe_eval(self.task_expr) + if task is None: + return False # 引數尚不可見(prologue)就放行 + try: + return int(task["pid"]) == self.target_pid + except (gdb.error, gdb.MemoryError): + return False + + +class KboxBreakPidCmd(gdb.Command): + """kbox-break-pid PID FUNC TASK_EXPR + 例:kbox-break-pid 42 __schedule prev""" + def __init__(self): + super().__init__("kbox-break-pid", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + parts = arg.split() + if len(parts) != 3: + print("用法:kbox-break-pid PID FUNC TASK_EXPR") + return + pid, func, task_expr = parts + bp = KboxBreakOnPid(func, pid, task_expr) + print(f"Breakpoint {bp.number}: 停在 {func},當 {task_expr}->pid == {pid}") + + +class KboxBreakOnOpen(gdb.Breakpoint): + """在 do_sys_openat2 停,但只有開啟路徑含指定子字串時才中斷。""" + def __init__(self, needle): + super().__init__("do_sys_openat2") + self.needle = needle + + def stop(self): + fn = _safe_eval("filename") + if fn is None: + return False + try: + path = fn.string() + except (gdb.error, gdb.MemoryError): + return False + if self.needle in path: + print(f"[kbox-events] openat 命中: {path}") + return True + return False + + +class KboxBreakOpenCmd(gdb.Command): + """kbox-break-openat SUBSTR + 例:kbox-break-openat /etc/hostname""" + def __init__(self): + super().__init__("kbox-break-openat", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + needle = arg.strip() + if not needle: + print("用法:kbox-break-openat SUBSTR(路徑子字串)") + return + bp = KboxBreakOnOpen(needle) + print(f"Breakpoint {bp.number}: 停在 do_sys_openat2,當開啟路徑含 '{needle}'") + + +KboxBreakPidCmd() +KboxBreakOpenCmd() +print("kbox-events.py 已載入:kbox-break-pid、kbox-break-openat") From 00009c2c02999d5e0dafe2333d5a45a4f4c09660 Mon Sep 17 00:00:00 2001 From: Jyun Kai Yang Date: Tue, 7 Jul 2026 21:00:15 +0800 Subject: [PATCH 2/4] Add ftrace buffer export mechanism Change-Id: Ib6ff3821f2bec4ec1b9382ff1dc9dbc9403a825b --- include/kbox/cli.h | 5 + include/kbox/ftrace.h | 10 ++ mk/features.mk | 1 + src/cli.c | 33 ++++++ src/ftrace.c | 239 ++++++++++++++++++++++++++++++++++++++++++ src/image.c | 5 + 6 files changed, 293 insertions(+) create mode 100644 include/kbox/ftrace.h create mode 100644 src/ftrace.c diff --git a/include/kbox/cli.h b/include/kbox/cli.h index 2ea3430..c8a23ae 100644 --- a/include/kbox/cli.h +++ b/include/kbox/cli.h @@ -42,6 +42,11 @@ struct kbox_image_args { int web_port; /* --web=PORT (default 8080) */ const char *web_bind; /* --web-bind ADDR */ const char *trace_format; /* --trace-format=json */ + const char *ftrace_tracer; /* --ftrace=TRACER */ + const char *ftrace_filter; /* --ftrace-filter=SYM */ + const char *ftrace_dump; /* --ftrace-dump=PATH */ + const char *ftrace_events; /* --ftrace-events=cat/ev,... */ + const char *ftrace_outdir; /* --ftrace-outdir=DIR */ bool sqpoll; /* --sqpoll: busy-poll service thread */ const char *const *extra_args; /* remaining args after -- */ int extra_argc; /* count of extra_args */ diff --git a/include/kbox/ftrace.h b/include/kbox/ftrace.h new file mode 100644 index 0000000..591d7ae --- /dev/null +++ b/include/kbox/ftrace.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef KBOX_FTRACE_H +#define KBOX_FTRACE_H +struct kbox_sysnrs; +struct kbox_image_args; +int kbox_ftrace_enable(const struct kbox_sysnrs *s, + const struct kbox_image_args *args); +void kbox_ftrace_dump(const struct kbox_sysnrs *s, + const struct kbox_image_args *args); +#endif /* KBOX_FTRACE_H */ diff --git a/mk/features.mk b/mk/features.mk index 26a8ad5..ace15a3 100644 --- a/mk/features.mk +++ b/mk/features.mk @@ -28,6 +28,7 @@ SRCS = $(SRC_DIR)/main.c \ $(SRC_DIR)/mount.c \ $(SRC_DIR)/probe.c \ $(SRC_DIR)/image.c \ + $(SRC_DIR)/ftrace.c \ $(SRC_DIR)/seccomp-bpf.c \ $(SRC_DIR)/seccomp-notify.c \ $(SRC_DIR)/shadow-fd.c \ diff --git a/src/cli.c b/src/cli.c index 19dc9cc..88889d1 100644 --- a/src/cli.c +++ b/src/cli.c @@ -20,6 +20,11 @@ enum { OPT_WEB_BIND, OPT_SYSCALL_MODE, OPT_TRACE_FORMAT, + OPT_FTRACE, + OPT_FTRACE_FILTER, + OPT_FTRACE_DUMP, + OPT_FTRACE_EVENTS, + OPT_FTRACE_OUTDIR, OPT_SQPOLL, OPT_HELP, }; @@ -46,6 +51,11 @@ static const struct option longopts[] = { {"syscall-mode", required_argument, NULL, OPT_SYSCALL_MODE}, {"sqpoll", no_argument, NULL, OPT_SQPOLL}, {"trace-format", required_argument, NULL, OPT_TRACE_FORMAT}, + {"ftrace", required_argument, NULL, OPT_FTRACE}, + {"ftrace-filter", required_argument, NULL, OPT_FTRACE_FILTER}, + {"ftrace-dump", required_argument, NULL, OPT_FTRACE_DUMP}, + {"ftrace-events", required_argument, NULL, OPT_FTRACE_EVENTS}, + {"ftrace-outdir", required_argument, NULL, OPT_FTRACE_OUTDIR}, {"help", no_argument, NULL, OPT_HELP}, {NULL, 0, NULL, 0}, }; @@ -85,6 +95,14 @@ void kbox_usage(const char *argv0) " --web-bind ADDR Bind address for web (default: " "127.0.0.1)\n" " --trace-format FMT Trace output format (json)\n" + " --ftrace=TRACER Enable LKL ftrace " + "(function/function_graph)\n" + " --ftrace-filter=SYM Restrict ftrace to symbol SYM\n" + " --ftrace-dump=PATH Dump LKL trace to host file PATH\n" + " --ftrace-events=LIST Enable tracepoints cat/ev,... (event " + "mode)\n" + " --ftrace-outdir=DIR Event-mode output dir (ftrace.log + " + "start_ts)\n" " -h, --help Show this help\n", argv0); } @@ -260,6 +278,21 @@ int kbox_parse_args(int argc, char *argv[], struct kbox_image_args *img) } break; case 'h': + case OPT_FTRACE: + img->ftrace_tracer = optarg; + break; + case OPT_FTRACE_FILTER: + img->ftrace_filter = optarg; + break; + case OPT_FTRACE_DUMP: + img->ftrace_dump = optarg; + break; + case OPT_FTRACE_EVENTS: + img->ftrace_events = optarg; + break; + case OPT_FTRACE_OUTDIR: + img->ftrace_outdir = optarg; + break; case OPT_HELP: kbox_usage(argv[0]); return -1; diff --git a/src/ftrace.c b/src/ftrace.c new file mode 100644 index 0000000..51b3861 --- /dev/null +++ b/src/ftrace.c @@ -0,0 +1,239 @@ +/* SPDX-License-Identifier: MIT */ +#include +#include +#include +#include +#include + +#include "kbox/cli.h" +#include "kbox/ftrace.h" +#include "lkl-wrap.h" + +#define KBOX_TRACE_DIR "/.kbox-tracing" + +static int write_tracefs(const struct kbox_sysnrs *s, + const char *name, + const char *val) +{ + char path[256]; + snprintf(path, sizeof(path), "%s/%s", KBOX_TRACE_DIR, name); + long fd = kbox_lkl_openat(s, AT_FDCWD_LINUX, path, O_WRONLY, 0); + if (fd < 0) { + fprintf(stderr, "ftrace: open %s: %s\n", path, kbox_err_text(fd)); + return -1; + } + long n = kbox_lkl_write(s, fd, val, (long) strlen(val)); + kbox_lkl_close(s, fd); + if (n < 0) { + fprintf(stderr, "ftrace: write %s='%s': %s\n", path, val, + kbox_err_text(n)); + return -1; + } + return 0; +} + +static long read_lkl_file(const struct kbox_sysnrs *s, + const char *lkl_path, + char *buf, + long cap) +{ + long fd = kbox_lkl_openat(s, AT_FDCWD_LINUX, lkl_path, O_RDONLY, 0); + if (fd < 0) + return -1; + long total = 0, n; + while (total < cap - 1 && + (n = kbox_lkl_read(s, fd, buf + total, cap - 1 - total)) > 0) + total += n; + kbox_lkl_close(s, fd); + buf[total < 0 ? 0 : total] = '\0'; + return total; +} + +static long dump_lkl_to_host(const struct kbox_sysnrs *s, + const char *lkl_path, + const char *host_path) +{ + long fd = kbox_lkl_openat(s, AT_FDCWD_LINUX, lkl_path, O_RDONLY, 0); + if (fd < 0) { + fprintf(stderr, "ftrace: open %s: %s\n", lkl_path, kbox_err_text(fd)); + return -1; + } + FILE *out = fopen(host_path, "w"); + if (!out) { + fprintf(stderr, "ftrace: fopen %s: %s\n", host_path, strerror(errno)); + kbox_lkl_close(s, fd); + return -1; + } + char buf[8192]; + long total = 0, n; + while ((n = kbox_lkl_read(s, fd, buf, (long) sizeof(buf))) > 0) { + fwrite(buf, 1, (size_t) n, out); + total += n; + } + fclose(out); + kbox_lkl_close(s, fd); + return total; +} + +static void dump_available_tracers(const struct kbox_sysnrs *s) +{ + char buf[512]; + if (read_lkl_file(s, KBOX_TRACE_DIR "/available_tracers", buf, + sizeof(buf)) > 0) + fprintf(stderr, "ftrace: available_tracers: %s", buf); +} + +static int mount_tracefs(const struct kbox_sysnrs *s) +{ + long ret = kbox_lkl_mkdir(s, KBOX_TRACE_DIR, 0755); + if (ret < 0 && ret != -EEXIST) { + fprintf(stderr, "ftrace: mkdir %s: %s\n", KBOX_TRACE_DIR, + kbox_err_text(ret)); + return -1; + } + ret = kbox_lkl_mount(s, "tracefs", KBOX_TRACE_DIR, "tracefs", 0, NULL); + if (ret < 0 && ret != -EBUSY) { + fprintf(stderr, "ftrace: mount tracefs at %s: %s\n", KBOX_TRACE_DIR, + kbox_err_text(ret)); + return -1; + } + return 0; +} + +static int enable_events(const struct kbox_sysnrs *s, + const struct kbox_image_args *args) +{ + if (!args->ftrace_outdir) { + fprintf(stderr, "ftrace: --ftrace-events requires --ftrace-outdir\n"); + return -1; + } + write_tracefs(s, "events/enable", "0"); + write_tracefs(s, "current_tracer", "nop"); + if (write_tracefs(s, "trace_clock", "boot") < 0) + fprintf(stderr, + "ftrace: trace_clock=boot unavailable, using default clock " + "(start_ts alignment may differ)\n"); + write_tracefs(s, "options/record-tgid", "1"); + + char list[512]; + snprintf(list, sizeof(list), "%s", args->ftrace_events); + int enabled = 0; + char *save = NULL; + for (char *tok = strtok_r(list, ",", &save); tok; + tok = strtok_r(NULL, ",", &save)) { + char ev[256]; + snprintf(ev, sizeof(ev), "events/%s/enable", tok); + if (write_tracefs(s, ev, "1") == 0) + enabled++; + else + fprintf(stderr, "ftrace: event '%s' not found\n", tok); + } + if (enabled == 0) { + fprintf(stderr, "ftrace: no events enabled\n"); + return -1; + } + + write_tracefs(s, "tracing_on", "1"); + + mkdir(args->ftrace_outdir, 0755); + char up[128]; + if (read_lkl_file(s, "/proc/uptime", up, sizeof(up)) > 0) { + char *sp = up; + while (*sp && *sp != ' ' && *sp != '\n') + sp++; + *sp = '\0'; + char tspath[512]; + snprintf(tspath, sizeof(tspath), "%s/start_ts", args->ftrace_outdir); + FILE *f = fopen(tspath, "w"); + if (f) { + fprintf(f, "%s\n", up); + fclose(f); + } else { + fprintf(stderr, "ftrace: fopen %s: %s\n", tspath, strerror(errno)); + } + } else { + fprintf(stderr, + "ftrace: warning: could not read LKL /proc/uptime " + "for start_ts\n"); + } + + fprintf(stderr, "ftrace: %d event(s) enabled, recording to %s\n", enabled, + args->ftrace_outdir); + return 0; +} + +static void dump_events(const struct kbox_sysnrs *s, + const struct kbox_image_args *args) +{ + write_tracefs(s, "tracing_on", "0"); + mkdir(args->ftrace_outdir, 0755); + char logpath[512]; + snprintf(logpath, sizeof(logpath), "%s/ftrace.log", args->ftrace_outdir); + long n = dump_lkl_to_host(s, KBOX_TRACE_DIR "/trace", logpath); + if (n >= 0) + fprintf(stderr, "ftrace: wrote %ld bytes to %s\n", n, logpath); +} + +static int enable_tracer(const struct kbox_sysnrs *s, + const struct kbox_image_args *args) +{ + if (args->ftrace_filter) { + const char *ff = (strcmp(args->ftrace_tracer, "function_graph") == 0) + ? "set_graph_function" + : "set_ftrace_filter"; + if (write_tracefs(s, ff, args->ftrace_filter) < 0) + fprintf(stderr, + "ftrace: warning: filter '%s' rejected " + "(need CONFIG_DYNAMIC_FTRACE and a valid symbol)\n", + args->ftrace_filter); + } + if (write_tracefs(s, "current_tracer", args->ftrace_tracer) < 0) { + fprintf(/* format-ok */ + stderr, + "ftrace: cannot select tracer '%s' " + "(LKL has no HAVE_FUNCTION_GRAPH_TRACER; use " + "--ftrace-events)\n", + args->ftrace_tracer); + dump_available_tracers(s); + return -1; + } + write_tracefs(s, "tracing_on", "1"); + fprintf(stderr, "ftrace: tracer '%s' active%s%s\n", args->ftrace_tracer, + args->ftrace_filter ? ", filter " : "", + args->ftrace_filter ? args->ftrace_filter : ""); + return 0; +} + +int kbox_ftrace_enable(const struct kbox_sysnrs *s, + const struct kbox_image_args *args) +{ + if (!args->ftrace_events && !args->ftrace_tracer) + return 0; + + if (mount_tracefs(s) < 0) + return -1; + + write_tracefs(s, "tracing_on", "0"); + write_tracefs(s, "trace", ""); + + if (args->ftrace_events) + return enable_events(s, args); + return enable_tracer(s, args); +} + +void kbox_ftrace_dump(const struct kbox_sysnrs *s, + const struct kbox_image_args *args) +{ + if (args->ftrace_events && args->ftrace_outdir) { + dump_events(s, args); + return; + } + if (args->ftrace_tracer && args->ftrace_dump) { + write_tracefs(s, "tracing_on", "0"); + long n = + dump_lkl_to_host(s, KBOX_TRACE_DIR "/trace", args->ftrace_dump); + if (n >= 0) + fprintf(stderr, "ftrace: wrote %ld bytes to %s\n", n, + args->ftrace_dump); + } +} diff --git a/src/image.c b/src/image.c index 94160d9..799e572 100644 --- a/src/image.c +++ b/src/image.c @@ -21,6 +21,7 @@ #include "fd-table.h" #include "kbox/compiler.h" #include "kbox/elf.h" +#include "kbox/ftrace.h" #include "kbox/identity.h" #include "kbox/image.h" #include "kbox/mount.h" @@ -952,6 +953,9 @@ int kbox_run_image(const struct kbox_image_args *args) goto err_post_boot; } + if (kbox_ftrace_enable(sysnrs, args) < 0) + goto err_post_boot; + /* Probe host features. Rewrite mode skips seccomp-specific probes. */ if (kbox_probe_host_features(probe_mode) < 0) goto err_post_boot; @@ -1533,6 +1537,7 @@ int kbox_run_image(const struct kbox_image_args *args) close(exec_memfd); err_net: + kbox_ftrace_dump(sysnrs, args); kbox_halt_kernel(); #ifdef KBOX_HAS_WEB if (web_ctx) From 000047e3ceb39b79bd7c7afa62ae888d2202053c Mon Sep 17 00:00:00 2001 From: Jyun Kai Yang Date: Tue, 7 Jul 2026 21:00:29 +0800 Subject: [PATCH 3/4] Add drgn probing scripts for LKL kernel state Change-Id: I579fcee2ef4b9d9e113ee34ac54895b3efc920dd --- scripts/drgn/kbox_tasks.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 scripts/drgn/kbox_tasks.py diff --git a/scripts/drgn/kbox_tasks.py b/scripts/drgn/kbox_tasks.py new file mode 100644 index 0000000..5a7b8d2 --- /dev/null +++ b/scripts/drgn/kbox_tasks.py @@ -0,0 +1,27 @@ +# scripts/drgn/kbox_tasks.py +# 走訪 LKL task list,印 PID / COMM / STATE,用於與 lx-ps / kbox-task-walk 交叉驗證。 +from drgn.helpers.linux.pid import for_each_task + +STATE_NAMES = { + 0x0000: "RUNNING", + 0x0001: "INTERRUPTIBLE", + 0x0002: "UNINTERRUPTIBLE", + 0x0004: "STOPPED", + 0x0008: "TRACED", + 0x0402: "IDLE", # TASK_UNINTERRUPTIBLE | TASK_NOLOAD +} + +def state_name(task): + try: + s = task.member_("__state").value_() + except Exception: + return "?" + return STATE_NAMES.get(s, hex(s)) + +print(f"{'PID':>5} {'COMM':<18} STATE") +print("-" * 42) +count = 0 +for task in for_each_task(prog): + print(f"{task.pid.value_():>5} {task.comm.string_().decode():<18} {state_name(task)}") + count += 1 +print(f"\n共 {count} 個 task") From 000080d5ec4183df04f031313131dfb94855f784 Mon Sep 17 00:00:00 2001 From: Jyun Kai Yang Date: Tue, 7 Jul 2026 21:00:36 +0800 Subject: [PATCH 4/4] Add .gitignore for trace artifacts and vendored deps Change-Id: I8bea043198ee7c4d5e79ee8e5736691a03a35443 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9453641..00fc875 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ tests/stress/signal-race !tests/stress/*.c build/lkl-src src/web-assets.c +trace.pftrace +trace-graph/ +*.pftrace