1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <unistd.h>
#include "tests.h"
#include "util/addr_location.h"
#include "util/event.h"
#include "util/sample.h"
#include "util/thread.h"
#include "util/thread-stack.h"
#define CALL_REF 1234UL
#define RET_REF 5678UL
struct return_check {
unsigned int matched;
unsigned int unmatched;
};
static int check_call_return(struct call_return *cr,
u64 *parent_db_id __maybe_unused, void *data)
{
struct return_check *check = data;
if (cr->call_ref == CALL_REF && cr->return_ref == RET_REF && !cr->flags)
check->matched++;
else
check->unmatched++;
return 0;
}
/* A zero expected_ret_addr asks the stack to use ip + insn_len. */
static int check_return_address(u64 expected_ret_addr, u64 actual_ret_addr,
u32 flags)
{
struct call_return_processor *crp;
struct return_check check = { };
struct thread *thread;
struct addr_location from = { }, to = { };
struct perf_sample sample = { };
int ret = TEST_FAIL;
thread = thread__new(getpid(), getpid());
if (!thread)
return TEST_FAIL;
crp = call_return_processor__new(check_call_return, &check);
if (!crp)
goto out;
sample.ip = 0x1000; /* Call or exception source addr */
sample.addr = 0x2000; /* Callee or exception handler addr */
sample.ret_addr = expected_ret_addr;
sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_CALL | flags;
sample.time = 1;
/* Model the opcode length after an instruction fetch. */
sample.insn_len = 4;
if (thread_stack__process(thread, thread__comm(thread), &sample,
&from, &to, CALL_REF, crp))
goto out;
sample.ip = 0x2000; /* Return instruction addr */
sample.addr = actual_ret_addr; /* Return branch target addr */
sample.ret_addr = 0;
sample.flags = PERF_IP_FLAG_BRANCH | PERF_IP_FLAG_RETURN |
(flags & (PERF_IP_FLAG_INTERRUPT | PERF_IP_FLAG_SYSCALLRET));
sample.time = 2;
if (thread_stack__process(thread, thread__comm(thread), &sample,
&to, &from, RET_REF, crp))
goto out;
if (check.matched == 1 && !check.unmatched)
ret = TEST_OK;
out:
thread__put(thread);
call_return_processor__free(crp);
return ret;
}
static int test__thread_stack(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
static const struct {
const char *name;
u64 expected_ret_addr;
u64 actual_ret_addr;
u32 flags;
} cases[] = {
{ "ordinary call", 0, 0x1004, 0 },
{ "interrupt", 0x1000, 0x1000, PERF_IP_FLAG_ASYNC | PERF_IP_FLAG_INTERRUPT },
{ "fault or trap", 0x1000, 0x1000, PERF_IP_FLAG_INTERRUPT },
{ "SVC", 0x1004, 0x1004, PERF_IP_FLAG_SYSCALLRET },
};
for (size_t i = 0; i < ARRAY_SIZE(cases); i++) {
if (check_return_address(cases[i].expected_ret_addr,
cases[i].actual_ret_addr, cases[i].flags)) {
pr_debug("Incorrect return address for %s\n", cases[i].name);
return TEST_FAIL;
}
}
return TEST_OK;
}
DEFINE_SUITE("Thread stack return addresses after instruction fetching", thread_stack);
|