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 }