diff --git a/kernel/kprobes.c b/kernel/kprobes.c index e787e4948c8b..2441b9e39343 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -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) { diff --git a/kernel/trace/trace_btf.c b/kernel/trace/trace_btf.c index adefd3969b1b..f0e0c04d58d9 100644 --- a/kernel/trace/trace_btf.c +++ b/kernel/trace/trace_btf.c @@ -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; } } diff --git a/kernel/trace/trace_btf.h b/kernel/trace/trace_btf.h index 20cbc3d4ae0e..b3d408e6a7f6 100644 --- a/kernel/trace/trace_btf.h +++ b/kernel/trace/trace_btf.h @@ -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); diff --git a/kernel/trace/trace_probe.c b/kernel/trace/trace_probe.c index 88518c5322a5..a880a90642d7 100644 --- a/kernel/trace/trace_probe.c +++ b/kernel/trace/trace_probe.c @@ -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); diff --git a/tools/testing/selftests/ftrace/Makefile b/tools/testing/selftests/ftrace/Makefile index 7c12263f8260..3d41f9545683 100644 --- a/tools/testing/selftests/ftrace/Makefile +++ b/tools/testing/selftests/ftrace/Makefile @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime-ktap b/tools/testing/selftests/ftrace/boottime-ktap new file mode 100755 index 000000000000..1fed68c8a944 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime-ktap @@ -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 "$@" diff --git a/tools/testing/selftests/ftrace/boottime/Makefile b/tools/testing/selftests/ftrace/boottime/Makefile new file mode 100644 index 000000000000..fd0975350f10 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +all: + +run_tests: + @./run_boottime_test.sh + +clean: + +.PHONY: all run_tests clean diff --git a/tools/testing/selftests/ftrace/boottime/README b/tools/testing/selftests/ftrace/boottime/README new file mode 100644 index 000000000000..1eb0191d75d4 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/README @@ -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/.sh`, the harness dynamically detects: +- `bootconfigs/.bconf` or `.bconf`: Bootconfig configuration file. +- `cmdlines/.cmdline` or `# CMDLINE: ` header in script: Kernel boot parameters. +- `qemuopts/.qemuopts` or `# QEMUOPTS: ` header in script: Additional QEMU CLI arguments. +- `# 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 diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf new file mode 100644 index 000000000000..13d30d1e51a6 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/01-kprobe.bconf @@ -0,0 +1,4 @@ +ftrace.event.kprobes.vfs_read { + probes = "vfs_read $arg1" + enable +} diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf new file mode 100644 index 000000000000..b1f37713f33c --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/02-synth.bconf @@ -0,0 +1,4 @@ +ftrace.event.synthetic.boot_lat { + fields = "unsigned long id", "u64 delta" + enable +} diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf new file mode 100644 index 000000000000..c1a8138e605d --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/03-eprobe.bconf @@ -0,0 +1,4 @@ +ftrace.event.eprobes.ep_read { + probes = "sched/sched_switch" + enable +} diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf new file mode 100644 index 000000000000..82ae2ac0629f --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/04-fprobe.bconf @@ -0,0 +1,4 @@ +ftrace.event.fprobes.fp_read { + probes = "vfs_read" + enable +} diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf new file mode 100644 index 000000000000..02a2803c770a --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/05-tprobe.bconf @@ -0,0 +1,4 @@ +ftrace.event.tracepoints.tp_sched { + probes = "sched_switch" + enable +} diff --git a/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf b/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf new file mode 100644 index 000000000000..3fdb6717afe2 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/bootconfigs/06-instance.bconf @@ -0,0 +1,5 @@ +ftrace.instance.foo { + event.sched.sched_switch { + enable + } +} diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline new file mode 100644 index 000000000000..4f6fc54240c8 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-01-ftrace.cmdline @@ -0,0 +1 @@ +ftrace=function diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline new file mode 100644 index 000000000000..66bcd29b14cb --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-02-trace-event.cmdline @@ -0,0 +1 @@ +trace_event=sched:sched_switch,kmem:kmalloc diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline new file mode 100644 index 000000000000..e5d1c4a8ca89 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-03-trace-buf-size.cmdline @@ -0,0 +1 @@ +trace_buf_size=2048K diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline new file mode 100644 index 000000000000..51d82f8e948d --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-04-trace-options.cmdline @@ -0,0 +1 @@ +trace_options=sym-addr,verbose diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline new file mode 100644 index 000000000000..f72a4ac808a3 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-05-trace-clock.cmdline @@ -0,0 +1 @@ +trace_clock=global diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline new file mode 100644 index 000000000000..9618e3e2b88b --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/cmdline-06-trace-instance.cmdline @@ -0,0 +1 @@ +trace_instance=bar,sched:sched_switch diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline new file mode 100644 index 000000000000..c69eeb27deb6 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-01-reserve-mem.cmdline @@ -0,0 +1 @@ +reserve_mem=12M:32M:trace trace_instance=boot_map@trace diff --git a/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline new file mode 100644 index 000000000000..43217c986ca1 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/cmdlines/persistent-02-backup-instance.cmdline @@ -0,0 +1 @@ +reserve_mem=12M:32M:trace trace_instance=boot_map@trace trace_instance=backup=boot_map diff --git a/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh b/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh new file mode 100755 index 000000000000..6e8593d5a2c5 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/run_boottime_test.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh new file mode 100644 index 000000000000..6ed7caca4c5f --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh b/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh new file mode 100644 index 000000000000..745c328dde15 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/02-synth.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh new file mode 100644 index 000000000000..9b3b7ab23c69 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh new file mode 100644 index 000000000000..27e090c28a4b --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh b/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh new file mode 100644 index 000000000000..810d34055f94 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh new file mode 100644 index 000000000000..f6e8519912e4 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/06-instance.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh new file mode 100644 index 000000000000..23a0b15f2dd3 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-01-ftrace.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh new file mode 100644 index 000000000000..b793907c48cd --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-02-trace-event.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh new file mode 100644 index 000000000000..4b936f84d4d7 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-03-trace-buf-size.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh new file mode 100644 index 000000000000..53a65b55f7a7 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-04-trace-options.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh new file mode 100644 index 000000000000..d6de9f6e2090 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-05-trace-clock.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh new file mode 100644 index 000000000000..aae7f0a86d81 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/cmdline-06-trace-instance.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh b/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh new file mode 100644 index 000000000000..49abaf518481 --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/persistent-01-reserve-mem.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh b/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh new file mode 100644 index 000000000000..0b2a2f03ce4f --- /dev/null +++ b/tools/testing/selftests/ftrace/boottime/tests/persistent-02-backup-instance.sh @@ -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 diff --git a/tools/testing/selftests/ftrace/config b/tools/testing/selftests/ftrace/config index 544de0db5f58..29f05f7c6e2c 100644 --- a/tools/testing/selftests/ftrace/config +++ b/tools/testing/selftests/ftrace/config @@ -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 diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc index 07b1177c1634..03eb9e70b438 100644 --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc @@ -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