platform_bionic/linker/linker_mips.cpp
Goran Ferenc e8c76b7a30 MIPS: Fix MIPS linker VDSO issues
This patch resolves two issues:
(1) AOSP MIPS linker crash with SIGSEGV while relocating VDSO GOT
(2) Missing of MIPS_ABI_FP_ANY flag while ckecking & adjusting MIPS FP modes

(1): AOSP MIPS linker crash with SIGSEGV while relocating VDSO GOT

During bringup and testing of the kernel VDSO support on the emulator we
encountered an issue where all userspace processes were crashing due to
linker crash with SIGSEGV during VDSO GOT relocation.

The mentioned scenario will trigger SIGSEGV in the linker only if the kernel
code base contains VDSO implementation introduced with the following commits:

a7f4df4 MIPS: VDSO: Add implementations of gettimeofday() and clock_gettime()
c0a9f72 irqchip: irq-mips-gic: Provide function to map GIC user section
ebb5e78 MIPS: Initial implementation of a VDSO

Another prerequisite is that the linker contains the following commit from AOSP:
https://android-review.googlesource.com/#/c/264857/

The above commit introduces auxvec.h header containing definition of
AT_SYSINFO_EHDR in:
https://android.googlesource.com/platform/bionic/+/master/libc/kernel/uapi/asm-mips/asm/auxvec.h,
which in turn activates VDSO GOT relocations in mips_relocate_got():
https://android.googlesource.com/platform/bionic/+/master/linker/linker_mips.cpp#149

Since VDSO is mapped as a RO region, writing anything to its page will result
in SIGSEGV.

Removing this 0xdeadbeef cookies writes to the got[0]/got[1] solved SIGSEGV issue.
We also compared with the glibc linker code and we haven't seen anything similar
like writing some cookie values into GOT entries.

(2): Missing of MIPS_ABI_FP_ANY flag while checking & adjusting MIPS FP modes

This issue was found during testing of the patch:
https://android-review.googlesource.com/#/c/platform/bionic/+/494440/

This patch adds a bionic dlfcn.dlopen_vdso test which tries to open "linux-vdso.so.1"
(virtual ELF shared library) and expects to succeed.

dlopen fails in the mips linker part due to the following error:
"Unsupported MIPS32 FloatPt ABI 0 found in linux.vdso.so.1"

According to:
https://dmz-portal.imgtec.com/wiki/MIPS_O32_ABI_-_FR0_and_FR1_Interlinking#A.2._O32_FPXX_code_calling_FPXX,
ABI 0 is:
Val_GNU_MIPS_ABI_FP_ANY = 0 /* Not tagged or not using any ABIs affected by the differences.  */

The issue appeared because MIPS_ABI_FP_ANY is not supposed to be checked as
a possible FP ABI-variant compatibility flag.

This patch adds logic to MIPS linker to handle this case appropriately,
after which dlfcn.dlopen_vdso test passes.

Test:
* bionic-unit-tests --gtest_filter=dlfcn.dlopen_vdso
* Boot android in emulator with kernel which supports VDSO optimizations

Change-Id: Icbcd9879beea1b38fbe8d97b3b205058eaa861f4
Signed-off-by: Goran Ferenc <goran.ferenc@imgtec.com>
2017-10-13 12:08:30 +02:00

334 lines
13 KiB
C++

/*
* 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>
#endif
#include "linker.h"
#include "linker_debug.h"
#include "linker_globals.h"
#include "linker_phdr.h"
#include "linker_relocs.h"
#include "linker_reloc_iterators.h"
#include "linker_sleb128.h"
#include "linker_soinfo.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.
// FIXME: maybe this should be in a separate routine?
if ((flags_ & FLAG_LINKER) == 0) {
size_t g = 1;
// Check for the high bit to determine whether to skip got[1]
if (reinterpret_cast<intptr_t>(got[g]) < 0) {
g++;
}
// 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
uint32_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_ANY 0 // Not tagged or not using any ABIs affected by the differences.
#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 compilers 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_ANY ||
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___