mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
s390/unwind: add a test for the internal API
unwind_for_each_frame can take at least 8 different sets of parameters. Add a test to make sure they all are handled in a sane way. Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com> Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Co-developed-by: Vasily Gorbik <gor@linux.ibm.com> Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
This commit is contained in:
parent
adcfb8cdc9
commit
badbf39790
@ -1018,3 +1018,17 @@ config S390_GUEST
|
||||
the KVM hypervisor.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Selftests"
|
||||
|
||||
config S390_UNWIND_SELFTEST
|
||||
def_tristate n
|
||||
prompt "Test unwind functions"
|
||||
help
|
||||
This option enables s390 specific stack unwinder testing kernel
|
||||
module. This option is not useful for distributions or general
|
||||
kernels, but only for kernel developers working on architecture code.
|
||||
|
||||
Say N if you are unsure.
|
||||
|
||||
endmenu
|
||||
|
@ -11,3 +11,6 @@ lib-$(CONFIG_UPROBES) += probes.o
|
||||
# Instrumenting memory accesses to __user data (in different address space)
|
||||
# produce false positives
|
||||
KASAN_SANITIZE_uaccess.o := n
|
||||
|
||||
obj-$(CONFIG_S390_UNWIND_SELFTEST) += test_unwind.o
|
||||
CFLAGS_test_unwind.o += -fno-optimize-sibling-calls
|
||||
|
231
arch/s390/lib/test_unwind.c
Normal file
231
arch/s390/lib/test_unwind.c
Normal file
@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Test module for unwind_for_each_frame
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "test_unwind: " fmt
|
||||
#include <asm/unwind.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#define BT_BUF_SIZE (PAGE_SIZE * 4)
|
||||
|
||||
/*
|
||||
* To avoid printk line limit split backtrace by lines
|
||||
*/
|
||||
static void print_backtrace(char *bt)
|
||||
{
|
||||
char *p;
|
||||
|
||||
while (true) {
|
||||
p = strsep(&bt, "\n");
|
||||
if (!p)
|
||||
break;
|
||||
pr_err("%s\n", p);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls unwind_for_each_frame(task, regs, sp) and verifies that the result
|
||||
* contains unwindme_func2 followed by unwindme_func1.
|
||||
*/
|
||||
static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs,
|
||||
unsigned long sp)
|
||||
{
|
||||
int frame_count, prev_is_func2, seen_func2_func1;
|
||||
const int max_frames = 128;
|
||||
struct unwind_state state;
|
||||
size_t bt_pos = 0;
|
||||
int ret = 0;
|
||||
char *bt;
|
||||
|
||||
bt = kmalloc(BT_BUF_SIZE, GFP_KERNEL);
|
||||
if (!bt) {
|
||||
pr_err("failed to allocate backtrace buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
/* Unwind. */
|
||||
frame_count = 0;
|
||||
prev_is_func2 = 0;
|
||||
seen_func2_func1 = 0;
|
||||
unwind_for_each_frame(&state, task, regs, sp) {
|
||||
unsigned long addr = unwind_get_return_address(&state);
|
||||
char sym[KSYM_SYMBOL_LEN];
|
||||
|
||||
if (!addr || frame_count == max_frames)
|
||||
break;
|
||||
sprint_symbol(sym, addr);
|
||||
if (bt_pos < BT_BUF_SIZE) {
|
||||
bt_pos += snprintf(bt + bt_pos, BT_BUF_SIZE - bt_pos, "%s\n", sym);
|
||||
if (bt_pos >= BT_BUF_SIZE)
|
||||
pr_err("backtrace buffer is too small\n");
|
||||
}
|
||||
frame_count += 1;
|
||||
if (prev_is_func2 && str_has_prefix(sym, "unwindme_func1"))
|
||||
seen_func2_func1 = 1;
|
||||
prev_is_func2 = str_has_prefix(sym, "unwindme_func2");
|
||||
}
|
||||
|
||||
/* Check the results. */
|
||||
if (!seen_func2_func1) {
|
||||
pr_err("unwindme_func2 and unwindme_func1 not found\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (frame_count == max_frames) {
|
||||
pr_err("Maximum number of frames exceeded\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
print_backtrace(bt);
|
||||
kfree(bt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* State of the task being unwound. */
|
||||
struct unwindme {
|
||||
int flags;
|
||||
struct completion task_ready;
|
||||
wait_queue_head_t task_wq;
|
||||
unsigned long sp;
|
||||
};
|
||||
|
||||
/* Values of unwindme.flags. */
|
||||
#define UWM_DEFAULT 0x0
|
||||
#define UWM_THREAD 0x1 /* Unwind a separate task. */
|
||||
#define UWM_REGS 0x2 /* Pass regs to test_unwind(). */
|
||||
#define UWM_SP 0x4 /* Pass sp to test_unwind(). */
|
||||
#define UWM_CALLER 0x8 /* Unwind starting from caller. */
|
||||
|
||||
static __always_inline unsigned long get_psw_addr(void)
|
||||
{
|
||||
unsigned long psw_addr;
|
||||
|
||||
asm volatile(
|
||||
"basr %[psw_addr],0\n"
|
||||
: [psw_addr] "=d" (psw_addr));
|
||||
return psw_addr;
|
||||
}
|
||||
|
||||
/* This function may or may not appear in the backtrace. */
|
||||
static noinline int unwindme_func4(struct unwindme *u)
|
||||
{
|
||||
if (!(u->flags & UWM_CALLER))
|
||||
u->sp = current_frame_address();
|
||||
if (u->flags & UWM_THREAD) {
|
||||
complete(&u->task_ready);
|
||||
wait_event(u->task_wq, kthread_should_park());
|
||||
kthread_parkme();
|
||||
return 0;
|
||||
} else {
|
||||
struct pt_regs regs;
|
||||
|
||||
memset(®s, 0, sizeof(regs));
|
||||
regs.psw.addr = get_psw_addr();
|
||||
regs.gprs[15] = current_stack_pointer();
|
||||
return test_unwind(NULL,
|
||||
(u->flags & UWM_REGS) ? ®s : NULL,
|
||||
(u->flags & UWM_SP) ? u->sp : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function may or may not appear in the backtrace. */
|
||||
static noinline int unwindme_func3(struct unwindme *u)
|
||||
{
|
||||
u->sp = current_frame_address();
|
||||
return unwindme_func4(u);
|
||||
}
|
||||
|
||||
/* This function must appear in the backtrace. */
|
||||
static noinline int unwindme_func2(struct unwindme *u)
|
||||
{
|
||||
return unwindme_func3(u);
|
||||
}
|
||||
|
||||
/* This function must follow unwindme_func2 in the backtrace. */
|
||||
static noinline int unwindme_func1(void *u)
|
||||
{
|
||||
return unwindme_func2((struct unwindme *)u);
|
||||
}
|
||||
|
||||
/* Spawns a task and passes it to test_unwind(). */
|
||||
static int test_unwind_task(struct unwindme *u)
|
||||
{
|
||||
struct task_struct *task;
|
||||
int ret;
|
||||
|
||||
/* Initialize thread-related fields. */
|
||||
init_completion(&u->task_ready);
|
||||
init_waitqueue_head(&u->task_wq);
|
||||
|
||||
/*
|
||||
* Start the task and wait until it reaches unwindme_func4() and sleeps
|
||||
* in (task_ready, unwind_done] range.
|
||||
*/
|
||||
task = kthread_run(unwindme_func1, u, "%s", __func__);
|
||||
if (IS_ERR(task)) {
|
||||
pr_err("kthread_run() failed\n");
|
||||
return PTR_ERR(task);
|
||||
}
|
||||
/*
|
||||
* Make sure task reaches unwindme_func4 before parking it,
|
||||
* we might park it before kthread function has been executed otherwise
|
||||
*/
|
||||
wait_for_completion(&u->task_ready);
|
||||
kthread_park(task);
|
||||
/* Unwind. */
|
||||
ret = test_unwind(task, NULL, (u->flags & UWM_SP) ? u->sp : 0);
|
||||
kthread_stop(task);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int test_unwind_flags(int flags)
|
||||
{
|
||||
struct unwindme u;
|
||||
|
||||
u.flags = flags;
|
||||
if (u.flags & UWM_THREAD)
|
||||
return test_unwind_task(&u);
|
||||
else
|
||||
return unwindme_func1(&u);
|
||||
}
|
||||
|
||||
static int test_unwind_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
#define TEST(flags) \
|
||||
do { \
|
||||
pr_info("[ RUN ] " #flags "\n"); \
|
||||
if (!test_unwind_flags((flags))) { \
|
||||
pr_info("[ OK ] " #flags "\n"); \
|
||||
} else { \
|
||||
pr_err("[ FAILED ] " #flags "\n"); \
|
||||
ret = -EINVAL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
TEST(UWM_DEFAULT);
|
||||
TEST(UWM_SP);
|
||||
TEST(UWM_REGS);
|
||||
TEST(UWM_SP | UWM_REGS);
|
||||
TEST(UWM_CALLER | UWM_SP);
|
||||
TEST(UWM_CALLER | UWM_SP | UWM_REGS);
|
||||
TEST(UWM_THREAD);
|
||||
TEST(UWM_THREAD | UWM_SP);
|
||||
TEST(UWM_THREAD | UWM_CALLER | UWM_SP);
|
||||
#undef TEST
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void test_unwind_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(test_unwind_init);
|
||||
module_exit(test_unwind_exit);
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user