14226f635SJason King /*
24226f635SJason King  * This file and its contents are supplied under the terms of the
34226f635SJason King  * Common Development and Distribution License ("CDDL"), version 1.0.
44226f635SJason King  * You may only use this file in accordance with the terms of version
54226f635SJason King  * 1.0 of the CDDL.
64226f635SJason King  *
74226f635SJason King  * A full copy of the text of the CDDL should have accompanied this
84226f635SJason King  * source.  A copy of the CDDL is also available via the Internet at
94226f635SJason King  * http://www.illumos.org/license/CDDL.
104226f635SJason King  */
114226f635SJason King 
124226f635SJason King /*
13f5ac8590SJason King  * Copyright 2021 Jason King
14*1cd08393SJason King  * Copyright 2019 Joyent, Inc.
154226f635SJason King  */
164226f635SJason King 
174226f635SJason King #include <stdlib.h>
186a6cfa5dSJason King #include <stdio.h>
194226f635SJason King #include <string.h>
204226f635SJason King #include <errno.h>
21f5ac8590SJason King #include <limits.h>
224226f635SJason King #include <pthread.h>
236a6cfa5dSJason King #include <sys/ctype.h>
244226f635SJason King #include <sys/debug.h>
25d52aae23SJason King #include <sys/sysmacros.h>
266a6cfa5dSJason King #include <stdarg.h>
274226f635SJason King #include "demangle-sys.h"
284226f635SJason King #include "demangle_int.h"
29f5ac8590SJason King #include "strview.h"
304226f635SJason King 
314226f635SJason King #define	DEMANGLE_DEBUG	"DEMANGLE_DEBUG"
324226f635SJason King 
334226f635SJason King static pthread_once_t debug_once = PTHREAD_ONCE_INIT;
344226f635SJason King volatile boolean_t demangle_debug;
356a6cfa5dSJason King FILE *debugf = stderr;
366a6cfa5dSJason King 
37d52aae23SJason King static struct {
38d52aae23SJason King 	const char	*str;
39d52aae23SJason King 	sysdem_lang_t	lang;
40d52aae23SJason King } lang_tbl[] = {
41d52aae23SJason King 	{ "auto", SYSDEM_LANG_AUTO },
42d52aae23SJason King 	{ "c++", SYSDEM_LANG_CPP },
43d52aae23SJason King 	{ "rust", SYSDEM_LANG_RUST },
44d52aae23SJason King };
45d52aae23SJason King 
466a6cfa5dSJason King static const char *
langstr(sysdem_lang_t lang)476a6cfa5dSJason King langstr(sysdem_lang_t lang)
486a6cfa5dSJason King {
49d52aae23SJason King 	size_t i;
50d52aae23SJason King 
51d52aae23SJason King 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
52d52aae23SJason King 		if (lang == lang_tbl[i].lang)
53d52aae23SJason King 			return (lang_tbl[i].str);
54d52aae23SJason King 	}
55d52aae23SJason King 	return ("invalid");
56d52aae23SJason King }
57d52aae23SJason King 
58d52aae23SJason King boolean_t
sysdem_parse_lang(const char * str,sysdem_lang_t * langp)59d52aae23SJason King sysdem_parse_lang(const char *str, sysdem_lang_t *langp)
60d52aae23SJason King {
61d52aae23SJason King 	size_t i;
62d52aae23SJason King 
63d52aae23SJason King 	for (i = 0; i < ARRAY_SIZE(lang_tbl); i++) {
64d52aae23SJason King 		if (strcmp(str, lang_tbl[i].str) == 0) {
65d52aae23SJason King 			*langp = lang_tbl[i].lang;
66d52aae23SJason King 			return (B_TRUE);
67d52aae23SJason King 		}
686a6cfa5dSJason King 	}
69d52aae23SJason King 
70d52aae23SJason King 	return (B_FALSE);
716a6cfa5dSJason King }
724226f635SJason King 
73f5ac8590SJason King /*
74f5ac8590SJason King  * A quick check if str can possibly be a mangled string. Currently, that
75f5ac8590SJason King  * means it must start with _Z or __Z.
76f5ac8590SJason King  */
77f5ac8590SJason King static boolean_t
is_mangled(const char * str,size_t n)78f5ac8590SJason King is_mangled(const char *str, size_t n)
794226f635SJason King {
80f5ac8590SJason King 	strview_t sv;
816a6cfa5dSJason King 
82f5ac8590SJason King 	sv_init_str(&sv, str, str + n);
836a6cfa5dSJason King 
84f5ac8590SJason King 	if (!sv_consume_if_c(&sv, '_'))
85f5ac8590SJason King 		return (B_FALSE);
86f5ac8590SJason King 	(void) sv_consume_if_c(&sv, '_');
87f5ac8590SJason King 	if (sv_consume_if_c(&sv, 'Z'))
88f5ac8590SJason King 		return (B_TRUE);
89*1cd08393SJason King 	if (sv_consume_if_c(&sv, 'R'))
90*1cd08393SJason King 		return (B_TRUE);
914226f635SJason King 
92f5ac8590SJason King 	return (B_FALSE);
934226f635SJason King }
944226f635SJason King 
954226f635SJason King static void
check_debug(void)964226f635SJason King check_debug(void)
974226f635SJason King {
984226f635SJason King 	if (getenv(DEMANGLE_DEBUG))
994226f635SJason King 		demangle_debug = B_TRUE;
1004226f635SJason King }
1014226f635SJason King 
1024226f635SJason King char *
sysdemangle(const char * str,sysdem_lang_t lang,sysdem_ops_t * ops)1034226f635SJason King sysdemangle(const char *str, sysdem_lang_t lang, sysdem_ops_t *ops)
1044226f635SJason King {
105f5ac8590SJason King 	char *res = NULL;
106*1cd08393SJason King 
1076a6cfa5dSJason King 	/*
1086a6cfa5dSJason King 	 * While the language specific demangler code can handle non-NUL
1096a6cfa5dSJason King 	 * terminated strings, we currently don't expose this to consumers.
1106a6cfa5dSJason King 	 * Consumers should still pass in a NUL-terminated string.
1116a6cfa5dSJason King 	 */
1126a6cfa5dSJason King 	size_t slen;
1136a6cfa5dSJason King 
1144226f635SJason King 	VERIFY0(pthread_once(&debug_once, check_debug));
1154226f635SJason King 
1166a6cfa5dSJason King 	DEMDEBUG("name = '%s'", (str == NULL) ? "(NULL)" : str);
1176a6cfa5dSJason King 	DEMDEBUG("lang = %s (%d)", langstr(lang), lang);
1186a6cfa5dSJason King 
1196a6cfa5dSJason King 	if (str == NULL) {
1206a6cfa5dSJason King 		errno = EINVAL;
1216a6cfa5dSJason King 		return (NULL);
1226a6cfa5dSJason King 	}
1236a6cfa5dSJason King 
1246a6cfa5dSJason King 	slen = strlen(str);
1256a6cfa5dSJason King 
1266a6cfa5dSJason King 	switch (lang) {
1276a6cfa5dSJason King 		case SYSDEM_LANG_AUTO:
1286a6cfa5dSJason King 		case SYSDEM_LANG_CPP:
1296a6cfa5dSJason King 		case SYSDEM_LANG_RUST:
1306a6cfa5dSJason King 			break;
1316a6cfa5dSJason King 		default:
1326a6cfa5dSJason King 			errno = EINVAL;
1336a6cfa5dSJason King 			return (NULL);
1346a6cfa5dSJason King 	}
1356a6cfa5dSJason King 
1364226f635SJason King 	if (ops == NULL)
1374226f635SJason King 		ops = sysdem_ops_default;
1384226f635SJason King 
139f5ac8590SJason King 	/*
140f5ac8590SJason King 	 * If we were given an explicit language to demangle, we always
141f5ac8590SJason King 	 * use that. If not, we try to demangle as rust, then c++. Any
142f5ac8590SJason King 	 * mangled C++ symbol that manages to successfully demangle as a
143f5ac8590SJason King 	 * legacy rust symbol _should_ look the same as it can really
144f5ac8590SJason King 	 * only be a very simple C++ symbol. Otherwise, the rust demangling
145f5ac8590SJason King 	 * should fail and we can try C++.
146f5ac8590SJason King 	 */
1474226f635SJason King 	switch (lang) {
1484226f635SJason King 	case SYSDEM_LANG_CPP:
1496a6cfa5dSJason King 		return (cpp_demangle(str, slen, ops));
1506a6cfa5dSJason King 	case SYSDEM_LANG_RUST:
1516a6cfa5dSJason King 		return (rust_demangle(str, slen, ops));
1526a6cfa5dSJason King 	case SYSDEM_LANG_AUTO:
153f5ac8590SJason King 		break;
154f5ac8590SJason King 	}
155f5ac8590SJason King 
156f5ac8590SJason King 	/*
157f5ac8590SJason King 	 * To save us some potential work, if the symbol cannot
158f5ac8590SJason King 	 * possibly be a rust or C++ mangled name, we don't
159f5ac8590SJason King 	 * even attempt to demangle either.
160f5ac8590SJason King 	 */
161f5ac8590SJason King 	if (!is_mangled(str, slen)) {
1626a6cfa5dSJason King 		/*
163f5ac8590SJason King 		 * This does mean if we somehow get a string > 2GB
164f5ac8590SJason King 		 * the debugging output will be truncated, but that
165f5ac8590SJason King 		 * seems an acceptable tradeoff.
1666a6cfa5dSJason King 		 */
167f5ac8590SJason King 		int len = slen > INT_MAX ? INT_MAX : slen;
168f5ac8590SJason King 
169f5ac8590SJason King 		DEMDEBUG("ERROR: '%.*s' cannot be a mangled string", len, str);
1706a6cfa5dSJason King 		errno = EINVAL;
1716a6cfa5dSJason King 		return (NULL);
1724226f635SJason King 	}
173f5ac8590SJason King 
174f5ac8590SJason King 	DEMDEBUG("trying rust");
175f5ac8590SJason King 	res = rust_demangle(str, slen, ops);
176f5ac8590SJason King 
177f5ac8590SJason King 	IMPLY(ret != NULL, errno == 0);
178f5ac8590SJason King 	if (res != NULL)
179f5ac8590SJason King 		return (res);
180f5ac8590SJason King 
181f5ac8590SJason King 	DEMDEBUG("trying C++");
182f5ac8590SJason King 	return (cpp_demangle(str, slen, ops));
1836a6cfa5dSJason King }
1844226f635SJason King 
1856a6cfa5dSJason King int
demdebug(const char * fmt,...)1866a6cfa5dSJason King demdebug(const char *fmt, ...)
1876a6cfa5dSJason King {
1886a6cfa5dSJason King 	va_list ap;
1896a6cfa5dSJason King 
1906a6cfa5dSJason King 	flockfile(debugf);
1916a6cfa5dSJason King 	(void) fprintf(debugf, "LIBDEMANGLE: ");
1926a6cfa5dSJason King 	va_start(ap, fmt);
1936a6cfa5dSJason King 	(void) vfprintf(debugf, fmt, ap);
1946a6cfa5dSJason King 	(void) fputc('\n', debugf);
1956a6cfa5dSJason King 	(void) fflush(debugf);
1966a6cfa5dSJason King 	va_end(ap);
1976a6cfa5dSJason King 	funlockfile(debugf);
1986a6cfa5dSJason King 
1996a6cfa5dSJason King 	return (0);
2004226f635SJason King }