xref: /illumos-gate/usr/src/lib/libc/amd64/unwind/eh_frame.c (revision d48be21240dfd051b689384ce2b23479d757f2d8)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * interface used by unwind support to query frame descriptor info
29  */
30 
31 #ifndef _LIBCRUN_
32 #include "lint.h"
33 #endif
34 #include <sys/types.h>
35 #include <limits.h>
36 #include "stack_unwind.h"
37 #include "unwind_context.h"
38 #include <dlfcn.h>
39 
40 /*
41  * CIE:
42  *	UNUM32		length
43  *	UNUM32		ID
44  *	UNUM8		version
45  *	ZTSTRING	augmentation
46  *	ULEB128		Code Align Factor
47  *	SLEB128		Data Align Factor
48  *	UNUM8		RA
49  *	ULEB128		length
50  *	UNUM8		personality enc
51  *	ADDR		personality
52  *	UNUM8		code_enc
53  *	UNUM8		lsda_enc
54  *
55  * FDE:
56  *	UNUM32		length
57  *	UNUM32		ID
58  *	ADDR		initial loc
59  *	SIZE		size
60  *	ULEB128		length
61  *	ADDR		lsda
62  */
63 
64 
65 struct eh_frame_fields *
66 _Unw_Decode_FDE(struct eh_frame_fields *f, struct _Unwind_Context *ctx)
67 {
68 	void *fde_data;    /* location in this process of fde */
69 	void *fde_end;
70 	void *data;
71 	ptrdiff_t reloc;
72 	uintptr_t base;
73 	void *cie_data;    /* location in this process of cie */
74 	void *cie_end;
75 	void *cdata;
76 	ptrdiff_t creloc;
77 	int lsda_enc = 0;
78 	int per_enc = 0;
79 	int code_enc = 0;
80 	char augment[8];
81 	char *p;
82 	uint64_t scratch;
83 
84 	uint64_t func = 0;
85 	uint64_t range = 0;
86 	_Unwind_Personality_Fn	pfn = 0;
87 	void* lsda = 0;
88 
89 	/* here is where data mapping would happen ??REMOTE?? */
90 	fde_data = ctx->fde;
91 	data = fde_data;
92 	fde_end = (void *)(((intptr_t)fde_data) + 4 +
93 	    _Unw_get_val(&data, 0, UNUM32, 1, 1, 0));
94 	reloc = 0;
95 	base = ((intptr_t)data) + reloc;
96 	cie_data = (void *)(base -  _Unw_get_val(&data, 0, UNUM32, 1, 1, 0));
97 	cdata = cie_data;
98 	cie_end = (void *)(((intptr_t)cie_data) + 4 +
99 	    _Unw_get_val(&cdata, 0, UNUM32, 1, 1, 0));
100 	creloc = 0;
101 	/* data mapping has happened */
102 
103 	f->cie_ops_end = cie_end;
104 	f->cie_reloc = creloc;
105 	f->fde_ops_end = fde_end;
106 	f->fde_reloc = reloc;
107 
108 	(void) _Unw_get_val(&cdata, creloc, UNUM32, 1, 1, 0);
109 	(void) _Unw_get_val(&cdata, creloc, UNUM8, 1, 1, 0);
110 	/* LINTED alignment */
111 	(*((uint64_t *)(&(augment[0]))))  =
112 	    _Unw_get_val(&cdata, creloc, ZTSTRING, 1, 1, 0);
113 	f->code_align = _Unw_get_val(&cdata, creloc, ULEB128, 1, 1, 0);
114 	f->data_align = _Unw_get_val(&cdata, creloc, SLEB128, 1, 1, 0);
115 	(void) _Unw_get_val(&cdata, creloc, UNUM8, 1, 1, 0);
116 	if (augment[0] == 'z' &&
117 	    (scratch = _Unw_get_val(&cdata, creloc, ULEB128, 1, 1, 0)) != 0) {
118 		for (p = &(augment[1]); *p != 0; p++) {
119 			switch (*p) {
120 			case 'P':
121 				per_enc = _Unw_get_val(&cdata, creloc,
122 				    UNUM8, 1, 1, 0);
123 				if (per_enc == 0)
124 					per_enc = 0x4;
125 				pfn = (_Unwind_Personality_Fn)
126 				    _Unw_get_val(&cdata, creloc,
127 				    ADDR, 1, 1, per_enc);
128 				break;
129 			case 'R':
130 				code_enc = _Unw_get_val(&cdata, creloc,
131 				    UNUM8, 1, 1, 0);
132 				break;
133 			case 'L':
134 				lsda_enc = _Unw_get_val(&cdata, creloc,
135 				    UNUM8, 1, 1, 0);
136 				break;
137 			}
138 		}
139 	}
140 	if (code_enc == 0)
141 		code_enc = 0x4;
142 
143 	func = _Unw_get_val(&data, reloc, ADDR, 1, 1, code_enc);
144 	range = _Unw_get_val(&data, reloc, SIZE, 1, 1, code_enc);
145 	if ((ctx->pc < func) || (ctx->pc > (func+range)))
146 		return (0);
147 	ctx->func = func;
148 	ctx->range = range;
149 	if (augment[0] == 'z') {
150 		scratch = _Unw_get_val(&data, reloc, ULEB128, 1, 1, 0);
151 		if (scratch == 4 && lsda_enc) {
152 			/*
153 			 * without the two work-arounds test would be
154 			 * (scratch > 0 & lsda_enc)
155 			 */
156 			lsda = (void *)_Unw_get_val(&data, reloc,
157 			    ADDR, 1, 1, lsda_enc);
158 		} else if (scratch == 4) {
159 			/*
160 			 * 11/24/04 compiler is sometimes not outputing
161 			 * lsda_enc
162 			 */
163 			lsda = (void*)_Unw_get_val(&data, reloc,
164 			    ADDR, 1, 1, 0x1b);
165 		} else if (scratch == 8) {
166 			/*
167 			 * 11/12/04 - compiler is putting out relative
168 			 * encoding byte and absolute data - inconsistancy
169 			 * is caught here.
170 			 */
171 			lsda = (void *)_Unw_get_val(&data, reloc,
172 			    ADDR, 1, 1, 0x4);
173 		}
174 	}
175 	if (pfn)
176 		ctx->pfn = pfn;
177 	if (lsda)
178 		ctx->lsda = lsda;
179 	f->fde_ops = data;
180 	f->cie_ops = cdata;
181 	f->code_enc = code_enc;
182 	return (f);
183 }
184 
185 static int
186 table_ent_log_size(int enc)
187 {
188 	int val = enc & 0xf;
189 	int res;
190 
191 	switch (val) {
192 	case 0x3:
193 		res = 3;
194 		break;
195 	case 0x04:
196 		res = 4;
197 		break;
198 	case 0x0b:
199 		res = 3;
200 		break;
201 	case 0x0c:
202 		res = 4;
203 		break;
204 	default:
205 		break;
206 	}
207 	return (res);
208 }
209 
210 static void
211 get_table_ent_val(unsigned char *data, unsigned char *data_end,
212 	int enc, ptrdiff_t reloc, uintptr_t base,
213 	uint64_t *codep, uint64_t *next_codep, void **fdep)
214 {
215 	int val = enc & 0xf;
216 	int rel = (enc >> 4) & 0xf;
217 	unsigned char *second = data;
218 	unsigned char *third = data;
219 	uint64_t code;
220 	void *fde;
221 	uint64_t next_code;
222 
223 	switch (val) {
224 	case 0x3:
225 		/* LINTED alignment */
226 		code = (uint64_t)(*((uint32_t *)data));
227 		second += 4;
228 		/* LINTED alignment */
229 		fde = (void *)(uint64_t)(*((uint32_t *)second));
230 		third += 8;
231 		next_code = (third >= data_end)? ULONG_MAX :
232 		    /* LINTED alignment */
233 		    (uint64_t)(*((uint32_t *)third));
234 		break;
235 	case 0x04:
236 		/* LINTED alignment */
237 		code = (uint64_t)(*((uint64_t *)data));
238 		second += 8;
239 		/* LINTED alignment */
240 		fde = (void *)(uint64_t)(*((uint64_t *)second));
241 		third += 16;
242 		next_code = (third >= data_end)? ULONG_MAX :
243 		    /* LINTED alignment */
244 		    (uint64_t)(*((uint64_t *)third));
245 		break;
246 	case 0x0b:
247 		/* LINTED alignment */
248 		code = (uint64_t)(int64_t)(*((int32_t *)data));
249 		second += 4;
250 		/* LINTED alignment */
251 		fde = (void *)(uint64_t)(int64_t)(*((int32_t *)second));
252 		third += 8;
253 		next_code = (third >= data_end)? ULONG_MAX :
254 		    /* LINTED alignment */
255 		    (uint64_t)(int64_t)(*((int32_t *)third));
256 		break;
257 	case 0x0c:
258 		/* LINTED alignment */
259 		code = (uint64_t)(*((int64_t *)data));
260 		second += 8;
261 		/* LINTED alignment */
262 		fde = (void *)(uint64_t)(*((int64_t *)second));
263 		third += 16;
264 		next_code = (third >= data_end)? ULONG_MAX :
265 		    /* LINTED alignment */
266 		    (uint64_t)(*((int64_t *)third));
267 		break;
268 	}
269 
270 	switch (rel) {
271 	case 0:
272 		break;
273 	case 1:
274 		code += (uint64_t)data + reloc;
275 		fde = (void *)(((uint64_t)fde) + (uint64_t)second + reloc);
276 		if (next_code != ULONG_MAX)
277 			next_code += (uint64_t)third + reloc;
278 		break;
279 	case 3:
280 		code += base;
281 		fde = (void *)(((uint64_t)fde) +  base);
282 		if (next_code != ULONG_MAX)
283 			next_code += base;
284 		break;
285 	default:
286 		/* remainder not implemented */
287 		break;
288 	}
289 	*codep = code;
290 	*fdep = fde;
291 	*next_codep = next_code;
292 }
293 
294 
295 static void *
296 locate_fde_for_pc(uint64_t pc, int enc,
297 	unsigned char *table, unsigned char *table_end,
298 	ptrdiff_t reloc, uintptr_t base);
299 
300 /*
301  * Search the eh_frame info with a given pc.  Return a pointer to a
302  * FDE.  The search is performed in two stages.
303  * First rtld.so identifies the load module containing the target location.
304  * This returns the appropiate eh_frame_hdr, and a binary search is
305  * then performed on the eh_frame_hdr to locate the entry with
306  * a matching pc value.
307  */
308 void *
309 _Unw_EhfhLookup(struct _Unwind_Context *ctx)
310 {
311 	Dl_amd64_unwindinfo dlef;
312 	void* data;
313 	void* data_end;
314 	uint64_t pc = ctx->pc;
315 	int fp_enc, fc_enc, ft_enc;
316 	unsigned char *pi, *pj;
317 	ptrdiff_t reloc;
318 	uintptr_t base;
319 
320 	dlef.dlui_version = 1;
321 
322 	/* Locate the appropiate exception_range_entry table first */
323 	if (0 == dlamd64getunwind((void*)pc, &dlef)) {
324 		return (0);
325 	}
326 
327 	/*
328 	 * you now know size and position of block of data needed for
329 	 * binary search ??REMOTE??
330 	 */
331 	data = dlef.dlui_unwindstart;
332 	if (0 == data)
333 		return (0);
334 	base = (uintptr_t)data;
335 	data_end = dlef.dlui_unwindend;
336 	reloc = 0;
337 	/* ??REMOTE?? */
338 
339 	(void) _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
340 	fp_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
341 	fc_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
342 	ft_enc = _Unw_get_val(&data, reloc, UNUM8, 1, 1, 0);
343 	(void) _Unw_get_val(&data, reloc, ADDR, 1, 1, fp_enc);
344 	(void) _Unw_get_val(&data, reloc, SIZE, 1, 1, fc_enc);
345 	pi = data;
346 	pj = data_end;
347 	ctx->fde = locate_fde_for_pc(pc, ft_enc, pi,  pj, reloc, base);
348 	return ((void *)(ctx->fde));
349 }
350 
351 static void *
352 locate_fde_for_pc(uint64_t pc, int enc,
353 	unsigned char *table_bg, unsigned char *table_end,
354 	ptrdiff_t reloc, uintptr_t base)
355 {
356 	unsigned char *pi = table_bg;
357 	unsigned char *pj = table_end;
358 	uint64_t range_start, range_end;
359 	void* fde;
360 	int log_size = table_ent_log_size(enc);
361 
362 	/*
363 	 * Invariant -- if there is a containing range,
364 	 * it must lie in the interval [pi,pj).  That is,
365 	 * pi <= p < pj, if p exists.
366 	 */
367 	while (pi < pj) {
368 		unsigned char *pr =
369 		    pi + (((pj - pi) >> (log_size + 1)) << log_size);
370 				/* Don't use (pi+pj)>>1 */
371 		get_table_ent_val(pr, table_end, enc, reloc, base,
372 		    &range_start, &range_end, &fde);
373 
374 		/* Return fde if tpc is in this range. */
375 
376 		if (range_start <= pc && pc < range_end) {
377 			return ((void*) fde);
378 		}
379 
380 		if (range_start < pc)
381 			pi = pr + (1 << log_size);
382 		else
383 			pj = pr;
384 	}
385 	return (0);
386 }
387