Skip to content

Commit 80a9e35

Browse files
Weinan Liushaoyingxu
Weinan Liu
authored andcommitted
unwind: Implement generic sframe unwinder library
This change introduces a kernel space unwinder using sframe table for architectures without ORC unwinder support. The implementation is adapted from Josh's userspace sframe unwinder proposal[1] according to the sframe v2 spec[2]. [1] https://lore.kernel.org/lkml/42c0a99236af65c09c8182e260af7bcf5aa1e158.1730150953.git.jpoimboe@kernel.org/ [2] https://sourceware.org/binutils/docs/sframe-spec.html Signed-off-by: Weinan Liu <[email protected]> Signed-off-by: Puranjay Mohan <[email protected]>
1 parent 44bb17e commit 80a9e35

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

include/linux/sframe_lookup.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _LINUX_SFRAME_LOOKUP_H
3+
#define _LINUX_SFRAME_LOOKUP_H
4+
5+
/**
6+
* struct sframe_ip_entry - sframe unwind info for given ip
7+
* @cfa_offset: Offset for the Canonical Frame Address(CFA) from Frame
8+
* Pointer(FP) or Stack Pointer(SP)
9+
* @ra_offset: Offset for the Return Address from CFA.
10+
* @fp_offset: Offset for the Frame Pointer (FP) from CFA.
11+
* @use_fp: Use FP to get next CFA or not
12+
*/
13+
struct sframe_ip_entry {
14+
int32_t cfa_offset;
15+
int32_t ra_offset;
16+
int32_t fp_offset;
17+
bool use_fp;
18+
};
19+
20+
/**
21+
* struct sframe_table - sframe struct of a table
22+
* @sfhdr_p: Pointer to sframe header
23+
* @fde_p: Pointer to the first of sframe frame description entry(FDE).
24+
* @fre_p: Pointer to the first of sframe frame row entry(FRE).
25+
*/
26+
struct sframe_table {
27+
struct sframe_header *sfhdr_p;
28+
struct sframe_fde *fde_p;
29+
char *fre_p;
30+
};
31+
32+
#ifdef CONFIG_SFRAME_UNWINDER
33+
void init_sframe_table(void);
34+
int sframe_find_pc(unsigned long pc, struct sframe_ip_entry *entry);
35+
#else
36+
static inline void init_sframe_table(void) {}
37+
static inline int sframe_find_pc(unsigned long pc, struct sframe_ip_entry *entry)
38+
{
39+
return -EINVAL;
40+
}
41+
#endif
42+
43+
#endif /* _LINUX_SFRAME_LOOKUP_H */

kernel/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
131131

132132
obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o
133133
obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
134+
obj-$(CONFIG_SFRAME_UNWINDER) += sframe_lookup.o
134135

135136
CFLAGS_stackleak.o += $(DISABLE_STACKLEAK_PLUGIN)
136137
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o

