aboutsummaryrefslogtreecommitdiffstats
path: root/linker/linker_mips.cpp
blob: 75ea29042cea5a78bee3c3b03d978d5ce526be9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
 * Copyright (C) 2015 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#if !defined(__LP64__) && __mips_isa_rev >= 5
#include <sys/prctl.h>
#if defined(PR_SET_FP_MODE)
#error "remove following defs when avail in Android's kernel headers"
#else
#define PR_SET_FP_MODE 45
#define PR_GET_FP_MODE 46
#define PR_FP_MODE_FR  (1 << 0)
#define PR_FP_MODE_FRE (1 << 1)
#endif
#endif

#include "linker.h"
#include "linker_debug.h"
#include "linker_phdr.h"
#include "linker_relocs.h"
#include "linker_reloc_iterators.h"
#include "linker_sleb128.h"

template bool soinfo::relocate<plain_reloc_iterator>(const VersionTracker& version_tracker,
                                                     plain_reloc_iterator&& rel_iterator,
                                                     const soinfo_list_t& global_group,
                                                     const soinfo_list_t& local_group);

template bool soinfo::relocate<packed_reloc_iterator<sleb128_decoder>>(
    const VersionTracker& version_tracker,
    packed_reloc_iterator<sleb128_decoder>&& rel_iterator,
    const soinfo_list_t& global_group,
    const soinfo_list_t& local_group);

template <typename ElfRelIteratorT>
bool soinfo::relocate(const VersionTracker& version_tracker,
                      ElfRelIteratorT&& rel_iterator,
                      const soinfo_list_t& global_group,
                      const soinfo_list_t& local_group) {
  for (size_t idx = 0; rel_iterator.has_next(); ++idx) {
    const auto rel = rel_iterator.next();

    if (rel == nullptr) {
      return false;
    }

    ElfW(Word) type = ELFW(R_TYPE)(rel->r_info);
    ElfW(Word) sym = ELFW(R_SYM)(rel->r_info);

    ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + load_bias);
    ElfW(Addr) sym_addr = 0;
    const char* sym_name = nullptr;

    DEBUG("Processing '%s' relocation at index %zd", get_realpath(), idx);
    if (type == R_GENERIC_NONE) {
      continue;
    }

    const ElfW(Sym)* s = nullptr;
    soinfo* lsi = nullptr;

    if (sym != 0) {
      sym_name = get_string(symtab_[sym].st_name);
      const version_info* vi = nullptr;

      if (!lookup_version_info(version_tracker, sym, sym_name, &vi)) {
        return false;
      }

      if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) {
        return false;
      }

      if (s == nullptr) {
        // mips does not support relocation with weak-undefined symbols
        DL_ERR("cannot locate symbol \"%s\" referenced by \"%s\"...",
               sym_name, get_realpath());
        return false;
      } else {
        // We got a definition.
        sym_addr = lsi->resolve_symbol_address(s);
      }
      count_relocation(kRelocSymbol);
    }

    switch (type) {
      case R_MIPS_REL32:
#if defined(__LP64__)
        // MIPS Elf64_Rel entries contain compound relocations
        // We only handle the R_MIPS_NONE|R_MIPS_64|R_MIPS_REL32 case
        if (ELF64_R_TYPE2(rel->r_info) != R_MIPS_64 ||
            ELF64_R_TYPE3(rel->r_info) != R_MIPS_NONE) {
          DL_ERR("Unexpected compound relocation type:%d type2:%d type3:%d @ %p (%zu)",
                 type, static_cast<unsigned>(ELF64_R_TYPE2(rel->r_info)),
                 static_cast<unsigned>(ELF64_R_TYPE3(rel->r_info)), rel, idx);
          return false;
        }
#endif
        count_relocation(s == nullptr ? kRelocAbsolute : kRelocRelative);
        MARK(rel->r_offset);
        TRACE_TYPE(RELO, "RELO REL32 %08zx <- %08zx %s", static_cast<size_t>(reloc),
                   static_cast<size_t>(sym_addr), sym_name ? sym_name : "*SECTIONHDR*");
        if (s != nullptr) {
          *reinterpret_cast<ElfW(Addr)*>(reloc) += sym_addr;
        } else {
          *reinterpret_cast<ElfW(Addr)*>(reloc) += load_bias;
        }
        break;
      default:
        DL_ERR("unknown reloc type %d @ %p (%zu)", type, rel, idx);
        return false;
    }
  }
  return true;
}

