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 }