Probes updates for v7.4:

- kprobes: Asynchronous optprobe optimizer and cleanups
   . Make optprobe optimizer multi-generational and asynchronous: Rework
     the optimizer as a state machine to avoid calling time-consuming
     synchronize_rcu_tasks() while holding kprobe_mutex, text_mutex, and
     cpus_read_lock(). Uses call_rcu_tasks() and a fixed ring of generation
     slots instead, so locks are only held during the brief instruction-
     patching window.
   . Use DEFINE_DEBUGFS_ATTRIBUTE for the kprobes enabled knob: Replace a
     hand-rolled debugfs read/write pair (dating from 2007) with the proper
     DEFINE_DEBUGFS_ATTRIBUTE macro, removing dead code and the stale XXX
     comment.
 
 - tracing/probes: Code quality and documentation cleanups
   . Add const qualifier to new_argv allocation type in trace_probe.c,
     preparing for the type-aware kmalloc_obj allocator conversion.
   . Clarify btf_find_struct_member() container parameter: Rename the
     misleading @member_type parameter to @container_type and add proper
     kernel-doc documentation for all parameters and return values.
 
 - selftests/ftrace: Boot tracing test framework and testcases
   . Add a generic boot tracing test framework under
     tools/testing/selftests/ftrace/boottime/. The test runner builds a
     minimal initramfs with busybox, boots QEMU, and runs tracefs checker
     scripts, supporting bootconfig, kernel command-line options, and
     multi-boot crash/reboot scenarios.
   . Add boot-time tracing testcases: Verify dynamic kprobe, synthetic
     events, eprobe, fprobe, tprobe, and trace instance creation via
     bootconfig.
   . Add kernel cmdline tracing testcases: Verify ftrace, trace_event,
     trace_buf_size, trace_options, trace_clock, and trace_instance kernel
     parameters.
   . Add persistent ring buffer testcases: Verify memory reservation via
     reserve_mem= and trace data retention across guest crash/reboot, as
     well as backup instance creation and boot log preservation.
   . Change non-unique test symbol from name_show to init_once in
     kprobe_non_uniq_symbol.tc for more reliable cross-architecture
     testing.
 -----BEGIN PGP SIGNATURE-----
 
 iQFPBAABCgA5FiEEh7BulGwFlgAOi5DV2/sHvwUrPxsFAmrX9VMbHG1hc2FtaS5o
 aXJhbWF0c3VAZ21haWwuY29tAAoJENv7B78FKz8bIfUIALyWP9gm/MdQ0jnl/qBg
 gtQ8Hp6xFVagZ7qbDBX72i8tLucVjQbadRyABeIo7XbTjxZxnKtQo1msbVm4kJS6
 y+9YzjHEcfCdJI6oArhvZ1E+QdOyQZrF0+HtZ/IGO4l5oDMp4v4D1uy2SKyg+WDs
 L+Bq5nPitjAxKw5bbai8WYnmm9gicRur7vcFft6NbShaGpb7ZJ3CKbSaGsD9t7+o
 qUiJYOuZ3WH+uN8ANPBA7Ckqe5Prk4hqNGZOgRVgDukQ6UGtbpGs+t26pndbPkL2
 UsBhvn+Yhs8byzW+99LZG66Wrfab/UC6FbayVCxeEciFp5ojBywRwjSrOPnt8mD7
 osY=
 =K6e/
 -----END PGP SIGNATURE-----

Merge tag 'probes-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace

Pull probes updates from Masami Hiramatsu:
 "Asynchronous optprobe optimizer and cleanups:

   - Make optprobe optimizer multi-generational and asynchronous

     Rework the optimizer as a state machine to avoid calling
     time-consuming synchronize_rcu_tasks() while holding kprobe_mutex,
     text_mutex, and cpus_read_lock(). Uses call_rcu_tasks() and a fixed
     ring of generation slots instead, so locks are only held during the
     brief instruction- patching window

   - Use DEFINE_DEBUGFS_ATTRIBUTE for the kprobes enabled knob

     Replace a hand-rolled debugfs read/write pair (dating from 2007)
     with the proper DEFINE_DEBUGFS_ATTRIBUTE macro, removing dead code
     and the stale XXX comment

  Code quality and documentation cleanups:

   - Add const qualifier to new_argv allocation type in trace_probe.c,
     preparing for the type-aware kmalloc_obj allocator conversion

   - Clarify btf_find_struct_member() container parameter

     Rename the misleading @member_type parameter to @container_type and
     add proper kernel-doc documentation for all parameters and return
     values

  Boot tracing test framework and testcases:

   - Add a generic boot tracing test framework under
     tools/testing/selftests/ftrace/boottime/

     The test runner builds a minimal initramfs with busybox, boots
     QEMU, and runs tracefs checker scripts, supporting bootconfig,
     kernel command-line options, and multi-boot crash/reboot scenarios

   - Add boot-time tracing testcases

     Verify dynamic kprobe, synthetic events, eprobe, fprobe, tprobe,
     and trace instance creation via bootconfig

   - Add kernel cmdline tracing testcases

     Verify ftrace, trace_event, trace_buf_size, trace_options,
     trace_clock, and trace_instance kernel parameters

   - Add persistent ring buffer testcases:

     Verify memory reservation via reserve_mem= and trace data retention
     across guest crash/reboot, as well as backup instance creation and
     boot log preservation

   - Change non-unique test symbol from name_show to init_once in
     kprobe_non_uniq_symbol.tc for more reliable cross-architecture
     testing"

* tag 'probes-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
  ftrace/selftests: Change non-unique test symbol to init_once
  tracing/probes: Clarify btf_find_struct_member() container parameter and add kernel-doc
  kprobes: Make optprobe optimizer multi-generational and asynchronous
  tracing/probes: Add const to new_argv allocation type
  kprobes: use DEFINE_DEBUGFS_ATTRIBUTE for the enabled knob
  selftests/ftrace: Add persistent ring buffer testcases
  selftests/ftrace: Add kernel cmdline tracing testcases
  selftests/ftrace: Add boot-time tracing testcases
  selftests/ftrace: Add generic boot tracing test framework
This commit is contained in:
Linus Torvalds 2026-10-23 12:12:26 -10:00
commit 3f942403bb
39 changed files with 1293 additions and 173 deletions

View File