bool soinfo::mips_relocate_got(const VersionTracker& version_tracker,
                               const soinfo_list_t& global_group,
                               const soinfo_list_t& local_group) {
  ElfW(Addr)** got = plt_got_;
  if (got == nullptr) {
    return true;
  }

  // got[0] is the address of the lazy resolver function.
  // got[1] may be used for a GNU extension.
  // Set it to a recognizable address in case someone calls it (should be _rtld_bind_start).
  // FIXME: maybe this should be in a separate routine?
  if ((flags_ & FLAG_LINKER) == 0) {
    size_t g = 0;
    got[g++] = reinterpret_cast<ElfW(Addr)*>(0xdeadbeef);
    if (reinterpret_cast<intptr_t>(got[g]) < 0) {
      got[g++] = reinterpret_cast<ElfW(Addr)*>(0xdeadfeed);
    }
    // Relocate the local GOT entries.
    for (; g < mips_local_gotno_; g++) {
      got[g] = reinterpret_cast<ElfW(Addr)*>(reinterpret_cast<uintptr_t>(got[g]) + load_bias);
    }
  }

  // Now for the global GOT entries...
  got = plt_got_ + mips_local_gotno_;
  for (ElfW(Word) sym = mips_gotsym_; sym < mips_symtabno_; sym++, got++) {
    // This is an undefined reference... try to locate it.
    const ElfW(Sym)* local_sym = symtab_ + sym;
    const char* sym_name = get_string(local_sym->st_name);
    soinfo* lsi = nullptr;
    const ElfW(Sym)* s = nullptr;

    ElfW(Word) st_visibility = (local_sym->st_other & 0x3);

    if (st_visibility == STV_DEFAULT) {
      const version_info* vi = nullptr;

      if (!lookup_version_info(version_tracker, sym, sym_name, &vi)) {
        return false;
      }

      if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) {
        return false;
      }
    } else if (st_visibility == STV_PROTECTED) {
      if (local_sym->st_value == 0) {
        DL_ERR("%s: invalid symbol \"%s\" (PROTECTED/UNDEFINED) ",
               get_realpath(), sym_name);
        return false;
      }
      s = local_sym;
      lsi = this;
    } else {
      DL_ERR("%s: invalid symbol \"%s\" visibility: 0x%x",
             get_realpath(), sym_name, st_visibility);
      return false;
    }

    if (s == nullptr) {
      // We only allow an undefined symbol if this is a weak reference.
      if (ELF_ST_BIND(local_sym->st_info) != STB_WEAK) {
        DL_ERR("%s: cannot locate \"%s\"...", get_realpath(), sym_name);
        return false;
      }
      *got = 0;
    } else {
      // FIXME: is this sufficient?
      // For reference see NetBSD link loader
      // http://cvsweb.netbsd.org/bsdweb.cgi/src/libexec/ld.elf_so/arch/mips/mips_reloc.c?rev=1.53&content-type=text/x-cvsweb-markup
      *got = reinterpret_cast<ElfW(Addr)*>(lsi->resolve_symbol_address(s));
    }
  }
  return true;
}

#if !defined(__LP64__)

// Checks for mips32's various floating point abis.
// (Mips64 Android has a single floating point abi and doesn't need any checks)

// Linux kernel has declarations similar to the following
//   in <linux>/arch/mips/include/asm/elf.h,
// but that non-uapi internal header file will never be imported
// into bionic's kernel headers.

#define PT_MIPS_ABIFLAGS  0x70000003	// is .MIPS.abiflags segment

struct mips_elf_abiflags_v0 {
  uint16_t version;  // version of this structure
  uint8_t  isa_level, isa_rev, gpr_size, cpr1_size, cpr2_size;
  uint8_t  fp_abi;  // mips32 ABI variants for floating point
  uint16_t isa_ext, ases, flags1, flags2;
};

// Bits of flags1:
#define MIPS_AFL_FLAGS1_ODDSPREG 1  // Uses odd-numbered single-prec fp regs

// Some values of fp_abi:        via compiler flag:
#define MIPS_ABI_FP_DOUBLE 1  // -mdouble-float
#define MIPS_ABI_FP_XX     5  // -mfpxx
#define MIPS_ABI_FP_64A    7  // -mips32r* -mfp64 -mno-odd-spreg

#if __mips_isa_rev >= 5
static bool mips_fre_mode_on = false;  // have set FRE=1 mode for process
#endif

