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 2021 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 <limits.h>
22 #include <pthread.h>
23 #include <sys/ctype.h>
24 #include <sys/debug.h>
25 #include <sys/sysmacros.h>
26 #include <stdarg.h>
27 #include "demangle-sys.h"
28 #include "demangle_int.h"
29 #include "strview.h"
30 
31 #define	DEMANGLE_DEBUG	"DEMANGLE_DEBUG"
32 
33 static pthread_once_t debug_once = PTHREAD_ONCE_INIT;
34 volatile boolean_t demangle_debug;
35 FILE *debugf = stderr;
36 
37 static struct {
38 	const char	*str;
39 	sysdem_lang_t	lang;
40 } lang_tbl[] = {
41 	{ "auto", SYSDEM_LANG_AUTO },
42 	{ "c++", SYSDEM_LANG_CPP },
43 	{ "rust", SYSDEM_LANG_RUST },
44 };
45 
46 static const char *
langstr(sysdem_lang_t lang)47 langstr(sysdem_lang_t lang)
48 {
49 	size_t i;
50 
51 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
52 		if (lang == lang_tbl[i].lang)
53 			return (lang_tbl[i].str);
54 	}
55 	return ("invalid");
56 }
57 
58 boolean_t
sysdem_parse_lang(const char * str,sysdem_lang_t * langp)59 sysdem_parse_lang(const char *str, sysdem_lang_t *langp)
60 {
61 	size_t i;
62 
63 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
64 		if (strcmp(str, lang_tbl[i].str) == 0) {
65 			*langp = lang_tbl[i].lang;
66 			return (B_TRUE);
67 		}
68 	}
69 
70 	return (B_FALSE);
71 }
72 
73 /*
74  * A quick check if str can possibly be a mangled string. Currently, that
75  * means it must start with _Z or __Z.
76  */
77 static boolean_t
is_mangled(const char * str,size_t n)78 is_mangled(const char *str, size_t n)
79 {
80 	strview_t sv;
81 
82 	sv_init_str(&sv, str, str + n);
83 
84 	if (!sv_consume_if_c(&sv, '_'))
85 		return (B_FALSE);
86 	(void) sv_consume_if_c(&sv, '_');
87 	if (sv_consume_if_c(&sv, 'Z'))
88 		return (B_TRUE);
89 	if (sv_consume_if_c(&sv, 'R'))
90 		return (B_TRUE);
91 
92 	return (B_FALSE);
93 }
94 
95 static void
check_debug(void)96 check_debug(void)
97 {
98 	if (getenv(DEMANGLE_DEBUG))
99 		demangle_debug = B_TRUE;
100 }
101 
102 char *
sysdemangle(const char * str,sysdem_lang_t lang,sysdem_ops_t * ops)103 sysdemangle(const char *str, sysdem_lang_t lang, sysdem_ops_t *ops)
104 {
105 	char *res = NULL;
106 
107 	/*
108 	 * While the language specific demangler code can handle non-NUL
109 	 * terminated strings, we currently don't expose this to consumers.
110 	 * Consumers should still pass in a NUL-terminated string.
111 	 */
112 	size_t slen;
113 
114 	VERIFY0(pthread_once(&debug_once, check_debug));
115 
116 	DEMDEBUG("name = '%s'", (str == NULL) ? "(NULL)" : str);
117 	DEMDEBUG("lang = %s (%d)", langstr(lang), lang);
118 
119 	if (str == NULL) {
120 		errno = EINVAL;
121 		return (NULL);
122 	}
123 
124 	slen = strlen(str);
125 
126 	switch (lang) {
127 		case SYSDEM_LANG_AUTO:
128 		case SYSDEM_LANG_CPP:
129 		case SYSDEM_LANG_RUST:
130 			break;
131 		default:
132 			errno = EINVAL;
133 			return (NULL);
134 	}
135 
136 	if (ops == NULL)
137 		ops = sysdem_ops_default;
138 
139 	/*
140 	 * If we were given an explicit language to demangle, we always
141 	 * use that. If not, we try to demangle as rust, then c++. Any
142 	 * mangled C++ symbol that manages to successfully demangle as a
143 	 * legacy rust symbol _should_ look the same as it can really
144 	 * only be a very simple C++ symbol. Otherwise, the rust demangling
145 	 * should fail and we can try C++.
146 	 */
147 	switch (lang) {
148 	case SYSDEM_LANG_CPP:
149 		return (cpp_demangle(str, slen, ops));
150 	case SYSDEM_LANG_RUST:
151 		return (rust_demangle(str, slen, ops));
152 	case SYSDEM_LANG_AUTO:
153 		break;
154 	}
155 
156 	/*
157 	 * To save us some potential work, if the symbol cannot
158 	 * possibly be a rust or C++ mangled name, we don't
159 	 * even attempt to demangle either.
160 	 */
161 	if (!is_mangled(str, slen)) {
162 		/*
163 		 * This does mean if we somehow get a string > 2GB
164 		 * the debugging output will be truncated, but that
165 		 * seems an acceptable tradeoff.
166 		 */
167 		int len = slen > INT_MAX ? INT_MAX : slen;
168 
169 		DEMDEBUG("ERROR: '%.*s' cannot be a mangled string", len, str);
170 		errno = EINVAL;
171 		return (NULL);
172 	}
173 
174 	DEMDEBUG("trying rust");
175 	res = rust_demangle(str, slen, ops);
176 
177 	IMPLY(ret != NULL, errno == 0);
178 	if (res != NULL)
179 		return (res);
180 
181 	DEMDEBUG("trying C++");
182 	return (cpp_demangle(str, slen, ops));
183 }
184 
185 int
demdebug(const char * fmt,...)186 demdebug(const char *fmt, ...)
187 {
188 	va_list ap;
189 
190 	flockfile(debugf);
191 	(void) fprintf(debugf, "LIBDEMANGLE: ");
192 	va_start(ap, fmt);
193 	(void) vfprintf(debugf, fmt, ap);
194 	(void) fputc('\n', debugf);
195 	(void) fflush(debugf);
196 	va_end(ap);
197 	funlockfile(debugf);
198 
199 	return (0);
200 }
201