Print this page
3337 x64 link-editor is painfully literal-minded about TLS

@@ -512,10 +512,27 @@
          */
         0x64, 0x48, 0x8b, 0x04, 0x25,
         0x00, 0x00, 0x00, 0x00
 };
 
+#define REX_B           0x1
+#define REX_X           0x2
+#define REX_R           0x4
+#define REX_W           0x8
+#define REX_PREFIX      0x40
+
+#define REX_RW          (REX_PREFIX | REX_R | REX_W)
+#define REX_BW          (REX_PREFIX | REX_B | REX_W)
+#define REX_BRW         (REX_PREFIX | REX_B | REX_R | REX_W)
+
+#define REG_ESP         0x4
+
+#define INSN_ADDMR      0x03    /* addq mem,reg */
+#define INSN_ADDIR      0x81    /* addq imm,reg */
+#define INSN_MOVMR      0x8b    /* movq mem,reg */
+#define INSN_MOVIR      0xc7    /* movq imm,reg */
+#define INSN_LEA        0x8d    /* leaq mem,reg */
 
 static Fixupret
 tls_fixups(Ofl_desc *ofl, Rel_desc *arsp)
 {
         Sym_desc        *sdp = arsp->rel_sym;

@@ -524,10 +541,14 @@
 
         offset = (uchar_t *)((uintptr_t)arsp->rel_roffset +
             (uintptr_t)_elf_getxoff(arsp->rel_isdesc->is_indata) +
             (uintptr_t)RELAUX_GET_OSDESC(arsp)->os_outdata->d_buf);
 
+        /*
+         * Note that in certain of the original insn sequences below, the
+         * instructions are not necessarily adjacent
+         */
         if (sdp->sd_ref == REF_DYN_NEED) {
                 /*
                  * IE reference model
                  */
                 switch (rtype) {

@@ -603,39 +624,92 @@
                  */
                 offset -= 4;
                 (void) memcpy(offset, tlsinstr_gd_le, sizeof (tlsinstr_gd_le));
                 return (FIX_RELOC);
 
-        case R_AMD64_GOTTPOFF:
+        case R_AMD64_GOTTPOFF: {
                 /*
                  * IE -> LE
                  *
-                 * Transition:
-                 *      0x00 movq %fs:0, %rax
-                 *      0x09 addq x@gottopoff(%rip), %rax
-                 *      0x10
+                 * Transition 1:
+                 *      movq %fs:0, %reg
+                 *      addq x@gottpoff(%rip), %reg
                  * To:
-                 *      0x00 movq %fs:0, %rax
-                 *      0x09 leaq x@tpoff(%rax), %rax
-                 *      0x10
+                 *      movq %fs:0, %reg
+                 *      leaq x@tpoff(%reg), %reg
+                 *
+                 * Transition (as a special case):
+                 *      movq %fs:0, %r12/%rsp
+                 *      addq x@gottpoff(%rip), %r12/%rsp
+                 * To:
+                 *      movq %fs:0, %r12/%rsp
+                 *      addq x@tpoff(%rax), %r12/%rsp
+                 *
+                 * Transition 2:
+                 *      movq x@gottpoff(%rip), %reg
+                 *      movq %fs:(%reg), %reg
+                 * To:
+                 *      movq x@tpoff(%reg), %reg
+                 *      movq %fs:(%reg), %reg
                  */
+                Conv_inv_buf_t  inv_buf;
+                uint8_t reg;            /* Register */
+
+                offset -= 3;
+
+                reg = offset[2] >> 3; /* Encoded dest. reg. operand */
+
                 DBG_CALL(Dbg_reloc_transition(ofl->ofl_lml, M_MACH,
                     R_AMD64_TPOFF32, arsp, ld_reloc_sym_name));
                 arsp->rel_rtype = R_AMD64_TPOFF32;
                 arsp->rel_raddend = 0;
 
                 /*
-                 * Adjust 'offset' to beginning of instruction sequence.
-                 */
-                offset -= 12;
+                 * This is transition 2, and the special case of form 1 where
+                 * a normal transition would index %rsp or %r12 and need a SIB
+                 * byte in the leaq for which we lack space
+                 */
+                if ((offset[1] == INSN_MOVMR) ||
+                    ((offset[1] == INSN_ADDMR) && (reg == REG_ESP))) {
+                        /*
+                         * If we needed an extra bit of MOD.reg to refer to
+                         * this register as the dest of the original movq we
+                         * need an extra bit of MOD.rm to refer to it in the
+                         * dest of the replacement movq or addq.
+                         */
+                        if (offset[0] == REX_RW)
+                                offset[0] = REX_BW;
+
+                        offset[1] = (offset[1] == INSN_MOVMR) ?
+                            INSN_MOVIR : INSN_ADDIR;
+                        offset[2] = 0xc0 | reg;
 
+                        return (FIX_RELOC);
+                } else if (offset[1] == INSN_ADDMR) {
                 /*
-                 * Same code sequence used in the GD -> LE transition.
+                         * If we needed an extra bit of MOD.reg to refer to
+                         * this register in the dest of the addq we need an
+                         * extra bit of both MOD.reg and MOD.rm to refer to it
+                         * in the source and dest of the leaq
                  */
-                (void) memcpy(offset, tlsinstr_gd_le, sizeof (tlsinstr_gd_le));
+                        if (offset[0] == REX_RW)
+                                offset[0] = REX_BRW;
+
+                        offset[1] = INSN_LEA;
+                        offset[2] = 0x80 | (reg << 3) | reg;
+
                 return (FIX_RELOC);
+                }
 
+                ld_eprintf(ofl, ERR_FATAL, MSG_INTL(MSG_REL_BADTLSINS),
+                    conv_reloc_amd64_type(arsp->rel_rtype, 0, &inv_buf),
+                    arsp->rel_isdesc->is_file->ifl_name,
+                    ld_reloc_sym_name(arsp),
+                    arsp->rel_isdesc->is_name,
+                    EC_OFF(arsp->rel_roffset));
+                return (FIX_ERROR);
+        }
         case R_AMD64_TLSLD:
                 /*
                  * LD -> LE
                  *
                  * Transition