kernel/sframe_lookup.c

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
3+
#define pr_fmt(fmt) "sframe: " fmt
4+
5+
#include <linux/module.h>
6+
#include <linux/sort.h>
7+
#include <linux/sframe_lookup.h>
8+
#include <linux/kallsyms.h>
9+
#include "sframe.h"
10+
11+
extern char __start_sframe_header[];
12+
extern char __stop_sframe_header[];
13+
14+
static bool sframe_init __ro_after_init;
15+
static struct sframe_table sftbl;
16+
17+
#define SFRAME_READ_TYPE(in, out, type) \
18+
({ \
19+
type __tmp; \
20+
memcpy(&__tmp, in, sizeof(__tmp)); \
21+
in += sizeof(__tmp); \
22+
out = __tmp; \
23+
})
24+
25+
#define SFRAME_READ_ROW_ADDR(in_addr, out_addr, type) \
26+
({ \
27+
switch (type) { \
28+
case SFRAME_FRE_TYPE_ADDR1: \
29+
SFRAME_READ_TYPE(in_addr, out_addr, u8); \
30+
break; \
31+
case SFRAME_FRE_TYPE_ADDR2: \
32+
SFRAME_READ_TYPE(in_addr, out_addr, u16); \
33+
break; \
34+
case SFRAME_FRE_TYPE_ADDR4: \
35+
SFRAME_READ_TYPE(in_addr, out_addr, u32); \
36+
break; \
37+
default: \
38+
break; \
39+
} \
40+
})
41+
42+
#define SFRAME_READ_ROW_OFFSETS(in_addr, out_addr, size) \
43+
({ \
44+
switch (size) { \
45+
case 1: \
46+
SFRAME_READ_TYPE(in_addr, out_addr, s8); \
47+
break; \
48+
case 2: \
49+
SFRAME_READ_TYPE(in_addr, out_addr, s16); \
50+
break; \
51+
case 4: \
52+
SFRAME_READ_TYPE(in_addr, out_addr, s32); \
53+
break; \
54+
default: \
55+
break; \
56+
} \
57+
})
58+
59+
static struct sframe_fde *find_fde(const struct sframe_table *tbl, unsigned long pc)
60+
{
61+
int l, r, m, f;
62+
int32_t ip;
63+
struct sframe_fde *fdep;
64+
65+
if (!tbl || !tbl->sfhdr_p || !tbl->fde_p)
66+
return NULL;
67+
68+
ip = (pc - (unsigned long)tbl->sfhdr_p);
69+
70+
/* Do a binary range search to find the rightmost FDE start_addr < ip */
71+
l = m = f = 0;
72+
r = tbl->sfhdr_p->num_fdes;
73+
while (l < r) {
74+
m = l + ((r - l) / 2);
75+
fdep = tbl->fde_p + m;
76+
if (fdep->start_addr > ip)
77+
r = m;
78+
else
79+
l = m + 1;
80+
}
81+
/* use l - 1 because l will be the first item fdep->start_addr > ip */
82+
f = l - 1;
83+
if (f >= tbl->sfhdr_p->num_fdes || f < 0)
84+
return NULL;
85+
fdep = tbl->fde_p + f;
86+
if (ip < fdep->start_addr || ip >= fdep->start_addr + fdep->size)
87+
return NULL;
88+
89+
return fdep;
90+
}
91+
92+
static int find_fre(const struct sframe_table *tbl, unsigned long pc,
93+
const struct sframe_fde *fdep, struct sframe_ip_entry *entry)
94+
{
95+
int i, offset_size, offset_count;
96+
char *fres, *offsets_loc;
97+
int32_t ip_off;
98+
uint32_t next_row_ip_off;
99+
uint8_t fre_info, fde_type = SFRAME_FUNC_FDE_TYPE(fdep->info),
100+
fre_type = SFRAME_FUNC_FRE_TYPE(fdep->info);
101+
102+
fres = tbl->fre_p + fdep->fres_off;
103+
104+
/* Whether PCs in the FREs should be treated as masks or not */
105+
if (fde_type == SFRAME_FDE_TYPE_PCMASK)
106+
ip_off = pc % fdep->rep_size;
107+
else
108+
ip_off = (int32_t)(pc - (unsigned long)tbl->sfhdr_p) - fdep->start_addr;
109+
110+
if (ip_off < 0 || ip_off >= fdep->size)
111+
return -EINVAL;
112+
113+
/*
114+
* FRE structure starts by address of the entry with variants length. Use
115+
* two pointers to track current head(fres) and the address of last
116+
* offset(offsets_loc)
117+
*/
118+
for (i = 0; i < fdep->fres_num; i++) {
119+
SFRAME_READ_ROW_ADDR(fres, next_row_ip_off, fre_type);
120+
if (ip_off < next_row_ip_off)
121+
break;
122+
SFRAME_READ_TYPE(fres, fre_info, u8);
123+
offsets_loc = fres;
124+
/*
125+
* jump to the start of next fre
126+
* fres += fre_offets_cnt*offset_size
127+
*/
128+
fres += SFRAME_FRE_OFFSET_COUNT(fre_info) << SFRAME_FRE_OFFSET_SIZE(fre_info);
129+
}
130+
131+
offset_size = 1 << SFRAME_FRE_OFFSET_SIZE(fre_info);
132+
offset_count = SFRAME_FRE_OFFSET_COUNT(fre_info);
133+
134+
if (offset_count > 0) {
135+
SFRAME_READ_ROW_OFFSETS(offsets_loc, entry->cfa_offset, offset_size);
136+
offset_count--;
137+
}
138+
if (offset_count > 0 && !entry->ra_offset) {
139+
SFRAME_READ_ROW_OFFSETS(offsets_loc, entry->ra_offset, offset_size);
140+
offset_count--;
141+
}
142+
if (offset_count > 0 && !entry->fp_offset) {
143+
SFRAME_READ_ROW_OFFSETS(offsets_loc, entry->fp_offset, offset_size);
144+
offset_count--;
145+
}
146+
if (offset_count)
147+
return -EINVAL;
148+
149+
entry->use_fp = SFRAME_FRE_CFA_BASE_REG_ID(fre_info) == SFRAME_BASE_REG_FP;
150+
151+
return 0;
152+
}
153+
154+
int sframe_find_pc(unsigned long pc, struct sframe_ip_entry *entry)
155+
{
156+
struct sframe_fde *fdep;
157+
struct sframe_table *sftbl_p = &sftbl;
158+
int err;
159+
160+
if (!sframe_init)
161+
return -EINVAL;
162+
163+
memset(entry, 0, sizeof(*entry));
164+
entry->ra_offset = sftbl_p->sfhdr_p->cfa_fixed_ra_offset;
165+
entry->fp_offset = sftbl_p->sfhdr_p->cfa_fixed_fp_offset;
166+
167+
fdep = find_fde(sftbl_p, pc);
168+
if (!fdep)
169+
return -EINVAL;
170+
err = find_fre(sftbl_p, pc, fdep, entry);
171+
if (err)
172+
return err;
173+
174+
return 0;
175+
}
176+
177+
void __init init_sframe_table(void)
178+
{
179+
size_t sframe_size = (void *)__stop_sframe_header - (void *)__start_sframe_header;
180+
void *sframe_buf = __start_sframe_header;
181+
182+
if (sframe_size <= 0)
183+
return;
184+
sftbl.sfhdr_p = sframe_buf;
185+
if (!sftbl.sfhdr_p || sftbl.sfhdr_p->preamble.magic != SFRAME_MAGIC ||
186+
sftbl.sfhdr_p->preamble.version != SFRAME_VERSION_2 ||
187+
!(sftbl.sfhdr_p->preamble.flags & SFRAME_F_FDE_SORTED)) {
188+
pr_warn("WARNING: Unable to read sframe header. Disabling unwinder.\n");
189+
return;
190+
}
191+
192+
sftbl.fde_p = (struct sframe_fde *)(__start_sframe_header + SFRAME_HDR_SIZE(*sftbl.sfhdr_p)
193+
+ sftbl.sfhdr_p->fdes_off);
194+
sftbl.fre_p = __start_sframe_header + SFRAME_HDR_SIZE(*sftbl.sfhdr_p)
195+
+ sftbl.sfhdr_p->fres_off;
196+
sframe_init = true;
197+
}

0 commit comments

Comments
 (0)