1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  *
  26  * Copyright 2020 Joyent, Inc.
  27  */
  28 
  29 /*
  30  * x86 relocation code.
  31  */
  32 
  33 #include <sys/types.h>
  34 #include <sys/param.h>
  35 #include <sys/sysmacros.h>
  36 #include <sys/systm.h>
  37 #include <sys/user.h>
  38 #include <sys/bootconf.h>
  39 #include <sys/modctl.h>
  40 #include <sys/elf.h>
  41 #include <sys/kobj.h>
  42 #include <sys/kobj_impl.h>
  43 #include <sys/tnf.h>
  44 #include <sys/tnf_probe.h>
  45 
  46 #include "reloc.h"
  47 
  48 
  49 /*
  50  * Probe Discovery
  51  */
  52 
  53 #define PROBE_MARKER_SYMBOL     "__tnf_probe_version_1"
  54 #define TAG_MARKER_SYMBOL       "__tnf_tag_version_1"
  55 
  56 extern int tnf_splice_probes(int, tnf_probe_control_t *, tnf_tag_data_t *);
  57 
  58 /*
  59  * The kernel run-time linker calls this to try to resolve a reference
  60  * it can't otherwise resolve.  We see if it's marking a probe control
  61  * block; if so, we do the resolution and return 0.  If not, we return
  62  * 1 to show that we can't resolve it, either.
  63  */
  64 static int
  65 tnf_reloc_resolve(char *symname, Addr *value_p,
  66     Elf64_Sxword *addend_p,
  67     long offset,
  68     tnf_probe_control_t **probelist,
  69     tnf_tag_data_t **taglist)
  70 {
  71         if (strcmp(symname, PROBE_MARKER_SYMBOL) == 0) {
  72                 *addend_p = 0;
  73                 ((tnf_probe_control_t *)offset)->next = *probelist;
  74                 *probelist = (tnf_probe_control_t *)offset;
  75                 return (0);
  76         }
  77         if (strcmp(symname, TAG_MARKER_SYMBOL) == 0) {
  78                 *addend_p = 0;
  79                 *value_p = (Addr)*taglist;
  80                 *taglist = (tnf_tag_data_t *)offset;
  81                 return (0);
  82         }
  83         return (1);
  84 }
  85 
  86 #define SDT_NOP         0x90
  87 #define SDT_NOPS        5
  88 
  89 static int
  90 sdt_reloc_resolve(struct module *mp, char *symname, uint8_t *instr)
  91 {
  92         sdt_probedesc_t *sdp;
  93         int i;
  94 
  95         /*
  96          * The "statically defined tracing" (SDT) provider for DTrace uses
  97          * a mechanism similar to TNF, but somewhat simpler.  (Surprise,
  98          * surprise.)  The SDT mechanism works by replacing calls to the
  99          * undefined routine __dtrace_probe_[name] with nop instructions.
 100          * The relocations are logged, and SDT itself will later patch the
 101          * running binary appropriately.
 102          */
 103         if (strncmp(symname, sdt_prefix, strlen(sdt_prefix)) != 0)
 104                 return (1);
 105 
 106         symname += strlen(sdt_prefix);
 107 
 108         sdp = kobj_alloc(sizeof (sdt_probedesc_t), KM_WAIT);
 109         sdp->sdpd_name = kobj_alloc(strlen(symname) + 1, KM_WAIT);
 110         bcopy(symname, sdp->sdpd_name, strlen(symname) + 1);
 111 
 112         sdp->sdpd_offset = (uintptr_t)instr;
 113         sdp->sdpd_next = mp->sdt_probes;
 114         mp->sdt_probes = sdp;
 115 
 116         for (i = 0; i < SDT_NOPS; i++)
 117                 instr[i - 1] = SDT_NOP;
 118 
 119         return (0);
 120 }
 121 
 122 
 123 /*
 124  * We're relying on the fact that the call we're replacing is
 125  * call (e8) plus 4 bytes of address, making a 5 byte instruction
 126  */
 127 #define NOP_INSTR       0x90
 128 #define SMAP_NOPS       5
 129 
 130 /*
 131  * Currently the only call replaced as a hot inline
 132  * is smap_enable() and smap_disable(). If more are needed
 133  * we should probably come up with an sdt probe like prefix
 134  * and look for those instead of exact call names.
 135  */
 136 static int
 137 smap_reloc_resolve(struct module *mp, char *symname, uint8_t *instr)
 138 {
 139         uint_t symlen;
 140         hotinline_desc_t *hid;
 141 
 142         if (strcmp(symname, "smap_enable") == 0 ||
 143             strcmp(symname, "smap_disable") == 0) {
 144 
 145 #ifdef  KOBJ_DEBUG
 146                 if (kobj_debug & D_RELOCATIONS) {
 147                         _kobj_printf(ops, "smap_reloc_resolve: %s relocating "
 148                             "enable/disable_smap\n", mp->filename);
 149                 }
 150 #endif
 151 
 152                 hid = kobj_alloc(sizeof (hotinline_desc_t), KM_WAIT);
 153                 symlen = strlen(symname) + 1;
 154                 hid->hid_symname = kobj_alloc(symlen, KM_WAIT);
 155                 bcopy(symname, hid->hid_symname, symlen);
 156 
 157                 /*
 158                  * We backtrack one byte here to consume the call
 159                  * instruction itself.
 160                  */
 161                 hid->hid_instr_offset = (uintptr_t)instr - 1;
 162                 hid->hid_next = mp->hi_calls;
 163                 mp->hi_calls = hid;
 164 
 165                 memset((void *)hid->hid_instr_offset, NOP_INSTR, SMAP_NOPS);
 166 
 167                 return (0);
 168         }
 169 
 170         return (1);
 171 }
 172 
 173 int
 174 do_relocate(struct module *mp, char *reltbl, int nreloc, int relocsize,
 175     Addr baseaddr)
 176 {
 177         unsigned long stndx;
 178         unsigned long off;      /* can't be register for tnf_reloc_resolve() */
 179         register unsigned long reladdr, rend;
 180         register unsigned int rtype;
 181         unsigned long value;
 182         Elf64_Sxword addend;
 183         Sym *symref = NULL;
 184         int err = 0;
 185         tnf_probe_control_t *probelist = NULL;
 186         tnf_tag_data_t *taglist = NULL;
 187         int symnum;
 188         reladdr = (unsigned long)reltbl;
 189         rend = reladdr + nreloc * relocsize;
 190 
 191 #ifdef  KOBJ_DEBUG
 192         if (kobj_debug & D_RELOCATIONS) {
 193                 _kobj_printf(ops, "krtld:\ttype\t\t\toffset\t   addend"
 194                     "      symbol\n");
 195                 _kobj_printf(ops, "krtld:\t\t\t\t\t   value\n");
 196         }
 197 #endif
 198 
 199         symnum = -1;
 200         /* loop through relocations */
 201         while (reladdr < rend) {
 202                 symnum++;
 203                 rtype = ELF_R_TYPE(((Rela *)reladdr)->r_info);
 204                 off = ((Rela *)reladdr)->r_offset;
 205                 stndx = ELF_R_SYM(((Rela *)reladdr)->r_info);
 206                 if (stndx >= mp->nsyms) {
 207                         _kobj_printf(ops, "do_relocate: bad strndx %d\n",
 208                             symnum);
 209                         return (-1);
 210                 }
 211                 if ((rtype > R_AMD64_NUM) || IS_TLS_INS(rtype)) {
 212                         _kobj_printf(ops, "krtld: invalid relocation type %d",
 213                             rtype);
 214                         _kobj_printf(ops, " at 0x%lx:", off);
 215                         _kobj_printf(ops, " file=%s\n", mp->filename);
 216                         err = 1;
 217                         continue;
 218                 }
 219 
 220 
 221                 addend = (long)(((Rela *)reladdr)->r_addend);
 222                 reladdr += relocsize;
 223 
 224 
 225                 if (rtype == R_AMD64_NONE)
 226                         continue;
 227 
 228 #ifdef  KOBJ_DEBUG
 229                 if (kobj_debug & D_RELOCATIONS) {
 230                         Sym *   symp;
 231                         symp = (Sym *)
 232                             (mp->symtbl+(stndx * mp->symhdr->sh_entsize));
 233                         _kobj_printf(ops, "krtld:\t%s",
 234                             conv_reloc_amd64_type(rtype));
 235                         _kobj_printf(ops, "\t0x%8lx", off);
 236                         _kobj_printf(ops, " %8lld", (longlong_t)addend);
 237                         _kobj_printf(ops, "  %s\n",
 238                             (const char *)mp->strings + symp->st_name);
 239                 }
 240 #endif
 241 
 242                 if (!(mp->flags & KOBJ_EXEC))
 243                         off += baseaddr;
 244 
 245                 /*
 246                  * if R_AMD64_RELATIVE, simply add base addr
 247                  * to reloc location
 248                  */
 249 
 250                 if (rtype == R_AMD64_RELATIVE) {
 251                         value = baseaddr;
 252                 } else {
 253                         /*
 254                          * get symbol table entry - if symbol is local
 255                          * value is base address of this object
 256                          */
 257                         symref = (Sym *)
 258                             (mp->symtbl+(stndx * mp->symhdr->sh_entsize));
 259 
 260                         if (ELF_ST_BIND(symref->st_info) == STB_LOCAL) {
 261                                 /* *** this is different for .o and .so */
 262                                 value = symref->st_value;
 263                         } else {
 264                                 /*
 265                                  * It's global. Allow weak references.  If
 266                                  * the symbol is undefined, give TNF (the
 267                                  * kernel probes facility) a chance to see
 268                                  * if it's a probe site, and fix it up if so.
 269                                  */
 270                                 if (symref->st_shndx == SHN_UNDEF &&
 271                                     sdt_reloc_resolve(mp, mp->strings +
 272                                     symref->st_name, (uint8_t *)off) == 0)
 273                                         continue;
 274 
 275                                 if (symref->st_shndx == SHN_UNDEF &&
 276                                     smap_reloc_resolve(mp, mp->strings +
 277                                     symref->st_name, (uint8_t *)off) == 0)
 278                                         continue;
 279 
 280                                 if (symref->st_shndx == SHN_UNDEF &&
 281                                     tnf_reloc_resolve(mp->strings +
 282                                     symref->st_name, &symref->st_value,
 283                                     &addend, off, &probelist, &taglist) != 0) {
 284                                         if (ELF_ST_BIND(symref->st_info)
 285                                             != STB_WEAK) {
 286                                                 _kobj_printf(ops,
 287                                                     "not found: %s\n",
 288                                                     mp->strings +
 289                                                     symref->st_name);
 290                                                 err = 1;
 291                                         }
 292                                         continue;
 293                                 } else { /* symbol found  - relocate */
 294                                         /*
 295                                          * calculate location of definition
 296                                          * - symbol value plus base address of
 297                                          * containing shared object
 298                                          */
 299                                         value = symref->st_value;
 300 
 301                                 } /* end else symbol found */
 302                         } /* end global or weak */
 303                 } /* end not R_AMD64_RELATIVE */
 304 
 305                 value += addend;
 306                 /*
 307                  * calculate final value -
 308                  * if PC-relative, subtract ref addr
 309                  */
 310                 if (IS_PC_RELATIVE(rtype))
 311                         value -= off;
 312 
 313 #ifdef  KOBJ_DEBUG
 314                 if (kobj_debug & D_RELOCATIONS) {
 315                         _kobj_printf(ops, "krtld:\t\t\t\t0x%8lx", off);
 316                         _kobj_printf(ops, " 0x%8lx\n", value);
 317                 }
 318 #endif
 319 
 320                 if (do_reloc_krtld(rtype, (unsigned char *)off, &value,
 321                     (const char *)mp->strings + symref->st_name,
 322                     mp->filename) == 0)
 323                         err = 1;
 324 
 325         } /* end of while loop */
 326         if (err)
 327                 return (-1);
 328 
 329         if (tnf_splice_probes(mp->flags & KOBJ_PRIM, probelist, taglist))
 330                 mp->flags |= KOBJ_TNF_PROBE;
 331 
 332         return (0);
 333 }
 334 
 335 int
 336 do_relocations(struct module *mp)
 337 {
 338         uint_t shn;
 339         Shdr *shp, *rshp;
 340         uint_t nreloc;
 341 
 342         /* do the relocations */
 343         for (shn = 1; shn < mp->hdr.e_shnum; shn++) {
 344                 rshp = (Shdr *)
 345                     (mp->shdrs + shn * mp->hdr.e_shentsize);
 346                 if (rshp->sh_type == SHT_REL) {
 347                         _kobj_printf(ops, "%s can't process type SHT_REL\n",
 348                             mp->filename);
 349                         return (-1);
 350                 }
 351                 if (rshp->sh_type != SHT_RELA)
 352                         continue;
 353                 if (rshp->sh_link != mp->symtbl_section) {
 354                         _kobj_printf(ops, "%s reloc for non-default symtab\n",
 355                             mp->filename);
 356                         return (-1);
 357                 }
 358                 if (rshp->sh_info >= mp->hdr.e_shnum) {
 359                         _kobj_printf(ops, "do_relocations: %s sh_info ",
 360                             mp->filename);
 361                         _kobj_printf(ops, "out of range %d\n", shn);
 362                         goto bad;
 363                 }
 364                 nreloc = rshp->sh_size / rshp->sh_entsize;
 365 
 366                 /* get the section header that this reloc table refers to */
 367                 shp = (Shdr *)
 368                     (mp->shdrs + rshp->sh_info * mp->hdr.e_shentsize);
 369 
 370                 /*
 371                  * Do not relocate any section that isn't loaded into memory.
 372                  * Most commonly this will skip over the .rela.stab* sections
 373                  */
 374                 if (!(shp->sh_flags & SHF_ALLOC))
 375                         continue;
 376 #ifdef  KOBJ_DEBUG
 377                 if (kobj_debug & D_RELOCATIONS) {
 378                         _kobj_printf(ops, "krtld: relocating: file=%s ",
 379                             mp->filename);
 380                         _kobj_printf(ops, "section=%d\n", shn);
 381                 }
 382 #endif
 383 
 384                 if (do_relocate(mp, (char *)rshp->sh_addr, nreloc,
 385                     rshp->sh_entsize, shp->sh_addr) < 0) {
 386                         _kobj_printf(ops,
 387                             "do_relocations: %s do_relocate failed\n",
 388                             mp->filename);
 389                         goto bad;
 390                 }
 391                 kobj_free((void *)rshp->sh_addr, rshp->sh_size);
 392                 rshp->sh_addr = 0;
 393         }
 394         mp->flags |= KOBJ_RELOCATED;
 395         return (0);
 396 bad:
 397         kobj_free((void *)rshp->sh_addr, rshp->sh_size);
 398         rshp->sh_addr = 0;
 399         return (-1);
 400 }