@ -43,6 +43,7 @@
#include <linux/cleanup.h>
#include <linux/wait.h>
#include <linux/wait_bit.h>
#include <linux/rcupdate.h>
#include <asm/sections.h>
#include <asm/cacheflush.h>
@ -67,7 +68,7 @@ static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE];
/* NOTE: change this value only with 'kprobe_mutex' held */
static bool kprobes_all_disarmed;
/* This protects 'kprobe_table' and 'optimizing_list' */
/* This protects 'kprobe_table' and 'optprobe_gens' */
static DEFINE_MUTEX(kprobe_mutex);
static DEFINE_PER_CPU(struct kprobe *, kprobe_instance);
@ -476,18 +477,6 @@ bool kprobe_disarmed(struct kprobe *p)
return kprobe_disabled(p) && list_empty(&op->list);
}
/* Return true if the probe is queued on (un)optimizing lists */
static bool kprobe_queued(struct kprobe *p)
{
struct optimized_kprobe *op;
if (kprobe_aggrprobe(p)) {
op = container_of(p, struct optimized_kprobe, kp);
if (!list_empty(&op->list))
return true;
}
return false;
}
/*
* Return an optimized kprobe whose optimizing code replaces
@ -514,10 +503,39 @@ static struct kprobe *get_optimized_kprobe(kprobe_opcode_t *addr)
return NULL;
}
/* Optimization staging list, protected by 'kprobe_mutex' */
static LIST_HEAD(optimizing_list);
static LIST_HEAD(unoptimizing_list);
static LIST_HEAD(freeing_list);
#define OPTPROBE_GEN_MAX 2
struct optprobe_generation {
/* Probes waiting for grace period to drain tasks from original text */
struct list_head optimizing_list;
/* Probes queued for batch unoptimization upon dispatch */
struct list_head unoptimizing_list;
/* In-use probes waiting for grace period to drain optinsn slot */
struct list_head cooling_list;
/* Unused probes unhashed and waiting for batch release */
struct list_head freeing_list;
/* Tasks RCU callback head for asynchronous waiting */
struct rcu_head rcu;
/* True if dispatched and awaiting Tasks RCU callback */
bool in_flight;
/* True when Tasks RCU callback has fired and ready to finalize */
bool ready;
};
/*
* Generational ring of optprobes.
*
* Incoming probe requests are queued into the waiting room generation
* (optprobe_gens[optprobe_cur_gen]). When dispatched, the generation
* unoptimizes its probes, invokes call_rcu_tasks(), and optprobe_cur_gen
* advances to the next slot.
*
* To ensure an idle generation is always available to collect incoming
* requests without dynamic allocation, the last available generation slot
* is never dispatched until another generation has finished.
*/
static struct optprobe_generation optprobe_gens[OPTPROBE_GEN_MAX];
static int optprobe_cur_gen;
static void optimize_kprobe(struct kprobe *p);
static struct task_struct *kprobe_optimizer_task;
@ -535,11 +553,66 @@ static unsigned long optimizer_passes;
#define OPTIMIZE_DELAY 5
/*
* Optimize (replace a breakpoint with a jump) kprobes listed on
* 'optimizing_list'.
* Note: gen->cooling_list is not checked here because it is strictly
* an in-flight holding list for dispatched generations, so it is always
* empty in optprobe_cur_gen.
*/
static void do_optimize_kprobes(void)
static bool optprobe_has_queued_probes(void)
{
struct optprobe_generation *gen = &optprobe_gens[optprobe_cur_gen];
return !list_empty(&gen->optimizing_list) ||
!list_empty(&gen->unoptimizing_list) ||
!list_empty(&gen->freeing_list);
}
static int optprobe_active_gens_count(void)
{
int count = 0;
int i;
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
if (optprobe_gens[i].in_flight || optprobe_gens[i].ready)
count++;
}
return count;
}
/*
* The last generation must not be fired until another generation is done.
* (Thus the last generation acts as the waiting room.)
*/
static bool optprobe_can_fire(void)
{
return optprobe_active_gens_count() < OPTPROBE_GEN_MAX - 1;
}
static bool optprobe_has_ready_gens(void)
{
int i;
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
if (READ_ONCE(optprobe_gens[i].ready))
return true;
}
return false;
}
static bool optprobe_optimizer_busy(void)
{
return optprobe_has_queued_probes() || (optprobe_active_gens_count() > 0);
}
/*
* Unoptimize (replace a jump with a breakpoint and remove the breakpoint
* if need) kprobes listed on 'unopt_list'.
*/
static void do_unoptimize_kprobes(struct list_head *unopt_list,
struct list_head *free_list,
struct list_head *cooling_list)
{
struct optimized_kprobe *op, *tmp;
lockdep_assert_held(&text_mutex);
/*
* The optimization/unoptimization refers 'online_cpus' via
@ -553,31 +626,11 @@ static void do_optimize_kprobes(void)
*/
lockdep_assert_cpus_held();
/* Optimization never be done when disarmed */
if (kprobes_all_disarmed || !kprobes_allow_optimization ||
list_empty(&optimizing_list))
return;
if (!list_empty(unopt_list))
arch_unoptimize_kprobes(unopt_list, free_list);
arch_optimize_kprobes(&optimizing_list);
}
/*
* Unoptimize (replace a jump with a breakpoint and remove the breakpoint
* if need) kprobes listed on 'unoptimizing_list'.
*/
static void do_unoptimize_kprobes(void)
{
struct optimized_kprobe *op, *tmp;
lockdep_assert_held(&text_mutex);
/* See comment in do_optimize_kprobes() */
lockdep_assert_cpus_held();
if (!list_empty(&unoptimizing_list))
arch_unoptimize_kprobes(&unoptimizing_list, &freeing_list);
/* Loop on 'freeing_list' for disarming and removing from kprobe hash list */
list_for_each_entry_safe(op, tmp, &freeing_list, list) {
/* Loop on 'free_list' for disarming and removing from kprobe hash list */
list_for_each_entry_safe(op, tmp, free_list, list) {
/* Switching from detour code to origin */
op->kp.flags &= ~KPROBE_FLAG_OPTIMIZED;
/* Disarm probes if marked disabled and not gone */
@ -589,18 +642,26 @@ static void do_unoptimize_kprobes(void)
* for synchronization, these probes are reclaimed.
* (reclaiming is done by do_free_cleaned_kprobes().)
*/
hlist_del_rcu(&op->kp.hlist);
} else
list_del_init(&op->list);
hlist_del_init_rcu(&op->kp.hlist);
} else {
/*
* Keep on cooling_list until the quiescence period
* completes so that kprobe_disarmed() remains false and
* unregister_kprobes() does not prematurely free it.
*/
list_move(&op->list, cooling_list);
}
}
}
/* Reclaim all kprobes on the 'freeing_list' */
static void do_free_cleaned_kprobes(void)
/* Reclaim all kprobes on the 'free_list' */
static void do_free_cleaned_kprobes(struct list_head *free_list)
{
struct optimized_kprobe *op, *tmp;
list_for_each_entry_safe(op, tmp, &freeing_list, list) {
list_for_each_entry_safe(op, tmp, free_list, list) {
struct kprobe *_p;
list_del_init(&op->list);
if (WARN_ON_ONCE(!kprobe_unused(&op->kp))) {
/*
@ -612,11 +673,10 @@ static void do_free_cleaned_kprobes(void)
/*
* The aggregator was holding back another probe while it sat on the
* unoptimizing/freeing lists. Now that the aggregator has been fully
* unoptimizing/freeing lists. Now that the aggregator has been fully
* reverted we can safely retry the optimization of that sibling.
*/
struct kprobe *_p = get_optimized_kprobe(op->kp.addr);
_p = get_optimized_kprobe(op->kp.addr);
if (unlikely(_p))
optimize_kprobe(_p);
@ -626,67 +686,146 @@ static void do_free_cleaned_kprobes(void)
static void kick_kprobe_optimizer(void);
/* Kprobe jump optimizer */
static void kprobe_optimizer(void)
static void optprobe_generation_rcu_cb(struct rcu_head *rcu)
{
guard(mutex)(&kprobe_mutex);
struct optprobe_generation *gen;
gen = container_of(rcu, struct optprobe_generation, rcu);
WRITE_ONCE(gen->ready, true);
wake_up(&kprobe_optimizer_wait);
}
static void optprobe_finalize_generation(struct optprobe_generation *gen)
{
struct optimized_kprobe *op, *tmp;
lockdep_assert_held(&kprobe_mutex);
scoped_guard(cpus_read_lock) {
guard(mutex)(&text_mutex);
/* Optimization never be done when disarmed */
if (!kprobes_all_disarmed && kprobes_allow_optimization &&
!list_empty(&gen->optimizing_list))
arch_optimize_kprobes(&gen->optimizing_list);
}
/* Free cleaned kprobes after quiescence period */
do_free_cleaned_kprobes(&gen->freeing_list);
/* Finalize unoptimized kprobes whose quiescence period completed */
list_for_each_entry_safe(op, tmp, &gen->cooling_list, list) {
if (kprobe_unused(&op->kp)) {
/*
* Unregistered while quiescence period was in flight.
* Unhash it now and move to cur_gen's freeing list so
* it will be reclaimed after the next quiescence period.
*/
hlist_del_init_rcu(&op->kp.hlist);
list_move(&op->list, &optprobe_gens[optprobe_cur_gen].freeing_list);
kick_kprobe_optimizer();
} else {
/* Still in use; now safely disarmed */
list_del_init(&op->list);
if (!kprobe_disabled(&op->kp))
optimize_kprobe(&op->kp);
}
}
gen->in_flight = false;
WRITE_ONCE(gen->ready, false);
}
static bool optprobe_dispatch_generation(void)
{
struct optprobe_generation *gen;
lockdep_assert_held(&kprobe_mutex);
if (!optprobe_can_fire() || !optprobe_has_queued_probes())
return false;
gen = &optprobe_gens[optprobe_cur_gen];
scoped_guard(cpus_read_lock) {
guard(mutex)(&text_mutex);
/*
* Step 1: Unoptimize kprobes and collect cleaned (unused and disarmed)
* kprobes before waiting for quiesence period.
* Unoptimize kprobes and collect cleaned (unused and disarmed)
* kprobes before waiting for quiescence period.
*/
do_unoptimize_kprobes();
/*
* Step 2: Wait for quiesence period to ensure all potentially
* preempted tasks to have normally scheduled. Because optprobe
* may modify multiple instructions, there is a chance that Nth
* instruction is preempted. In that case, such tasks can return
* to 2nd-Nth byte of jump instruction. This wait is for avoiding it.
* Note that on non-preemptive kernel, this is transparently converted
* to synchronoze_sched() to wait for all interrupts to have completed.
*/
synchronize_rcu_tasks();
/* Step 3: Optimize kprobes after quiesence period */
do_optimize_kprobes();
/* Step 4: Free cleaned kprobes after quiesence period */
do_free_cleaned_kprobes();
do_unoptimize_kprobes(&gen->unoptimizing_list, &gen->freeing_list,
&gen->cooling_list);
}
/* Step 5: Wake up flushers, and kick optimizer again if needed. */
optimizer_passes++;
wake_up_var_locked(&optimizer_passes, &kprobe_mutex);
/* Advance cur_gen to the next generation slot */
optprobe_cur_gen = (optprobe_cur_gen + 1) % OPTPROBE_GEN_MAX;
if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list))
kick_kprobe_optimizer(); /*normal kick*/
gen->in_flight = true;
WRITE_ONCE(gen->ready, false);
call_rcu_tasks(&gen->rcu, optprobe_generation_rcu_cb);
return true;
}
/* Kprobe jump optimizer */
static void kprobe_optimizer(void)
{
bool progress = false;
int i;
guard(mutex)(&kprobe_mutex);
/* Step 1: Finalize any generation whose Tasks RCU grace period completed */
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
if (READ_ONCE(optprobe_gens[i].ready)) {
optprobe_finalize_generation(&optprobe_gens[i]);
progress = true;
}
}
/* Step 2: Dispatch waiting room generation if allowed */
if (optprobe_dispatch_generation())
progress = true;
/* Step 3: Wake up flushers if progress was made */
if (progress) {
optimizer_passes++;
wake_up_var_locked(&optimizer_passes, &kprobe_mutex);
}
if (optprobe_has_queued_probes() && optprobe_can_fire()) {
/* Probes remain and can be fired immediately (e.g. retried siblings) */
kick_kprobe_optimizer();
}
}
static int kprobe_optimizer_thread(void *data)
{
while (!kthread_should_stop()) {
/* To avoid hung_task, wait in interruptible state. */
/* Wait until there is work to do or a generation is ready */
wait_event_interruptible(kprobe_optimizer_wait,
atomic_read(&optimizer_state) != OPTIMIZER_ST_IDLE ||
kthread_should_stop());
atomic_read(&optimizer_state) != OPTIMIZER_ST_IDLE ||
optprobe_has_ready_gens() ||
kthread_should_stop());
if (kthread_should_stop())
break;
/*
* If it was a normal kick, wait for OPTIMIZE_DELAY.
* This wait can be interrupted by a flush request.
* If it was a normal kick and no generation is ready to finalize,
* wait for OPTIMIZE_DELAY to batch incoming requests.
* This wait can be interrupted by a flush request or a ready generation.
*/
if (atomic_read(&optimizer_state) == 1)
if (atomic_read(&optimizer_state) == OPTIMIZER_ST_KICKED &&
!optprobe_has_ready_gens()) {
wait_event_interruptible_timeout(
kprobe_optimizer_wait,
atomic_read(&optimizer_state) == OPTIMIZER_ST_FLUSHING ||
optprobe_has_ready_gens() ||
kthread_should_stop(),
OPTIMIZE_DELAY);
}
if (kthread_should_stop())
break;
@ -711,20 +850,20 @@ static void wait_for_kprobe_optimizer_locked(void)
{
lockdep_assert_held(&kprobe_mutex);
while (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) {
while (optprobe_optimizer_busy()) {
unsigned long passes = optimizer_passes;
/*
* Set state to OPTIMIZER_ST_FLUSHING and wake up the thread if it's
* idle. If it's already kicked, it will see the state change.
*/
if (atomic_xchg_acquire(&optimizer_state,
OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING)
wake_up(&kprobe_optimizer_wait);
/* Wake up optimizer thread if it can make progress */
if ((optprobe_can_fire() && optprobe_has_queued_probes()) ||
optprobe_has_ready_gens()) {
if (atomic_xchg_acquire(&optimizer_state,
OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING)
wake_up(&kprobe_optimizer_wait);
}
/*
* kprobe_optimizer() holds 'kprobe_mutex' for a whole pass, which
* this drops while sleeping, so a new count means a full pass ran.
* this drops while sleeping, so a new count means progress was made.
*/
wait_var_event_mutex(&optimizer_passes,
optimizer_passes != passes, &kprobe_mutex);
@ -742,10 +881,43 @@ void wait_for_kprobe_optimizer(void)
bool optprobe_queued_unopt(struct optimized_kprobe *op)
{
struct optimized_kprobe *_op;
int i;
list_for_each_entry(_op, &unoptimizing_list, list) {
if (op == _op)
return true;
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
list_for_each_entry(_op, &optprobe_gens[i].unoptimizing_list, list) {
if (op == _op)
return true;
}
}
return false;
}
static bool optprobe_queued_freeing(struct optimized_kprobe *op)
{
struct optimized_kprobe *_op;
int i;
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
list_for_each_entry(_op, &optprobe_gens[i].freeing_list, list) {
if (op == _op)
return true;
}
}
return false;
}
static bool optprobe_queued_cooling(struct optimized_kprobe *op)
{
struct optimized_kprobe *_op;
int i;
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
list_for_each_entry(_op, &optprobe_gens[i].cooling_list, list) {
if (op == _op)
return true;
}
}
return false;
@ -779,6 +951,15 @@ static void optimize_kprobe(struct kprobe *p)
}
return;
}
if (optprobe_queued_cooling(op)) {
/* Under in-flight unoptimization. It will be re-optimized upon finalize */
return;
}
if (optprobe_queued_freeing(op))
list_del_init(&op->list);
op->kp.flags |= KPROBE_FLAG_OPTIMIZED;
/*
@ -788,7 +969,7 @@ static void optimize_kprobe(struct kprobe *p)
if (WARN_ON_ONCE(!list_empty(&op->list)))
return;
list_add(&op->list, &optimizing_list);
list_add(&op->list, &optprobe_gens[optprobe_cur_gen].optimizing_list);
kick_kprobe_optimizer();
}
@ -803,14 +984,14 @@ static void force_unoptimize_kprobe(struct optimized_kprobe *op)
/* Unoptimize a kprobe if p is optimized */
static void unoptimize_kprobe(struct kprobe *p, bool force)
{
struct optprobe_generation *cur;
struct optimized_kprobe *op;
if (!kprobe_aggrprobe(p) || kprobe_disarmed(p))
return; /* This is not an optprobe nor optimized */
op = container_of(p, struct optimized_kprobe, kp);
if (!kprobe_optimized(p))
return;
cur = &optprobe_gens[optprobe_cur_gen];
if (!list_empty(&op->list)) {
if (optprobe_queued_unopt(op)) {
@ -818,10 +999,30 @@ static void unoptimize_kprobe(struct kprobe *p, bool force)
if (force) {
/*
* Forcibly unoptimize the kprobe here, and queue it
* in the freeing list for release afterwards.
* for cooling down (or freeing if unused).
*/
force_unoptimize_kprobe(op);
list_move(&op->list, &freeing_list);
if (kprobe_unused(&op->kp)) {
if (!kprobe_gone(&op->kp))
arch_disarm_kprobe(&op->kp);
hlist_del_init_rcu(&op->kp.hlist);
list_move(&op->list, &cur->freeing_list);
} else {
list_move(&op->list, &cur->cooling_list);
}
kick_kprobe_optimizer();
}
} else if (optprobe_queued_cooling(op)) {
if (force && kprobe_unused(&op->kp)) {
/*
* Already unoptimized, move to freeing list for
* release afterwards.
*/
if (!kprobe_gone(&op->kp))
arch_disarm_kprobe(&op->kp);
hlist_del_init_rcu(&op->kp.hlist);
list_move(&op->list, &cur->freeing_list);
kick_kprobe_optimizer();
}
} else {
/* Dequeue from the optimizing queue */
@ -831,12 +1032,15 @@ static void unoptimize_kprobe(struct kprobe *p, bool force)
return;
}
if (!kprobe_optimized(p))
return;
/* Optimized kprobe case */
if (force) {
/* Forcibly update the code: this is a special case */
force_unoptimize_kprobe(op);
} else {
list_add(&op->list, &unoptimizing_list);
list_add(&op->list, &cur->unoptimizing_list);
kick_kprobe_optimizer();
}
}
@ -868,23 +1072,29 @@ static void kill_optimized_kprobe(struct kprobe *p)
struct optimized_kprobe *op;
op = container_of(p, struct optimized_kprobe, kp);
if (!list_empty(&op->list))
/* Dequeue from the (un)optimization queue */
list_del_init(&op->list);
if (!list_empty(&op->list)) {
if (kprobe_unused(p)) {
if (optprobe_queued_unopt(op) || optprobe_queued_cooling(op)) {
hlist_del_init_rcu(&op->kp.hlist);
list_move(&op->list,
&optprobe_gens[optprobe_cur_gen].freeing_list);
kick_kprobe_optimizer();
} else if (!optprobe_queued_freeing(op)) {
list_del_init(&op->list);
}
} else {
list_del_init(&op->list);
}
}
op->kp.flags &= ~KPROBE_FLAG_OPTIMIZED;
if (kprobe_unused(p)) {
/*
* Unused kprobe is on unoptimizing or freeing list. We move it
* to freeing_list and let the kprobe_optimizer() remove it from
* the kprobe hash list and free it.
*/
if (optprobe_queued_unopt(op))
list_move(&op->list, &freeing_list);
}
/* Don't touch the code, because it is already freed. */
arch_remove_optimized_kprobe(op);
/*
* Don't remove the slot if it is queued for freeing or unoptimization;
* the optimizer will reclaim it after the quiescence period.
*/
if (!optprobe_queued_freeing(op) && !optprobe_queued_unopt(op) &&
!optprobe_queued_cooling(op))
arch_remove_optimized_kprobe(op);
}
static inline
@ -1060,6 +1270,19 @@ static void __arm_kprobe(struct kprobe *p)
optimize_kprobe(p); /* Try to optimize (add kprobe to a list) */
}
/* Return true if the probe is queued on unoptimizing lists */
static bool kprobe_queued_unopt(struct kprobe *p)
{
struct optimized_kprobe *op;
if (kprobe_aggrprobe(p)) {
op = container_of(p, struct optimized_kprobe, kp);
if (optprobe_queued_unopt(op))
return true;
}
return false;
}
/* Remove the breakpoint of a probe. */
static void __disarm_kprobe(struct kprobe *p, bool reopt)
{
@ -1070,7 +1293,7 @@ static void __disarm_kprobe(struct kprobe *p, bool reopt)
/* Try to unoptimize */
unoptimize_kprobe(p, kprobes_all_disarmed);
if (!kprobe_queued(p)) {
if (!kprobe_queued_unopt(p)) {
arch_disarm_kprobe(p);
/* If another kprobe was blocked, re-optimize it. */
_p = get_optimized_kprobe(p->addr);
@ -1081,11 +1304,23 @@ static void __disarm_kprobe(struct kprobe *p, bool reopt)
static void __init init_optprobe(void)
{
int i;
#ifdef __ARCH_WANT_KPROBES_INSN_SLOT
/* Init 'kprobe_optinsn_slots' for allocation */
kprobe_optinsn_slots.insn_size = MAX_OPTINSN_SIZE;
#endif
for (i = 0; i < OPTPROBE_GEN_MAX; i++) {
INIT_LIST_HEAD(&optprobe_gens[i].optimizing_list);
INIT_LIST_HEAD(&optprobe_gens[i].unoptimizing_list);
INIT_LIST_HEAD(&optprobe_gens[i].cooling_list);
INIT_LIST_HEAD(&optprobe_gens[i].freeing_list);
optprobe_gens[i].in_flight = false;
optprobe_gens[i].ready = false;
}
optprobe_cur_gen = 0;
init_waitqueue_head(&kprobe_optimizer_wait);
atomic_set(&optimizer_state, OPTIMIZER_ST_IDLE);
kprobe_optimizer_task = kthread_run(kprobe_optimizer_thread, NULL,
@ -3027,47 +3262,22 @@ static int disarm_all_kprobes(void)
return ret;
}
/*
* XXX: The debugfs bool file interface doesn't allow for callbacks
* when the bool state is switched. We can reuse that facility when
* available
*/
static ssize_t read_enabled_file_bool(struct file *file,
char __user *user_buf, size_t count, loff_t *ppos)
static int kprobes_enabled_set(void *data, u64 val)
{
char buf[3];
if (val)
return arm_all_kprobes();
if (!kprobes_all_disarmed)
buf[0] = '1';
else
buf[0] = '0';
buf[1] = '\n';
buf[2] = 0x00;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
return disarm_all_kprobes();
}
static ssize_t write_enabled_file_bool(struct file *file,
const char __user *user_buf, size_t count, loff_t *ppos)
static int kprobes_enabled_get(void *data, u64 *val)
{
bool enable;
int ret;
ret = kstrtobool_from_user(user_buf, count, &enable);
if (ret)
return ret;
ret = enable ? arm_all_kprobes() : disarm_all_kprobes();
if (ret)
return ret;
return count;
*val = !kprobes_all_disarmed;
return 0;
}
static const struct file_operations fops_kp = {
.read = read_enabled_file_bool,
.write = write_enabled_file_bool,
.llseek = default_llseek,
};
DEFINE_DEBUGFS_ATTRIBUTE(fops_kp, kprobes_enabled_get,
kprobes_enabled_set, "%llu\n");
static int __init debugfs_kprobe_init(void)
{

View File

@ -63,19 +63,33 @@ struct btf_anon_stack {
u32 offset;
};
/*
* Find a member of data structure/union by name and return it.
* Return NULL if not found, or ERR_PTR(-EINVAL) if parameter is invalid.
* If the member is a member of an anonymous union/structure, the bit offset
* of that anonymous union/structure is stored into @anon_offset.
* If @member_type is non-NULL, the actual containing structure/union type
* of the found member is stored into @member_type.
/**
* btf_find_struct_member() - Find a member of data structure/union by name
* @btf: Pointer to BTF object.
* @type: Pointer to the struct/union BTF type to search within.
* @member_name: Name of the member to find.
* @anon_offset: Pointer to store bit offset of enclosing anonymous union/struct.
* @container_type: Pointer to store the actual containing struct/union type.
*
* Find a member of data structure/union by name, traversing into anonymous
* unions and structures if necessary.
*
* If the member is inside an anonymous union/structure, the bit offset
* of that anonymous union/structure from @type is stored into @anon_offset.
* If @container_type is non-NULL, the actual struct/union BTF type that
* directly contains the found member is stored into @container_type.
*
* Return:
* * Pointer to &struct btf_member on success.
* * %NULL if the member was not found.
* * %ERR_PTR(-EINVAL) if @type is not a struct/union.
* * %ERR_PTR(-ENOMEM) if memory allocation failed.
*/
const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset,
const struct btf_type **member_type)
const struct btf_type **container_type)
{
struct btf_anon_stack *anon_stack;
const struct btf_member *member;
@ -107,8 +121,8 @@ const struct btf_member *btf_find_struct_member(struct btf *btf,
if (name && !strcmp(member_name, name)) {
if (anon_offset)
*anon_offset = cur_offset;
if (member_type)
*member_type = type;
if (container_type)
*container_type = type;
goto out;
}
}

View File

@ -18,7 +18,8 @@ const struct btf_member *btf_find_struct_member(struct btf *btf,
const struct btf_type *type,
const char *member_name,
u32 *anon_offset,
const struct btf_type **member_type);
const struct btf_type **container_type);
#ifdef CONFIG_DEBUG_INFO_BTF
void btf_trim_retval(unsigned long func, unsigned long *retval, bool *print_retval,
int *fmt, bool hex);

View File

@ -625,7 +625,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
{
const struct btf_type *type = *ptype;
const struct btf_member *field;
const struct btf_type *mtype;
const struct btf_type *container_type;
struct btf *btf = ctx_btf(ctx);
char *fieldname = *pfieldname;
int bitoffs = 0;
@ -641,7 +641,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
anon_offs = 0;
field = btf_find_struct_member(btf, type, fieldname,
&anon_offs, &mtype);
&anon_offs, &container_type);
if (IS_ERR(field)) {
trace_probe_log_err(ctx->offset, BAD_BTF_TID);
return PTR_ERR(field);
@ -654,7 +654,7 @@ static int get_bitoffset_of_field(char **pfieldname, const struct btf_type **pty
bitoffs += anon_offs;
/* Accumulate the bit-offsets of the dot-connected fields */
if (btf_type_kflag(mtype)) {
if (btf_type_kflag(container_type)) {
bitoffs += BTF_MEMBER_BIT_OFFSET(field->offset);
ctx->last_bitsize = BTF_MEMBER_BITFIELD_SIZE(field->offset);
} else {
@ -2315,7 +2315,7 @@ const char **traceprobe_expand_meta_args(int argc, const char *argv[],
else
*new_argc = argc;
new_argv = kcalloc(*new_argc, sizeof(char *), GFP_KERNEL);
new_argv = kcalloc(*new_argc, sizeof(*new_argv), GFP_KERNEL);
if (!new_argv)
return ERR_PTR(-ENOMEM);

View File

@ -2,8 +2,8 @@
all:
TEST_PROGS_EXTENDED := ftracetest
TEST_PROGS := ftracetest-ktap
TEST_FILES := test.d settings
TEST_PROGS := ftracetest-ktap boottime-ktap
TEST_FILES := test.d settings boottime
EXTRA_CLEAN := $(OUTPUT)/logs/*
TEST_GEN_FILES := poll

View File

@ -0,0 +1,6 @@
#!/bin/sh -e
# SPDX-License-Identifier: GPL-2.0-only
#
# boottime-ktap: Wrapper to integrate boottime tracing test framework with kselftest runner
exec ./boottime/run_boottime_test.sh "$@"

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
all:
run_tests:
@./run_boottime_test.sh
clean:
.PHONY: all run_tests clean

View File

@ -0,0 +1,74 @@
Boot Tracing Test Suite
=======================
This directory contains an automated test framework to verify kernel boot
tracing configurations (bootconfig, kernel command-line tracing options, and
persistent ring buffers) during early boot.
Overview
--------
The test runner (`run_boottime_test.sh`) constructs a lightweight initramfs with
busybox and a tracefs checker script from `tests/`, attaches optional bootconfigs
from `bootconfigs/` using the `bootconfig` tool, applies optional command-line
parameters from `cmdlines/`, boots the target kernel image under QEMU, and reports
results according to TAP version 13 format.
Test Specification Format
-------------------------
For each test script `tests/<name>.sh`, the harness dynamically detects:
- `bootconfigs/<name>.bconf` or `<name>.bconf`: Bootconfig configuration file.
- `cmdlines/<name>.cmdline` or `# CMDLINE: <opts>` header in script: Kernel boot parameters.
- `qemuopts/<name>.qemuopts` or `# QEMUOPTS: <opts>` header in script: Additional QEMU CLI arguments.
- `# APPLETS: <applets>` header in script: Additional BusyBox applet symlinks to create in initramfs.
- `# REBOOT: 1` header in script: Multi-boot reboot/crash test mode (omits `-no-reboot` and doubles timeout).
Required Tools
--------------
- Host Architecture: x86 (x86_64/i686/i386/x86; tests skip on non-x86 hosts)
- `qemu-system-x86_64` (or custom QEMU executable)
- `busybox` (host binary at `/usr/bin/busybox` or custom path)
- `bootconfig` (compiled from `tools/bootconfig/bootconfig` or in PATH)
- `file` (standard file architecture identifier)
- `cpio` (standard archiver)
- `timeout` (standard coreutils execution timer)
Required Kernel Configurations
------------------------------
The kernel binary under test must be compiled with:
- `CONFIG_BOOT_CONFIG=y`
- `CONFIG_BOOTTIME_TRACING=y`
- `CONFIG_MAGIC_SYSRQ=y` (for guest reboot and poweroff)
Additional feature-specific config options enable respective testcases:
- `CONFIG_KPROBE_EVENTS=y` (for kprobe tests)
- `CONFIG_SYNTH_EVENTS=y` (for synthetic event tests)
- `CONFIG_EPROBE_EVENTS=y` (for eprobe tests)
- `CONFIG_FPROBE_EVENTS=y` (for fprobe and tracepoint probe tests)
- `CONFIG_FUNCTION_TRACER=y` (for tracer options)
- `CONFIG_RESERVE_MEM=y` (for persistent ring buffer tests)
Usage
-----
To run all test cases with a compiled kernel image:
$ ./run_boottime_test.sh -k /path/to/bzImage
To run a specific test case (e.g. `01-kprobe`):
$ ./run_boottime_test.sh -k /path/to/bzImage -t 01-kprobe
Customizing Tool Paths:
$ ./run_boottime_test.sh -b /path/to/bootconfig \
-k /path/to/bzImage \
-q /path/to/qemu-system-x86_64 \
-B /path/to/busybox
Environment Variables
---------------------
Tool locations and kernel paths can also be set via environment variables:
- `BOOTCONFIG`: Path to the `bootconfig` tool
- `KERNEL`: Path to the `bzImage` kernel binary
- `QEMU`: Path to the QEMU binary
- `BUSYBOX`: Path to the `busybox` binary
- `LOGDIR`: Directory path to preserve QEMU execution log files

View File

@ -0,0 +1,4 @@
ftrace.event.kprobes.vfs_read {
probes = "vfs_read $arg1"
enable
}

View File

@ -0,0 +1,4 @@
ftrace.event.synthetic.boot_lat {
fields = "unsigned long id", "u64 delta"
enable
}

View File

@ -0,0 +1,4 @@
ftrace.event.eprobes.ep_read {
probes = "sched/sched_switch"
enable
}

View File

@ -0,0 +1,4 @@
ftrace.event.fprobes.fp_read {
probes = "vfs_read"
enable
}

View File

@ -0,0 +1,4 @@
ftrace.event.tracepoints.tp_sched {
probes = "sched_switch"
enable
}

View File

@ -0,0 +1,5 @@
ftrace.instance.foo {
event.sched.sched_switch {
enable
}
}

View File

@ -0,0 +1 @@
ftrace=function

View File

@ -0,0 +1 @@
trace_event=sched:sched_switch,kmem:kmalloc

View File

@ -0,0 +1 @@
trace_buf_size=2048K

View File

@ -0,0 +1 @@
trace_options=sym-addr,verbose

View File

@ -0,0 +1 @@
trace_clock=global

View File

@ -0,0 +1 @@
trace_instance=bar,sched:sched_switch

View File

@ -0,0 +1 @@
reserve_mem=12M:32M:trace trace_instance=boot_map@trace

View File

@ -0,0 +1 @@
reserve_mem=12M:32M:trace trace_instance=boot_map@trace trace_instance=backup=boot_map

View File

@ -0,0 +1,403 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
#
# Generic Boot Tracing Test Harness
# Builds a lightweight initramfs, applies bootconfigs and kernel cmdline parameters,
# boots QEMU, and verifies tracefs configuration using test scripts.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -d "$SCRIPT_DIR/boottime" ]; then
SCRIPT_DIR="$SCRIPT_DIR/boottime"
fi
KERNEL_SRC="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
# Source KTAP helpers
if [ -f "$SCRIPT_DIR/../../kselftest/ktap_helpers.sh" ]; then
KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../../kselftest" && pwd)"
elif [ -f "$SCRIPT_DIR/../kselftest/ktap_helpers.sh" ]; then
KSELFTEST_DIR="$(cd "$SCRIPT_DIR/../kselftest" && pwd)"
else
echo "Error: ktap_helpers.sh not found relative to $SCRIPT_DIR" >&2
exit 1
fi
. "$KSELFTEST_DIR/ktap_helpers.sh"
BOOTCONFIG="${BOOTCONFIG:-"$KERNEL_SRC/tools/bootconfig/bootconfig"}"
KERNEL="${KERNEL:-"$KERNEL_SRC/arch/x86/boot/bzImage"}"
QEMU="${QEMU:-"qemu-system-x86_64"}"
BUSYBOX="${BUSYBOX:-"$(which busybox 2>/dev/null || echo "/usr/bin/busybox")"}"
TIMEOUT="${TIMEOUT:-60s}"
LOGDIR="${LOGDIR:-""}"
TARGET_TEST="all"
BUSYBOX_APPLETS=(
sh
cat
mount
umount
echo
sync
reboot
poweroff
cpio
grep
sleep
sed
)
usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " -b, --bootconfig PATH Path to bootconfig tool (default: $BOOTCONFIG)"
echo " -k, --kernel PATH Path to kernel image (default: $KERNEL)"
echo " -q, --qemu PATH Path to QEMU executable (default: $QEMU)"
echo " -B, --busybox PATH Path to busybox binary (default: $BUSYBOX)"
echo " -T, --timeout DURATION QEMU execution timeout (default: $TIMEOUT)"
echo " -l, --logdir DIR Directory to save QEMU log files"
echo " -t, --test NAME Run specific test (e.g. 01-kprobe) or 'all'"
echo " -h, --help Show this help message"
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
-b|--bootconfig)
BOOTCONFIG="$2"
shift 2
;;
-k|--kernel)
KERNEL="$2"
shift 2
;;
-q|--qemu)
QEMU="$2"
shift 2
;;
-B|--busybox)
BUSYBOX="$2"
shift 2
;;
-T|--timeout)
TIMEOUT="$2"
shift 2
;;
-l|--logdir)
LOGDIR="$2"
shift 2
;;
-t|--test)
TARGET_TEST="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# Check host architecture
HOST_ARCH="$(uname -m)"
case "$HOST_ARCH" in
x86_64|i686|i386|x86)
;;
*)
if [ "$KERNEL" = "$KERNEL_SRC/arch/x86/boot/bzImage" ]; then
ktap_print_header
ktap_skip_all \
"Default boot tracing test is x86 only ($HOST_ARCH)"
exit 0
fi
;;
esac
# Ensure bootconfig tool is built if Makefile exists
if [ ! -x "$BOOTCONFIG" ]; then
if [ -f "$KERNEL_SRC/tools/bootconfig/Makefile" ]; then
make -C "$KERNEL_SRC/tools/bootconfig" > /dev/null 2>&1 || true
fi
if [ ! -x "$BOOTCONFIG" ]; then
BOOTCONFIG="$(which bootconfig 2>/dev/null || true)"
fi
fi
if [ ! -x "$BOOTCONFIG" ]; then
ktap_print_header
ktap_skip_all "bootconfig tool not found at $BOOTCONFIG"
exit 0
fi
if [ ! -f "$KERNEL" ]; then
ktap_print_header
ktap_skip_all "Kernel image not found at $KERNEL"
exit 0
fi
if ! command -v "$QEMU" >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "QEMU binary not found: $QEMU"
exit 0
fi
if [ ! -x "$BUSYBOX" ]; then
ktap_print_header
ktap_skip_all "busybox binary not found at $BUSYBOX"
exit 0
fi
# Verify file command existence and architecture compatibility
if ! command -v file >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "file tool not found"
exit 0
fi
KERNEL_INFO="$(file -bL "$KERNEL" 2>/dev/null || true)"
case "$KERNEL_INFO" in
*x86*)
;;
*)
ktap_print_header
ktap_skip_all "Kernel architecture is not x86 ($KERNEL_INFO)"
exit 0
;;
esac
BUSYBOX_INFO="$(file -bL "$BUSYBOX" 2>/dev/null || true)"
case "$BUSYBOX_INFO" in
*x86-64*|*x86_64*|*80386*|*i386*)
;;
*)
ktap_print_header
ktap_skip_all \
"busybox binary architecture is incompatible with x86 kernel ($BUSYBOX_INFO)"
exit 0
;;
esac
if ! command -v cpio >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "cpio tool not found"
exit 0
fi
if ! command -v timeout >/dev/null 2>&1; then
ktap_print_header
ktap_skip_all "timeout tool not found"
exit 0
fi
double_timeout() {
local val="$1"
if [[ "$val" =~ ^([0-9]+)([a-z]*)$ ]]; then
local num="${BASH_REMATCH[1]}"
local unit="${BASH_REMATCH[2]}"
echo "$((num * 2))$unit"
else
echo "120s"
fi
}
run_single_test() {
local test_script="$1"
local name
local dir
name="$(basename "$test_script" .sh)"
dir="$(dirname "$test_script")"
local bconf_file=""
if [ -f "$SCRIPT_DIR/bootconfigs/$name.bconf" ]; then
bconf_file="$SCRIPT_DIR/bootconfigs/$name.bconf"
elif [ -f "$dir/$name.bconf" ]; then
bconf_file="$dir/$name.bconf"
fi
local test_cmdline=""
if [ -f "$SCRIPT_DIR/cmdlines/$name.cmdline" ]; then
test_cmdline="$(cat "$SCRIPT_DIR/cmdlines/$name.cmdline")"
elif [ -f "$dir/$name.cmdline" ]; then
test_cmdline="$(cat "$dir/$name.cmdline")"
fi
local inline_cmdline
inline_cmdline="$(grep -E '^# *CMDLINE:' "$test_script" | sed 's/^# *CMDLINE://' | xargs || true)"
if [ -n "$inline_cmdline" ]; then
test_cmdline="$test_cmdline $inline_cmdline"
fi
local test_qemuopts=""
if [ -f "$SCRIPT_DIR/qemuopts/$name.qemuopts" ]; then
test_qemuopts="$(cat "$SCRIPT_DIR/qemuopts/$name.qemuopts")"
elif [ -f "$dir/$name.qemuopts" ]; then
test_qemuopts="$(cat "$dir/$name.qemuopts")"
fi
local inline_qemuopts
inline_qemuopts="$(grep -E '^# *QEMUOPTS:' "$test_script" | sed 's/^# *QEMUOPTS://' | xargs || true)"
if [ -n "$inline_qemuopts" ]; then
test_qemuopts="$test_qemuopts $inline_qemuopts"
fi
local test_timeout="$TIMEOUT"
local inline_timeout
inline_timeout="$(grep -E '^# *TIMEOUT:' "$test_script" | sed 's/^# *TIMEOUT://' | xargs || true)"
if [ -n "$inline_timeout" ]; then
test_timeout="$inline_timeout"
fi
local is_reboot=0
if grep -q -E '^# *REBOOT: *1' "$test_script" || [ -f "$dir/$name.reboot" ]; then
is_reboot=1
fi
local effective_timeout="$test_timeout"
if [ "$is_reboot" -eq 1 ]; then
effective_timeout="$(double_timeout "$test_timeout")"
fi
local workdir
workdir="$(mktemp -d)"
if [ -z "$workdir" ] || [ ! -d "$workdir" ]; then
ktap_test_fail "$name (failed to create temporary directory)"
return 0
fi
trap 'rm -rf "$workdir"' EXIT
local test_applets=("${BUSYBOX_APPLETS[@]}")
local inline_applets
inline_applets="$(grep -E '^# *APPLETS:' "$test_script" | sed 's/^# *APPLETS://' | xargs || true)"
if [ -n "$inline_applets" ]; then
read -r -a extra_applets <<< "$inline_applets" || true
test_applets+=("${extra_applets[@]}")
fi
local rootfs="$workdir/rootfs"
mkdir -p "$rootfs"/{bin,sbin,etc,proc,sys,dev,tmp}
cp -L "$BUSYBOX" "$rootfs/bin/busybox"
chmod +x "$rootfs/bin/busybox"
(cd "$rootfs/bin" && for applet in "${test_applets[@]}"; do ln -sf busybox "$applet"; done)
if command -v ldd >/dev/null 2>&1; then
for lib in $(ldd "$BUSYBOX" 2>/dev/null | grep -o '/[^ ]*' || true); do
if [ -f "$lib" ]; then
mkdir -p "$rootfs$(dirname "$lib")"
cp -L "$lib" "$rootfs$lib" 2>/dev/null || true
fi
done
fi
cat << 'EOF' > "$rootfs/init"
#!/bin/sh
mount -t proc proc /proc 2>/dev/null
mount -t sysfs sys /sys 2>/dev/null
mount -t tracefs nodev /sys/kernel/tracing 2>/dev/null
/bin/check_test.sh
RET=$?
if [ $RET -eq 0 ]; then
echo "TEST RESULT: PASS"
else
echo "TEST RESULT: FAIL"
fi
sync
echo o > /proc/sysrq-trigger 2>/dev/null || true
poweroff -f 2>/dev/null || reboot -f 2>/dev/null || true
while true; do sleep 100 2>/dev/null || break; done
EOF
chmod +x "$rootfs/init"
cp "$test_script" "$rootfs/bin/check_test.sh"
chmod +x "$rootfs/bin/check_test.sh"
local initramfs="$workdir/initramfs.cpio"
(cd "$rootfs" && find . | cpio -o -H newc --quiet) > "$initramfs"
if [ -n "$bconf_file" ] && [ -f "$bconf_file" ]; then
"$BOOTCONFIG" -a "$bconf_file" "$initramfs" > /dev/null
fi
local logfile
local base_cmdline="bootconfig console=tty0 console=ttyS0 panic=-1"
if [ -n "$test_cmdline" ]; then
base_cmdline="$base_cmdline $test_cmdline"
fi
if [ -n "$LOGDIR" ]; then
mkdir -p "$LOGDIR"
logfile="$LOGDIR/$name.log"
base_cmdline="$base_cmdline dump_bconf"
else
logfile="$workdir/qemu.log"
base_cmdline="quiet $base_cmdline"
fi
local qemu_args=()
if [ -n "$test_qemuopts" ]; then
read -r -d '' -a qemu_args <<< "$test_qemuopts" || true
fi
if [ "$is_reboot" -ne 1 ]; then
qemu_args+=("-no-reboot")
fi
timeout "$effective_timeout" "$QEMU" -kernel "$KERNEL" \
-initrd "$initramfs" \
-append "$base_cmdline" \
-display none \
-serial stdio \
"${qemu_args[@]}" < /dev/null > "$logfile" 2>&1 || true
if grep -q "TEST RESULT: PASS" "$logfile"; then
ktap_test_pass "$name"
else
ktap_test_fail "$name"
ktap_print_msg "QEMU Console Output for $name:"
while IFS= read -r line; do
ktap_print_msg "$line"
done < "$logfile"
fi
rm -rf "$workdir"
trap - EXIT
return 0
}
TEST_SCRIPTS=()
if [ -d "$SCRIPT_DIR/tests" ]; then
while IFS= read -r f; do
[ -n "$f" ] && TEST_SCRIPTS+=("$f")
done < <(find "$SCRIPT_DIR/tests" -type f -name "*.sh" | sort)
fi
TOTAL_TESTS=0
for test_script in "${TEST_SCRIPTS[@]}"; do
name="$(basename "$test_script" .sh)"
if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
continue
fi
TOTAL_TESTS=$((TOTAL_TESTS + 1))
done
ktap_print_header
ktap_set_plan "$TOTAL_TESTS"
for test_script in "${TEST_SCRIPTS[@]}"; do
name="$(basename "$test_script" .sh)"
if [ "$TARGET_TEST" != "all" ] && [ "$TARGET_TEST" != "$name" ]; then
continue
fi
run_single_test "$test_script"
done
ktap_finished

View File

@ -0,0 +1,25 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check kprobe bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/events/kprobes/vfs_read" ]; then
echo "FAIL: kprobe event kprobes/vfs_read does not exist"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/events/kprobes/vfs_read/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: kprobe event kprobes/vfs_read is not enabled ($ENABLE)"
exit 1
fi
echo "PASS: 01-kprobe"
exit 0

View File

@ -0,0 +1,25 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check synthetic event bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/events/synthetic/boot_lat" ]; then
echo "FAIL: synthetic event synthetic/boot_lat does not exist"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/events/synthetic/boot_lat/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: synthetic event synthetic/boot_lat is not enabled ($ENABLE)"
exit 1
fi
echo "PASS: 02-synth"
exit 0

View File

@ -0,0 +1,25 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check eprobe event bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/events/eprobes/ep_read" ]; then
echo "FAIL: eprobe event eprobes/ep_read does not exist"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/events/eprobes/ep_read/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: eprobe event eprobes/ep_read is not enabled ($ENABLE)"
exit 1
fi
echo "PASS: 03-eprobe"
exit 0

View File

@ -0,0 +1,25 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check fprobe event bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/events/fprobes/fp_read" ]; then
echo "FAIL: fprobe event fprobes/fp_read does not exist"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/events/fprobes/fp_read/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: fprobe event fprobes/fp_read is not enabled ($ENABLE)"
exit 1
fi
echo "PASS: 04-fprobe"
exit 0

View File

@ -0,0 +1,25 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check tracepoint probe event bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/events/tracepoints/tp_sched" ]; then
echo "FAIL: tracepoint probe event tracepoints/tp_sched does not exist"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/events/tracepoints/tp_sched/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: tracepoint probe event tracepoints/tp_sched is not enabled ($ENABLE)"
exit 1
fi
echo "PASS: 05-tprobe"
exit 0

View File

@ -0,0 +1,30 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check instance bootconfig settings on tracefs
TRACEDIR="/sys/kernel/tracing"
if [ -f /proc/bootconfig ] && grep -q "dump_bconf" /proc/cmdline 2>/dev/null; then
echo "=== /proc/bootconfig ==="
cat /proc/bootconfig
echo "========================"
fi
if [ ! -d "$TRACEDIR/instances/foo" ]; then
echo "FAIL: trace instance foo does not exist"
exit 1
fi
if [ ! -d "$TRACEDIR/instances/foo/events/sched/sched_switch" ]; then
echo "FAIL: event sched_switch does not exist in instance foo"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/instances/foo/events/sched/sched_switch/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: event sched_switch is not enabled in instance foo ($ENABLE)"
exit 1
fi
echo "PASS: 06-instance"
exit 0

View File

@ -0,0 +1,19 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check ftrace= kernel command-line tracer setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -f "$TRACEDIR/current_tracer" ]; then
echo "FAIL: current_tracer does not exist"
exit 1
fi
read -r TRACER _ < "$TRACEDIR/current_tracer"
if [ "$TRACER" != "function" ]; then
echo "FAIL: current_tracer is '$TRACER', expected 'function'"
exit 1
fi
echo "PASS: cmdline-01-ftrace"
exit 0

View File

@ -0,0 +1,26 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check trace_event= kernel command-line setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -d "$TRACEDIR/events/sched/sched_switch" ]; then
echo "FAIL: event sched:sched_switch does not exist"
exit 1
fi
if [ ! -d "$TRACEDIR/events/kmem/kmalloc" ]; then
echo "FAIL: event kmem:kmalloc does not exist"
exit 1
fi
ENABLE1=$(cat "$TRACEDIR/events/sched/sched_switch/enable")
ENABLE2=$(cat "$TRACEDIR/events/kmem/kmalloc/enable")
if [ "$ENABLE1" != "1" ] || [ "$ENABLE2" != "1" ]; then
echo "FAIL: events not enabled (sched_switch=$ENABLE1, kmalloc=$ENABLE2)"
exit 1
fi
echo "PASS: cmdline-02-trace-event"
exit 0

View File

@ -0,0 +1,29 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# APPLETS: awk
# Check trace_buf_size= kernel command-line setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -f "$TRACEDIR/buffer_size_kb" ]; then
echo "FAIL: buffer_size_kb does not exist"
exit 1
fi
BUF_RAW=$(cat "$TRACEDIR/buffer_size_kb")
case "$BUF_RAW" in
*"expanded:"*)
BUFSIZE=$(echo "$BUF_RAW" | sed -n 's/.*expanded: *\([0-9]*\).*/\1/p')
;;
*)
BUFSIZE=$(echo "$BUF_RAW" | awk '{print $1}')
;;
esac
if [ -z "$BUFSIZE" ] || [ "$BUFSIZE" -lt 2048 ]; then
echo "FAIL: buffer_size_kb is '$BUF_RAW', expected >= 2048"
exit 1
fi
echo "PASS: cmdline-03-trace-buf-size"
exit 0

View File

@ -0,0 +1,23 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check trace_options= kernel command-line setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -f "$TRACEDIR/trace_options" ]; then
echo "FAIL: trace_options file does not exist"
exit 1
fi
if ! grep -qw "sym-addr" "$TRACEDIR/trace_options"; then
echo "FAIL: sym-addr option is not set in trace_options"
exit 1
fi
if ! grep -qw "verbose" "$TRACEDIR/trace_options"; then
echo "FAIL: verbose option is not set in trace_options"
exit 1
fi
echo "PASS: cmdline-04-trace-options"
exit 0

View File

@ -0,0 +1,19 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check trace_clock= kernel command-line setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -f "$TRACEDIR/trace_clock" ]; then
echo "FAIL: trace_clock file does not exist"
exit 1
fi
if ! grep -q '\[global\]' "$TRACEDIR/trace_clock"; then
CLOCK=$(cat "$TRACEDIR/trace_clock")
echo "FAIL: trace_clock is not set to global ($CLOCK)"
exit 1
fi
echo "PASS: cmdline-05-trace-clock"
exit 0

View File

@ -0,0 +1,24 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check trace_instance= kernel command-line setting
TRACEDIR="/sys/kernel/tracing"
if [ ! -d "$TRACEDIR/instances/bar" ]; then
echo "FAIL: trace instance bar does not exist"
exit 1
fi
if [ ! -d "$TRACEDIR/instances/bar/events/sched/sched_switch" ]; then
echo "FAIL: event sched_switch does not exist in instance bar"
exit 1
fi
ENABLE=$(cat "$TRACEDIR/instances/bar/events/sched/sched_switch/enable")
if [ "$ENABLE" != "1" ]; then
echo "FAIL: event sched_switch is not enabled in instance bar ($ENABLE)"
exit 1
fi
echo "PASS: cmdline-06-trace-instance"
exit 0

View File

@ -0,0 +1,28 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check persistent ring buffer across guest crash/reboot
# REBOOT: 1
TRACEDIR="/sys/kernel/tracing"
if [ ! -f "$TRACEDIR/instances/boot_map/trace" ]; then
echo "FAIL: persistent trace instance boot_map/trace file missing"
exit 1
fi
# Check if Boot 1 marker was already written
if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/boot_map/trace" 2>/dev/null; then
# Second boot: verify persistent ring buffer content from first boot
echo "PASS: persistent-01-reserve-mem"
exit 0
fi
# First boot: write marker to persistent buffer and trigger kernel crash/reboot
echo "BOOT1_MARKER" > "$TRACEDIR/instances/boot_map/trace_marker"
sync
# Trigger reboot to restart into second boot
echo b > /proc/sysrq-trigger 2>/dev/null || echo c > /proc/sysrq-trigger 2>/dev/null || true
sleep 5
echo "FAIL: reboot trigger failed on first boot"
exit 1

View File

@ -0,0 +1,40 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026, Google LLC.
# Check persistent backup instance across guest crash/reboot
# REBOOT: 1
TRACEDIR="/sys/kernel/tracing"
# Ensure both boot_map/trace and backup/trace files exist
if [ ! -f "$TRACEDIR/instances/boot_map/trace" ]; then
echo "FAIL: boot_map/trace file missing"
exit 1
fi
if [ ! -f "$TRACEDIR/instances/backup/trace" ]; then
echo "FAIL: backup/trace file missing"
exit 1
fi
# Check if BOOT1_MARKER is in boot_map/trace (indicates second boot)
if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/boot_map/trace" 2>/dev/null; then
# Second boot: verify BOOT1_MARKER was copied into backup/trace from Boot 1
if grep -q "BOOT1_MARKER" "$TRACEDIR/instances/backup/trace" 2>/dev/null; then
echo "PASS: persistent-02-backup-instance"
exit 0
else
echo "FAIL: BOOT1_MARKER found in boot_map/trace" \
"but missing from backup/trace on second boot"
exit 1
fi
fi
# First boot: write BOOT1_MARKER to boot_map and reboot via sysrq-trigger
echo "BOOT1_MARKER" > "$TRACEDIR/instances/boot_map/trace_marker"
sync
# Trigger reboot to restart into second boot
echo b > /proc/sysrq-trigger 2>/dev/null || echo c > /proc/sysrq-trigger 2>/dev/null || true
sleep 5
echo "FAIL: reboot trigger failed on first boot"
exit 1

View File

@ -1,3 +1,5 @@
CONFIG_BOOT_CONFIG=y
CONFIG_BOOTTIME_TRACING=y
CONFIG_BPF_SYSCALL=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_DWARF4=y
@ -8,22 +10,26 @@ CONFIG_FTRACE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_FUNCTION_GRAPH_RETVAL=y
CONFIG_FUNCTION_PROFILER=y
CONFIG_FUNCTION_TRACER=y
CONFIG_HIST_TRIGGERS=y
CONFIG_IRQSOFF_TRACER=y
CONFIG_KALLSYMS_ALL=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_PREEMPTIRQ_DELAY_TEST=m
CONFIG_PREEMPT_TRACER=y
CONFIG_PROBE_EVENTS_BTF_ARGS=y
CONFIG_RESERVE_MEM=y
CONFIG_SAMPLES=y
CONFIG_SAMPLE_FTRACE_DIRECT=m
CONFIG_SAMPLE_TRACE_EVENTS=m
CONFIG_SAMPLE_TRACE_PRINTK=m
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y
CONFIG_SYNTH_EVENTS=y
CONFIG_TRACER_SNAPSHOT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y

View File

@ -3,7 +3,7 @@
# description: Test failure of registering kprobe on non unique symbol
# requires: kprobe_events
SYMBOL='name_show'
SYMBOL='init_once'
# We skip this test on kernel where SYMBOL is unique or does not exist.
if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}$" /proc/kallsyms)" -le '1' ]; then