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 <stdarg.h>
25 #include "demangle-sys.h"
26 #include "demangle_int.h"
27
28 #define DEMANGLE_DEBUG "DEMANGLE_DEBUG"
29
30 static pthread_once_t debug_once = PTHREAD_ONCE_INIT;
31 volatile boolean_t demangle_debug;
32 FILE *debugf = stderr;
33
34 static const char *
35 langstr(sysdem_lang_t lang)
36 {
37 switch (lang) {
38 case SYSDEM_LANG_AUTO:
39 return ("auto");
40 case SYSDEM_LANG_CPP:
41 return ("c++");
42 case SYSDEM_LANG_RUST:
43 return ("rust");
44 default:
45 return ("invalid");
46 }
47 }
48
49 static sysdem_lang_t
50 detect_lang(const char *str, size_t n)
51 {
52 const char *p = str;
53 size_t len;
54
55 if (n < 3 || str[0] != '_')
56 return (SYSDEM_LANG_AUTO);
57
58 /*
59 * Check for ^_Z or ^__Z
60 */
61 p = str + 1;
62 if (*p == '_') {
63 p++;
64 }
65
66 if (*p != 'Z')
67 return (SYSDEM_LANG_AUTO);
68
69 /*
70 * Sadly, rust currently uses the same prefix as C++, however
71 * demangling rust as a C++ mangled name yields less than desirable
72 * results. However rust names end with a hash. We use that to
73 * attempt to disambiguate
74 */
75
76 /* Find 'h'<hexdigit>+E$ */
77 if ((p = strrchr(p, 'h')) == NULL)
78 return (SYSDEM_LANG_CPP);
79
80 if ((len = strspn(p + 1, "0123456789abcdef")) == 0)
81 return (SYSDEM_LANG_CPP);
82
83 p += len + 1;
84
85 if (p[0] != 'E' || p[1] != '\0')
86 return (SYSDEM_LANG_CPP);
87
88 return (SYSDEM_LANG_RUST);
89 }
90
91 static void
92 check_debug(void)
93 {
94 if (getenv(DEMANGLE_DEBUG))
95 demangle_debug = B_TRUE;
96 }
97
98 char *
99 sysdemangle(const char *str, sysdem_lang_t lang, sysdem_ops_t *ops)
100 {
101 /*
102 * While the language specific demangler code can handle non-NUL
103 * terminated strings, we currently don't expose this to consumers.
104 * Consumers should still pass in a NUL-terminated string.
105 */
106 size_t slen;
107
108 VERIFY0(pthread_once(&debug_once, check_debug));
109
110 DEMDEBUG("name = '%s'", (str == NULL) ? "(NULL)" : str);
111 DEMDEBUG("lang = %s (%d)", langstr(lang), lang);
112
113 if (str == NULL) {
114 errno = EINVAL;
115 return (NULL);
116 }
117
118 slen = strlen(str);
119
120 switch (lang) {
121 case SYSDEM_LANG_AUTO:
122 case SYSDEM_LANG_CPP:
123 case SYSDEM_LANG_RUST:
124 break;
125 default:
126 errno = EINVAL;
127 return (NULL);
128 }
129
130 if (ops == NULL)
131 ops = sysdem_ops_default;
132
133 if (lang == SYSDEM_LANG_AUTO) {
134 lang = detect_lang(str, slen);
135 if (lang != SYSDEM_LANG_AUTO)
136 DEMDEBUG("detected language is %s", langstr(lang));
137 }
138
139 switch (lang) {
140 case SYSDEM_LANG_CPP:
141 return (cpp_demangle(str, slen, ops));
142 case SYSDEM_LANG_RUST:
143 return (rust_demangle(str, slen, ops));
144 case SYSDEM_LANG_AUTO:
145 DEMDEBUG("could not detect language");
146 errno = ENOTSUP;
147 return (NULL);
148 default:
149 /*
150 * This can't happen unless there's a bug with detect_lang,
151 * but gcc doesn't know that.
152 */
153 errno = EINVAL;
154 return (NULL);
155 }
156 }
157
158 int
159 demdebug(const char *fmt, ...)
160 {
161 va_list ap;
162
163 flockfile(debugf);
164 (void) fprintf(debugf, "LIBDEMANGLE: ");
165 va_start(ap, fmt);
166 (void) vfprintf(debugf, fmt, ap);
167 (void) fputc('\n', debugf);
168 (void) fflush(debugf);
169 va_end(ap);
170 funlockfile(debugf);
171
172 return (0);
173 }