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 }