bool soinfo::mips_check_and_adjust_fp_modes() {
  mips_elf_abiflags_v0* abiflags = nullptr;
  int mips_fpabi;

  // Find soinfo's optional .MIPS.abiflags segment
  for (size_t i = 0; i<phnum; ++i) {
    const ElfW(Phdr)& ph = phdr[i];
    if (ph.p_type == PT_MIPS_ABIFLAGS) {
      if (ph.p_filesz < sizeof (mips_elf_abiflags_v0)) {
        DL_ERR("Corrupt PT_MIPS_ABIFLAGS header found \"%s\"", get_realpath());
        return false;
      }
      abiflags = reinterpret_cast<mips_elf_abiflags_v0*>(ph.p_vaddr + load_bias);
      break;
    }
  }

  // FP ABI-variant compatibility checks for MIPS o32 ABI
  if (abiflags == nullptr) {
    // Old compiles lack the new abiflags section.
    // These compilers used -mfp32 -mdouble-float -modd-spreg defaults,
    //   ie FP32 aka DOUBLE, using odd-numbered single-prec regs
    mips_fpabi = MIPS_ABI_FP_DOUBLE;
  } else {
    mips_fpabi = abiflags->fp_abi;
    if ( (abiflags->flags1 & MIPS_AFL_FLAGS1_ODDSPREG)
         && (mips_fpabi == MIPS_ABI_FP_XX ||
             mips_fpabi == MIPS_ABI_FP_64A   ) ) {
      // Android supports fewer cases than Linux
      DL_ERR("Unsupported odd-single-prec FloatPt reg uses in \"%s\"",
             get_realpath());
      return false;
    }
  }
  if (!(mips_fpabi == MIPS_ABI_FP_DOUBLE ||
#if __mips_isa_rev >= 5
        mips_fpabi == MIPS_ABI_FP_64A    ||
#endif
        mips_fpabi == MIPS_ABI_FP_XX       )) {
    DL_ERR("Unsupported MIPS32 FloatPt ABI %d found in \"%s\"",
           mips_fpabi, get_realpath());
    return false;
  }

#if __mips_isa_rev >= 5
  // Adjust process's FR Emulation mode, if needed
  //
  // On Mips R5 & R6, Android runs continuously in FR=1 64bit-fpreg mode.
  // NDK mips32 apps compiled with old compilers generate FP32 code
  //   which expects FR=0 32-bit fp registers.
  // NDK mips32 apps compiled with newer compilers generate modeless
  //   FPXX code which runs on both FR=0 and FR=1 modes.
  // Android itself is compiled in FP64A which requires FR=1 mode.
  // FP32, FPXX, and FP64A all interlink okay, without dynamic FR mode
  //   changes during calls.  For details, see
  //   http://dmz-portal.mips.com/wiki/MIPS_O32_ABI_-_FR0_and_FR1_Interlinking
  // Processes containing FR32 FR=0 code are run via kernel software assist,
  //   which maps all odd-numbered single-precision reg refs onto the
  //   upper half of the paired even-numbered double-precision reg.
  // FRE=1 triggers traps to the kernel's emulator on every single-precision
  //   fp op (for both odd and even-numbered registers).
  // Turning on FRE=1 traps is done at most once per process, simultanously
  //   for all threads of that process, when dlopen discovers FP32 code.
  // The kernel repacks threads' registers when FRE mode is turn on or off.
  //   These asynchronous adjustments are wrong if any thread was executing
  //   FPXX code using odd-numbered single-precision regs.
  // Current Android compilers default to the -mno-oddspreg option,
  //   and this requirement is checked by Android's dlopen.
  //   So FRE can always be safely turned on for FP32, anytime.
  // Deferred enhancement: Allow loading of odd-spreg FPXX modules.

  if (mips_fpabi == MIPS_ABI_FP_DOUBLE && !mips_fre_mode_on) {
    // Turn on FRE mode, which emulates mode-sensitive FR=0 code on FR=1
    //   register files, by trapping to kernel on refs to single-precision regs
    if (prctl(PR_SET_FP_MODE, PR_FP_MODE_FR|PR_FP_MODE_FRE)) {
      DL_ERR("Kernel or cpu failed to set FRE mode required for running \"%s\"",
             get_realpath());
      return false;
    }
    DL_WARN("Using FRE=1 mode to run \"%s\"", get_realpath());
    mips_fre_mode_on = true;  // Avoid future redundant mode-switch calls
    // FRE mode is never turned back off.
    // Deferred enhancement:
    //   Reset FRE mode when dlclose() removes all FP32 modules
  }
#else
  // Android runs continuously in FR=0 32bit-fpreg mode.
#endif  // __mips_isa_rev
  return true;
}

#endif  // __LP64___