mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
3f942403bb
520
kernel/kprobes.c
520
kernel/kprobes.c
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
6
tools/testing/selftests/ftrace/boottime-ktap
Executable file
6
tools/testing/selftests/ftrace/boottime-ktap
Executable 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 "$@"
|
||||
10
tools/testing/selftests/ftrace/boottime/Makefile
Normal file
10
tools/testing/selftests/ftrace/boottime/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
all:
|
||||
|
||||
run_tests:
|
||||
@./run_boottime_test.sh
|
||||
|
||||
clean:
|
||||
|
||||
.PHONY: all run_tests clean
|
||||
74
tools/testing/selftests/ftrace/boottime/README
Normal file
74
tools/testing/selftests/ftrace/boottime/README
Normal 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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ftrace.event.kprobes.vfs_read {
|
||||
probes = "vfs_read $arg1"
|
||||
enable
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ftrace.event.synthetic.boot_lat {
|
||||
fields = "unsigned long id", "u64 delta"
|
||||
enable
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ftrace.event.eprobes.ep_read {
|
||||
probes = "sched/sched_switch"
|
||||
enable
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ftrace.event.fprobes.fp_read {
|
||||
probes = "vfs_read"
|
||||
enable
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
ftrace.event.tracepoints.tp_sched {
|
||||
probes = "sched_switch"
|
||||
enable
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
ftrace.instance.foo {
|
||||
event.sched.sched_switch {
|
||||
enable
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
ftrace=function
|
||||
|
|
@ -0,0 +1 @@
|
|||
trace_event=sched:sched_switch,kmem:kmalloc
|
||||
|
|
@ -0,0 +1 @@
|
|||
trace_buf_size=2048K
|
||||
|
|
@ -0,0 +1 @@
|
|||
trace_options=sym-addr,verbose
|
||||
|
|
@ -0,0 +1 @@
|
|||
trace_clock=global
|
||||
|
|
@ -0,0 +1 @@
|
|||
trace_instance=bar,sched:sched_switch
|
||||
|
|
@ -0,0 +1 @@
|
|||
reserve_mem=12M:32M:trace trace_instance=boot_map@trace
|
||||
|
|
@ -0,0 +1 @@
|
|||
reserve_mem=12M:32M:trace trace_instance=boot_map@trace trace_instance=backup=boot_map
|
||||
403
tools/testing/selftests/ftrace/boottime/run_boottime_test.sh
Executable file
403
tools/testing/selftests/ftrace/boottime/run_boottime_test.sh
Executable 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
|
||||
25
tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh
Normal file
25
tools/testing/selftests/ftrace/boottime/tests/01-kprobe.sh
Normal 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
|
||||
25
tools/testing/selftests/ftrace/boottime/tests/02-synth.sh
Normal file
25
tools/testing/selftests/ftrace/boottime/tests/02-synth.sh
Normal 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
|
||||
25
tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh
Normal file
25
tools/testing/selftests/ftrace/boottime/tests/03-eprobe.sh
Normal 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
|
||||
25
tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh
Normal file
25
tools/testing/selftests/ftrace/boottime/tests/04-fprobe.sh
Normal 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
|
||||
25
tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh
Normal file
25
tools/testing/selftests/ftrace/boottime/tests/05-tprobe.sh
Normal 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
|
||||
30
tools/testing/selftests/ftrace/boottime/tests/06-instance.sh
Normal file
30
tools/testing/selftests/ftrace/boottime/tests/06-instance.sh
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user