1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2018 Jason King
14  * Copyright 2019, Joyent, Inc.
15  */
16 
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <errno.h>
21 #include <pthread.h>
22 #include <sys/ctype.h>
23 #include <sys/debug.h>
24 #include <sys/sysmacros.h>
25 #include <stdarg.h>
26 #include "demangle-sys.h"
27 #include "demangle_int.h"
28 
29 #define	DEMANGLE_DEBUG	"DEMANGLE_DEBUG"
30 
31 static pthread_once_t debug_once = PTHREAD_ONCE_INIT;
32 volatile boolean_t demangle_debug;
33 FILE *debugf = stderr;
34 
35 static struct {
36 	const char	*str;
37 	sysdem_lang_t	lang;
38 } lang_tbl[] = {
39 	{ "auto", SYSDEM_LANG_AUTO },
40 	{ "c++", SYSDEM_LANG_CPP },
41 	{ "rust", SYSDEM_LANG_RUST },
42 };
43 
44 static const char *
45 langstr(sysdem_lang_t lang)
46 {
47 	size_t i;
48 
49 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
50 		if (lang == lang_tbl[i].lang)
51 			return (lang_tbl[i].str);
52 	}
53 	return ("invalid");
54 }
55 
56 boolean_t
57 sysdem_parse_lang(const char *str, sysdem_lang_t *langp)
58 {
59 	size_t i;
60 
61 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
62 		if (strcmp(str, lang_tbl[i].str) == 0) {
63 			*langp = lang_tbl[i].lang;
64 			return (B_TRUE);
65 		}
66 	}
67 
68 	return (B_FALSE);
69 }
70 
71 static sysdem_lang_t
72 detect_lang(const char *str, size_t n)
73 {
74 	const char *p = str;
75 	size_t len;
76 
77 	if (n < 3 || str[0] != '_')
78 		return (SYSDEM_LANG_AUTO);
79 
80 	/*
81 	 * Check for ^_Z or ^__Z
82 	 */
83 	p = str + 1;
84 	if (*p == '_') {
85 		p++;
86 	}
87 
88 	if (*p != 'Z')
89 		return (SYSDEM_LANG_AUTO);
90 
91 	/*
92 	 * Sadly, rust currently uses the same prefix as C++, however
93 	 * demangling rust as a C++ mangled name yields less than desirable
94 	 * results.  However rust names end with a hash.  We use that to
95 	 * attempt to disambiguate
96 	 */
97 
98 	/* Find 'h'<hexdigit>+E$ */
99 	if ((p = strrchr(p, 'h')) == NULL)
100 		return (SYSDEM_LANG_CPP);
101 
102 	if ((len = strspn(p + 1, "0123456789abcdef")) == 0)
103 		return (SYSDEM_LANG_CPP);
104 
105 	p += len + 1;
106 
107 	if (p[0] != 'E' || p[1] != '\0')
108 		return (SYSDEM_LANG_CPP);
109 
110 	return (SYSDEM_LANG_RUST);
111 }
112 
113 static void
114 check_debug(void)
115 {
116 	if (getenv(DEMANGLE_DEBUG))
117 		demangle_debug = B_TRUE;
118 }
119 
120 char *
121 sysdemangle(const char *str, sysdem_lang_t lang, sysdem_ops_t *ops)
122 {
123 	/*
124 	 * While the language specific demangler code can handle non-NUL
125 	 * terminated strings, we currently don't expose this to consumers.
126 	 * Consumers should still pass in a NUL-terminated string.
127 	 */
128 	size_t slen;
129 
130 	VERIFY0(pthread_once(&debug_once, check_debug));
131 
132 	DEMDEBUG("name = '%s'", (str == NULL) ? "(NULL)" : str);
133 	DEMDEBUG("lang = %s (%d)", langstr(lang), lang);
134 
135 	if (str == NULL) {
136 		errno = EINVAL;
137 		return (NULL);
138 	}
139 
140 	slen = strlen(str);
141 
142 	switch (lang) {
143 		case SYSDEM_LANG_AUTO:
144 		case SYSDEM_LANG_CPP:
145 		case SYSDEM_LANG_RUST:
146 			break;
147 		default:
148 			errno = EINVAL;
149 			return (NULL);
150 	}
151 
152 	if (ops == NULL)
153 		ops = sysdem_ops_default;
154 
155 	if (lang == SYSDEM_LANG_AUTO) {
156 		lang = detect_lang(str, slen);
157 		if (lang != SYSDEM_LANG_AUTO)
158 			DEMDEBUG("detected language is %s", langstr(lang));
159 	}
160 
161 	switch (lang) {
162 	case SYSDEM_LANG_CPP:
163 		return (cpp_demangle(str, slen, ops));
164 	case SYSDEM_LANG_RUST:
165 		return (rust_demangle(str, slen, ops));
166 	case SYSDEM_LANG_AUTO:
167 		DEMDEBUG("could not detect language");
168 		errno = ENOTSUP;
169 		return (NULL);
170 	default:
171 		/*
172 		 * This can't happen unless there's a bug with detect_lang,
173 		 * but gcc doesn't know that.
174 		 */
175 		errno = EINVAL;
176 		return (NULL);
177 	}
178 }
179 
180 int
181 demdebug(const char *fmt, ...)
182 {
183 	va_list ap;
184 
185 	flockfile(debugf);
186 	(void) fprintf(debugf, "LIBDEMANGLE: ");
187 	va_start(ap, fmt);
188 	(void) vfprintf(debugf, fmt, ap);
189 	(void) fputc('\n', debugf);
190 	(void) fflush(debugf);
191 	va_end(ap);
192 	funlockfile(debugf);
193 
194 	return (0);
195 }
196