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