1/*-
2 * Copyright (c) 2003, 2004 Marcel Moolenaar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/param.h>
31#include <sys/kdb.h>
32#include <sys/kernel.h>
33#include <sys/systm.h>
34#include <sys/malloc.h>
35#include <sys/queue.h>
36
37#include <machine/frame.h>
38#include <machine/md_var.h>
39#include <machine/pcb.h>
40#include <machine/unwind.h>
41
42#include <uwx.h>
43
44static MALLOC_DEFINE(M_UNWIND, "Unwind", "Unwind information");
45
46struct unw_entry {
47	uint64_t	ue_start;	/* procedure start */
48	uint64_t	ue_end;		/* procedure end */
49	uint64_t	ue_info;	/* offset to procedure descriptors */
50};
51
52struct unw_table {
53	LIST_ENTRY(unw_table) ut_link;
54	uint64_t	ut_base;
55	uint64_t	ut_limit;
56	struct unw_entry *ut_start;
57	struct unw_entry *ut_end;
58};
59
60LIST_HEAD(unw_table_list, unw_table);
61
62static struct unw_table_list unw_tables;
63
64#ifdef KDB
65#define	KDBHEAPSZ	8192
66
67struct mhdr {
68	uint32_t	sig;
69#define	MSIG_FREE	0x65657246	/* "Free". */
70#define	MSIG_USED	0x64657355	/* "Used". */
71	uint32_t	size;
72	int32_t		next;
73	int32_t		prev;
74};
75
76static struct mhdr *kdbheap;
77#endif /* KDB */
78
79static void *
80unw_alloc(size_t sz)
81{
82#ifdef KDB
83	struct mhdr *hdr, *hfree;
84
85	if (kdb_active) {
86		sz = (sz + 15) >> 4;
87		hdr = kdbheap;
88		while (hdr->sig != MSIG_FREE || hdr->size < sz) {
89			if (hdr->next == -1)
90				return (NULL);
91			hdr = kdbheap + hdr->next;
92		}
93		if (hdr->size > sz + 1) {
94			hfree = hdr + sz + 1;
95			hfree->sig = MSIG_FREE;
96			hfree->size = hdr->size - sz - 1;
97			hfree->prev = hdr - kdbheap;
98			hfree->next = hdr->next;
99			hdr->size = sz;
100			hdr->next = hfree - kdbheap;
101			if (hfree->next >= 0) {
102				hfree = kdbheap + hfree->next;
103				hfree->prev = hdr->next;
104			}
105		}
106		hdr->sig = MSIG_USED;
107		return (void*)(hdr + 1);
108	}
109#endif
110	return (malloc(sz, M_UNWIND, M_NOWAIT));
111}
112
113static void
114unw_free(void *p)
115{
116#ifdef KDB
117	struct mhdr *hdr, *hfree;
118
119	if (kdb_active) {
120		hdr = (struct mhdr*)p - 1;
121		if (hdr->sig != MSIG_USED)
122			return;
123		hdr->sig = MSIG_FREE;
124		if (hdr->prev >= 0 && kdbheap[hdr->prev].sig == MSIG_FREE) {
125			hfree = kdbheap + hdr->prev;
126			hfree->size += hdr->size + 1;
127			hfree->next = hdr->next;
128			if (hdr->next >= 0) {
129				hfree = kdbheap + hdr->next;
130				hfree->prev = hdr->prev;
131			}
132		} else if (hdr->next >= 0 &&
133		    kdbheap[hdr->next].sig == MSIG_FREE) {
134			hfree = kdbheap + hdr->next;
135			hdr->size += hfree->size + 1;
136			hdr->next = hfree->next;
137			if (hdr->next >= 0) {
138				hfree = kdbheap + hdr->next;
139				hfree->prev = hdr - kdbheap;
140			}
141		}
142		return;
143	}
144#endif
145	free(p, M_UNWIND);
146}
147
148static struct unw_table *
149unw_table_lookup(uint64_t ip)
150{
151	struct unw_table *ut;
152
153	LIST_FOREACH(ut, &unw_tables, ut_link) {
154		if (ip >= ut->ut_base && ip < ut->ut_limit)
155			return (ut);
156	}
157	return (NULL);
158}
159
160static uint64_t
161unw_copyin_from_frame(struct trapframe *tf, uint64_t from)
162{
163	uint64_t val;
164	int reg;
165
166	if (from == UWX_REG_AR_PFS)
167		val = tf->tf_special.pfs;
168	else if (from == UWX_REG_PREDS)
169		val = tf->tf_special.pr;
170	else if (from == UWX_REG_AR_RNAT)
171		val = tf->tf_special.rnat;
172	else if (from == UWX_REG_AR_UNAT)
173		val = tf->tf_special.unat;
174	else if (from >= UWX_REG_GR(0) && from <= UWX_REG_GR(127)) {
175		reg = from - UWX_REG_GR(0);
176		if (reg == 1)
177			val = tf->tf_special.gp;
178		else if (reg == 12)
179			val = tf->tf_special.sp;
180		else if (reg == 13)
181			val = tf->tf_special.tp;
182		else if (reg >= 2 && reg <= 3)
183			val = (&tf->tf_scratch.gr2)[reg - 2];
184		else if (reg >= 8 && reg <= 11)
185			val = (&tf->tf_scratch.gr8)[reg - 8];
186		else if (reg >= 14 && reg <= 31)
187			val = (&tf->tf_scratch.gr14)[reg - 14];
188		else
189			goto oops;
190	} else if (from >= UWX_REG_BR(0) && from <= UWX_REG_BR(7)) {
191		reg = from - UWX_REG_BR(0);
192		if (reg == 0)
193			val = tf->tf_special.rp;
194		else if (reg >= 6 && reg <= 7)
195			val = (&tf->tf_scratch.br6)[reg - 6];
196		else
197			goto oops;
198	} else
199		goto oops;
200	return (val);
201
202 oops:
203	printf("UNW: %s(%p, %lx)\n", __func__, tf, from);
204	return (0UL);
205}
206
207static uint64_t
208unw_copyin_from_pcb(struct pcb *pcb, uint64_t from)
209{
210	uint64_t val;
211	int reg;
212
213	if (from == UWX_REG_AR_PFS)
214		val = pcb->pcb_special.pfs;
215	else if (from == UWX_REG_PREDS)
216		val = pcb->pcb_special.pr;
217	else if (from == UWX_REG_AR_RNAT)
218		val = pcb->pcb_special.rnat;
219	else if (from == UWX_REG_AR_UNAT)
220		val = pcb->pcb_special.unat;
221	else if (from >= UWX_REG_GR(0) && from <= UWX_REG_GR(127)) {
222		reg = from - UWX_REG_GR(0);
223		if (reg == 1)
224			val = pcb->pcb_special.gp;
225		else if (reg == 12)
226			val = pcb->pcb_special.sp;
227		else if (reg == 13)
228			val = pcb->pcb_special.tp;
229		else if (reg >= 4 && reg <= 7)
230			val = (&pcb->pcb_preserved.gr4)[reg - 4];
231		else
232			goto oops;
233	} else if (from >= UWX_REG_BR(0) && from <= UWX_REG_BR(7)) {
234		reg = from - UWX_REG_BR(0);
235		if (reg == 0)
236			val = pcb->pcb_special.rp;
237		else if (reg >= 1 && reg <= 5)
238			val = (&pcb->pcb_preserved.br1)[reg - 1];
239		else
240			goto oops;
241	} else
242		goto oops;
243	return (val);
244
245 oops:
246	printf("UNW: %s(%p, %lx)\n", __func__, pcb, from);
247	return (0UL);
248}
249
250static int
251unw_cb_copyin(int req, char *to, uint64_t from, int len, intptr_t tok)
252{
253	struct unw_regstate *rs = (void*)tok;
254	uint64_t val;
255
256	switch (req) {
257	case UWX_COPYIN_UINFO:
258		break;
259	case UWX_COPYIN_MSTACK:
260		*((uint64_t*)to) = *((uint64_t*)from);
261		return (8);
262	case UWX_COPYIN_RSTACK:
263		*((uint64_t*)to) = *((uint64_t*)from);
264		return (8);
265	case UWX_COPYIN_REG:
266		if (rs->frame != NULL)
267			val = unw_copyin_from_frame(rs->frame, from);
268		else if (rs->pcb != NULL)
269			val = unw_copyin_from_pcb(rs->pcb, from);
270		else
271			goto oops;
272		*((uint64_t*)to) = val;
273		return (len);
274	}
275
276 oops:
277	printf("UNW: %s(%d, %p, %lx, %d, %lx)\n", __func__, req, to, from,
278	    len, tok);
279	return (0);
280}
281
282static int
283unw_cb_lookup(int req, uint64_t ip, intptr_t tok, uint64_t **vec)
284{
285	struct unw_regstate *rs = (void*)tok;
286	struct unw_table *ut;
287
288	switch (req) {
289	case UWX_LKUP_LOOKUP:
290		ut = unw_table_lookup(ip);
291		if (ut == NULL)
292			return (UWX_LKUP_NOTFOUND);
293		rs->keyval[0] = UWX_KEY_TBASE;
294		rs->keyval[1] = ut->ut_base;
295		rs->keyval[2] = UWX_KEY_USTART;
296		rs->keyval[3] = (intptr_t)ut->ut_start;
297		rs->keyval[4] = UWX_KEY_UEND;
298		rs->keyval[5] = (intptr_t)ut->ut_end;
299		rs->keyval[6] = 0;
300		rs->keyval[7] = 0;
301		*vec = rs->keyval;
302		return (UWX_LKUP_UTABLE);
303	case UWX_LKUP_FREE:
304		return (0);
305	}
306
307	return (UWX_LKUP_ERR);
308}
309
310int
311unw_create_from_frame(struct unw_regstate *rs, struct trapframe *tf)
312{
313	uint64_t bsp, ip;
314	int uwxerr;
315
316	rs->frame = tf;
317	rs->pcb = NULL;
318	rs->env = uwx_init();
319	if (rs->env == NULL)
320		return (ENOMEM);
321
322	uwxerr = uwx_register_callbacks(rs->env, (intptr_t)rs,
323	    unw_cb_copyin, unw_cb_lookup);
324	if (uwxerr)
325		return (EINVAL);		/* XXX */
326
327	bsp = tf->tf_special.bspstore + tf->tf_special.ndirty;
328	bsp = ia64_bsp_adjust(bsp, -IA64_CFM_SOF(tf->tf_special.cfm));
329	ip = tf->tf_special.iip + ((tf->tf_special.psr >> 41) & 3);
330
331	uwxerr = uwx_init_context(rs->env, ip, tf->tf_special.sp, bsp,
332	    tf->tf_special.cfm);
333
334	return ((uwxerr) ? EINVAL : 0);		/* XXX */
335}
336
337int
338unw_create_from_pcb(struct unw_regstate *rs, struct pcb *pcb)
339{
340	uint64_t bsp, cfm, ip;
341	int uwxerr;
342
343	rs->frame = NULL;
344	rs->pcb = pcb;
345	rs->env = uwx_init();
346	if (rs->env == NULL)
347		return (ENOMEM);
348
349	uwxerr = uwx_register_callbacks(rs->env, (intptr_t)rs,
350	    unw_cb_copyin, unw_cb_lookup);
351	if (uwxerr)
352		return (EINVAL);		/* XXX */
353
354	bsp = pcb->pcb_special.bspstore;
355	if (pcb->pcb_special.__spare == ~0UL) {
356		ip = pcb->pcb_special.iip + ((pcb->pcb_special.psr >> 41) & 3);
357		cfm = pcb->pcb_special.cfm;
358		bsp += pcb->pcb_special.ndirty;
359		bsp = ia64_bsp_adjust(bsp, -IA64_CFM_SOF(cfm));
360	} else {
361		ip = pcb->pcb_special.rp;
362		cfm = pcb->pcb_special.pfs;
363		bsp = ia64_bsp_adjust(bsp, -IA64_CFM_SOL(cfm));
364	}
365	uwxerr = uwx_init_context(rs->env, ip, pcb->pcb_special.sp, bsp, cfm);
366
367	return ((uwxerr) ? EINVAL : 0);		/* XXX */
368}
369
370void
371unw_delete(struct unw_regstate *rs)
372{
373
374	if (rs->env != NULL)
375		uwx_free(rs->env);
376}
377
378int
379unw_step(struct unw_regstate *rs)
380{
381	int err;
382
383	switch (uwx_step(rs->env)) {
384	case UWX_ABI_FRAME:
385		err = ERESTART;
386		break;
387	case UWX_BOTTOM:
388		err = EJUSTRETURN;
389		break;
390	case UWX_OK:
391		err = 0;
392		break;
393	default:
394		err = EINVAL;		/* XXX */
395		break;
396	}
397	return (err);
398}
399
400int
401unw_get_bsp(struct unw_regstate *s, uint64_t *r)
402{
403	int uwxerr;
404
405	uwxerr = uwx_get_reg(s->env, UWX_REG_BSP, r);
406	return ((uwxerr) ? EINVAL : 0); 	/* XXX */
407}
408
409int
410unw_get_cfm(struct unw_regstate *s, uint64_t *r)
411{
412	int uwxerr;
413
414	uwxerr = uwx_get_reg(s->env, UWX_REG_CFM, r);
415	return ((uwxerr) ? EINVAL : 0); 	/* XXX */
416}
417
418int
419unw_get_ip(struct unw_regstate *s, uint64_t *r)
420{
421	int uwxerr;
422
423	uwxerr = uwx_get_reg(s->env, UWX_REG_IP, r);
424	return ((uwxerr) ? EINVAL : 0); 	/* XXX */
425}
426
427int
428unw_get_sp(struct unw_regstate *s, uint64_t *r)
429{
430	int uwxerr;
431
432	uwxerr = uwx_get_reg(s->env, UWX_REG_SP, r);
433	return ((uwxerr) ? EINVAL : 0); 	/* XXX */
434}
435
436int
437unw_table_add(uint64_t base, uint64_t start, uint64_t end)
438{
439	struct unw_table *ut;
440
441	ut = malloc(sizeof(struct unw_table), M_UNWIND, M_WAITOK);
442	ut->ut_base = base;
443	ut->ut_start = (struct unw_entry*)start;
444	ut->ut_end = (struct unw_entry*)end;
445	ut->ut_limit = base + ut->ut_end[-1].ue_end;
446	LIST_INSERT_HEAD(&unw_tables, ut, ut_link);
447
448	if (bootverbose)
449		printf("UNWIND: table added: base=%lx, start=%lx, end=%lx\n",
450		    base, start, end);
451
452	return (0);
453}
454
455void
456unw_table_remove(uint64_t base)
457{
458	struct unw_table *ut;
459
460	ut = unw_table_lookup(base);
461	if (ut != NULL) {
462		LIST_REMOVE(ut, ut_link);
463		free(ut, M_UNWIND);
464		if (bootverbose)
465			printf("UNWIND: table removed: base=%lx\n", base);
466	}
467}
468
469static void
470unw_initialize(void *dummy __unused)
471{
472
473	LIST_INIT(&unw_tables);
474	uwx_register_alloc_cb(unw_alloc, unw_free);
475#ifdef KDB
476	kdbheap = malloc(KDBHEAPSZ, M_UNWIND, M_WAITOK);
477	kdbheap->sig = MSIG_FREE;
478	kdbheap->size = (KDBHEAPSZ - sizeof(struct mhdr)) >> 4;
479	kdbheap->next = -1;
480	kdbheap->prev = -1;
481#endif
482}
483SYSINIT(unwind, SI_SUB_KMEM, SI_ORDER_ANY, unw_initialize, 0);
484