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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26#include <stdio.h>
27#include <string.h>
28#include <libelf.h>
29#include "rdb.h"
30
31/*
32 * Given a symbol index, look up the corresponding symbol from the
33 * given symbol table.
34 *
35 * This function allows the caller to treat the symbol table as a single
36 * logical entity even though there may be 2 actual ELF symbol tables
37 * involved. See the comments in Pcontrol.h for details.
38 */
39static GElf_Sym *
40symtab_getsym(sym_tbl_t *symtab, int ndx, GElf_Sym *dst)
41{
42	/* If index is in range of primary symtab, look it up there */
43	if (ndx >= symtab->st_symn_aux) {
44		return (gelf_getsym(symtab->st_syms_pri,
45		    ndx - symtab->st_symn_aux, dst));
46	}
47
48	/* Not in primary: Look it up in the auxiliary symtab */
49	return (gelf_getsym(symtab->st_syms_aux, ndx, dst));
50}
51
52retc_t
53str_map_sym(const char *symname, map_info_t *mp, GElf_Sym *symptr, char **str)
54{
55	sym_tbl_t	*symp;
56	char		*strs;
57	int		i;
58
59	if (mp->mi_symtab.st_syms_pri)
60		symp = &(mp->mi_symtab);
61	else if (mp->mi_dynsym.st_syms_pri)
62		symp = &(mp->mi_dynsym);
63	else
64		return (RET_FAILED);
65
66	strs = symp->st_strs;
67
68	for (i = 0; i < (int)symp->st_symn; i++) {
69		GElf_Sym sym;
70
71		if (symtab_getsym(symp, i, &sym) == NULL) {
72			(void) printf("symtab_getsym(): %s\n", elf_errmsg(-1));
73			return (RET_FAILED);
74		}
75
76		if (sym.st_name == 0)
77			continue;
78		if ((sym.st_shndx == SHN_UNDEF) ||
79		    (strcmp(strs + sym.st_name, symname) != 0))
80			continue;
81		*symptr = sym;
82		if (str != NULL)
83			*str = (char *)strs + symptr->st_name;
84		if ((mp->mi_flags & FLG_MI_EXEC) == 0)
85			symptr->st_value += (GElf_Addr)(mp->mi_addr);
86		return (RET_OK);
87	}
88
89	return (RET_FAILED);
90}
91
92/*
93 * If two syms are of equal value this routine will
94 * favor one over the other based off of it's symbol
95 * type.
96 */
97static GElf_Sym
98sym_swap(GElf_Sym * s1, GElf_Sym * s2)
99{
100	int	t1 = GELF_ST_TYPE(s1->st_info);
101	int	t2 = GELF_ST_TYPE(s2->st_info);
102
103	if ((t1 == STT_FUNC) || (t2 == STT_FUNC)) {
104		if (t1 == STT_FUNC)
105			return (*s1);
106		return (*s2);
107	}
108
109	if ((t1 == STT_OBJECT) || (t2 == STT_OBJECT)) {
110		if (t1 == STT_OBJECT)
111			return (*s1);
112		return (*s2);
113	}
114
115	if ((t1 == STT_OBJECT) || (t2 == STT_OBJECT)) {
116		if (t1 == STT_OBJECT)
117			return (*s1);
118		return (*s2);
119	}
120	return (*s1);
121}
122
123static retc_t
124addr_map_sym(map_info_t *mp, ulong_t addr, GElf_Sym *symptr, char **str)
125{
126	sym_tbl_t	*symp;
127	GElf_Sym	sym;
128	GElf_Sym	*symr = NULL;
129	GElf_Sym	*lsymr = NULL;
130	GElf_Sym	rsym;
131	GElf_Sym	lsym;
132	ulong_t		baseaddr = 0;
133	int		i;
134
135	if ((mp->mi_flags & FLG_MI_EXEC) == 0)
136		baseaddr = (ulong_t)mp->mi_addr;
137
138	if (mp->mi_symtab.st_syms_pri)
139		symp = &(mp->mi_symtab);
140	else if (mp->mi_dynsym.st_syms_pri)
141		symp = &(mp->mi_dynsym);
142	else
143		return (RET_FAILED);
144
145	/*
146	 * normalize address
147	 */
148	addr -= baseaddr;
149	for (i = 0; i < (int)symp->st_symn; i++) {
150		ulong_t	svalue;
151
152		if (symtab_getsym(symp, i, &sym) == NULL) {
153			(void) printf("symtab_getsym(): %s\n", elf_errmsg(-1));
154			return (RET_FAILED);
155		}
156		if ((sym.st_name == 0) || (sym.st_shndx == SHN_UNDEF))
157			continue;
158
159		svalue = (ulong_t)sym.st_value;
160
161		if (svalue <= addr) {
162			/*
163			 * track both the best local and best
164			 * global fit for this address.  Later
165			 * we will favor the global over the local
166			 */
167			if ((GELF_ST_BIND(sym.st_info) == STB_LOCAL) &&
168			    ((lsymr == NULL) ||
169			    (svalue >= (ulong_t)lsymr->st_value))) {
170				if (lsymr && (lsymr->st_value == svalue))
171					*lsymr = sym_swap(lsymr, &sym);
172				else {
173					lsymr = &lsym;
174					*lsymr = sym;
175				}
176			} else if ((symr == NULL) ||
177			    (svalue >= (ulong_t)symr->st_value)) {
178				if (symr && (symr->st_value == svalue))
179					*symr = sym_swap(symr, &sym);
180				else {
181					symr = &rsym;
182					*symr = sym;
183				}
184			}
185		}
186	}
187	if ((symr == NULL) && (lsymr == NULL))
188		return (RET_FAILED);
189
190	if (lsymr) {
191		/*
192		 * If a possible local symbol was found should
193		 * we use it.
194		 */
195		if (symr && (lsymr->st_value > symr->st_value))
196			symr = lsymr;
197		else if (symr == NULL)
198			symr = lsymr;
199	}
200
201	*symptr = *symr;
202	*str = (char *)(symp->st_strs + symptr->st_name);
203	symptr->st_value += baseaddr;
204	return (RET_OK);
205}
206
207retc_t
208addr_to_sym(struct ps_prochandle *ph, ulong_t addr,
209	GElf_Sym *symp, char **str)
210{
211	map_info_t	*mip;
212
213	if ((mip = addr_to_map(ph, addr)) == NULL)
214		return (RET_FAILED);
215
216	return (addr_map_sym(mip, addr, symp, str));
217}
218
219retc_t
220str_to_sym(struct ps_prochandle *ph, const char *name, GElf_Sym *symp)
221{
222	map_info_t	*mip;
223
224	if (ph->pp_lmaplist.ml_head == NULL) {
225		if (str_map_sym(name, &(ph->pp_ldsomap), symp, NULL) == RET_OK)
226			return (RET_OK);
227
228		return (str_map_sym(name, &(ph->pp_execmap), symp, NULL));
229	}
230
231	for (mip = ph->pp_lmaplist.ml_head; mip; mip = mip->mi_next)
232		if (str_map_sym(name, mip, symp, NULL) == RET_OK)
233			return (RET_OK);
234
235	return (RET_FAILED);
236}
237