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 } 174