636 lines
22 KiB
C++
636 lines
22 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// This program acts as a linker wrapper. Its executable name is meant
|
|
// to be that of a linker, and it will find the next linker with the same
|
|
// name in $PATH. However, if for some reason the next linker cannot be
|
|
// found this way, the caller may pass its path via the --real-linker
|
|
// option.
|
|
//
|
|
// More in-depth background on https://glandium.org/blog/?p=4297
|
|
|
|
#include "relrhack.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <spawn.h>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "mozilla/ScopeExit.h"
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
class CantSwapSections : public std::runtime_error {
|
|
public:
|
|
CantSwapSections(const char* what) : std::runtime_error(what) {}
|
|
};
|
|
|
|
template <int bits>
|
|
struct Elf {};
|
|
|
|
#define ELF(bits) \
|
|
template <> \
|
|
struct Elf<bits> { \
|
|
using Ehdr = Elf##bits##_Ehdr; \
|
|
using Phdr = Elf##bits##_Phdr; \
|
|
using Shdr = Elf##bits##_Shdr; \
|
|
using Dyn = Elf##bits##_Dyn; \
|
|
using Addr = Elf##bits##_Addr; \
|
|
using Word = Elf##bits##_Word; \
|
|
using Off = Elf##bits##_Off; \
|
|
using Verneed = Elf##bits##_Verneed; \
|
|
using Vernaux = Elf##bits##_Vernaux; \
|
|
}
|
|
|
|
ELF(32);
|
|
ELF(64);
|
|
|
|
template <int bits>
|
|
struct RelR : public Elf<bits> {
|
|
using Elf_Ehdr = typename Elf<bits>::Ehdr;
|
|
using Elf_Phdr = typename Elf<bits>::Phdr;
|
|
using Elf_Shdr = typename Elf<bits>::Shdr;
|
|
using Elf_Dyn = typename Elf<bits>::Dyn;
|
|
using Elf_Addr = typename Elf<bits>::Addr;
|
|
using Elf_Word = typename Elf<bits>::Word;
|
|
using Elf_Off = typename Elf<bits>::Off;
|
|
using Elf_Verneed = typename Elf<bits>::Verneed;
|
|
using Elf_Vernaux = typename Elf<bits>::Vernaux;
|
|
|
|
#define TAG_NAME(t) {t, #t}
|
|
class DynInfo {
|
|
public:
|
|
using Tag = decltype(Elf_Dyn::d_tag);
|
|
using Value = decltype(Elf_Dyn::d_un.d_val);
|
|
bool is_wanted(Tag tag) const { return tag_names.count(tag); }
|
|
void insert(off_t offset, Tag tag, Value val) {
|
|
data[tag] = std::make_pair(offset, val);
|
|
}
|
|
off_t offset(Tag tag) const { return data.at(tag).first; }
|
|
bool contains(Tag tag) const { return data.count(tag); }
|
|
Value& operator[](Tag tag) {
|
|
if (!is_wanted(tag)) {
|
|
std::stringstream msg;
|
|
msg << "Tag 0x" << std::hex << tag << " is not in DynInfo::tag_names";
|
|
throw std::runtime_error(msg.str());
|
|
}
|
|
return data[tag].second;
|
|
}
|
|
const char* name(Tag tag) const { return tag_names.at(tag); }
|
|
|
|
private:
|
|
std::unordered_map<Tag, std::pair<off_t, Value>> data;
|
|
|
|
const std::unordered_map<Tag, const char*> tag_names = {
|
|
TAG_NAME(DT_JMPREL), TAG_NAME(DT_PLTRELSZ), TAG_NAME(DT_RELR),
|
|
TAG_NAME(DT_RELRENT), TAG_NAME(DT_RELRSZ), TAG_NAME(DT_RELA),
|
|
TAG_NAME(DT_RELASZ), TAG_NAME(DT_RELAENT), TAG_NAME(DT_REL),
|
|
TAG_NAME(DT_RELSZ), TAG_NAME(DT_RELENT), TAG_NAME(DT_STRTAB),
|
|
TAG_NAME(DT_STRSZ), TAG_NAME(DT_VERNEED), TAG_NAME(DT_VERNEEDNUM),
|
|
};
|
|
};
|
|
|
|
// Translate a virtual address into an offset in the file based on the program
|
|
// headers' PT_LOAD.
|
|
static Elf_Addr get_offset(const std::vector<Elf_Phdr>& phdr, Elf_Addr addr) {
|
|
for (const auto& p : phdr) {
|
|
if (p.p_type == PT_LOAD && addr >= p.p_vaddr &&
|
|
addr < p.p_vaddr + p.p_filesz) {
|
|
return addr - (p.p_vaddr - p.p_paddr);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool hack(std::fstream& f, bool set_relrhack_bit);
|
|
};
|
|
|
|
template <typename T>
|
|
T read_one_at(std::istream& in, off_t pos) {
|
|
T result;
|
|
in.seekg(pos, std::ios::beg);
|
|
in.read(reinterpret_cast<char*>(&result), sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
std::vector<T> read_vector_at(std::istream& in, off_t pos, size_t num) {
|
|
std::vector<T> result(num);
|
|
in.seekg(pos, std::ios::beg);
|
|
in.read(reinterpret_cast<char*>(result.data()), num * sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
void write_at(std::ostream& out, off_t pos, const char* buf, size_t len) {
|
|
out.seekp(pos, std::ios::beg);
|
|
out.write(buf, len);
|
|
}
|
|
|
|
template <typename T>
|
|
void write_one_at(std::ostream& out, off_t pos, const T& data) {
|
|
write_at(out, pos, reinterpret_cast<const char*>(&data), sizeof(T));
|
|
}
|
|
|
|
template <typename T>
|
|
void write_vector_at(std::ostream& out, off_t pos, const std::vector<T>& vec) {
|
|
write_at(out, pos, reinterpret_cast<const char*>(&vec.front()),
|
|
vec.size() * sizeof(T));
|
|
}
|
|
|
|
template <int bits>
|
|
bool RelR<bits>::hack(std::fstream& f, bool set_relrhack_bit) {
|
|
auto ehdr = read_one_at<Elf_Ehdr>(f, 0);
|
|
if (ehdr.e_phentsize != sizeof(Elf_Phdr)) {
|
|
throw std::runtime_error("Invalid ELF?");
|
|
}
|
|
auto phdr = read_vector_at<Elf_Phdr>(f, ehdr.e_phoff, ehdr.e_phnum);
|
|
const auto& dyn_phdr =
|
|
std::find_if(phdr.begin(), phdr.end(),
|
|
[](const auto& p) { return p.p_type == PT_DYNAMIC; });
|
|
if (dyn_phdr == phdr.end()) {
|
|
return false;
|
|
}
|
|
if (dyn_phdr->p_filesz % sizeof(Elf_Dyn)) {
|
|
throw std::runtime_error("Invalid ELF?");
|
|
}
|
|
auto dyn = read_vector_at<Elf_Dyn>(f, dyn_phdr->p_offset,
|
|
dyn_phdr->p_filesz / sizeof(Elf_Dyn));
|
|
off_t dyn_offset = dyn_phdr->p_offset;
|
|
DynInfo dyn_info;
|
|
for (const auto& d : dyn) {
|
|
if (d.d_tag == DT_NULL) {
|
|
break;
|
|
}
|
|
|
|
if (dyn_info.is_wanted(d.d_tag)) {
|
|
if (dyn_info.contains(d.d_tag)) {
|
|
std::stringstream msg;
|
|
msg << dyn_info.name(d.d_tag) << " appears twice?";
|
|
throw std::runtime_error(msg.str());
|
|
}
|
|
dyn_info.insert(dyn_offset, d.d_tag, d.d_un.d_val);
|
|
}
|
|
dyn_offset += sizeof(Elf_Dyn);
|
|
}
|
|
|
|
// Find the location and size of the SHT_RELR section, which contains the
|
|
// packed-relative-relocs.
|
|
Elf_Addr relr_off =
|
|
dyn_info.contains(DT_RELR) ? get_offset(phdr, dyn_info[DT_RELR]) : 0;
|
|
Elf_Off relrsz = dyn_info[DT_RELRSZ];
|
|
const decltype(Elf_Dyn::d_tag) rel_tags[3][2] = {
|
|
{DT_REL, DT_RELA}, {DT_RELSZ, DT_RELASZ}, {DT_RELENT, DT_RELAENT}};
|
|
for (const auto& [rel_tag, rela_tag] : rel_tags) {
|
|
if (dyn_info.contains(rel_tag) && dyn_info.contains(rela_tag)) {
|
|
std::stringstream msg;
|
|
msg << "Both " << dyn_info.name(rel_tag) << " and "
|
|
<< dyn_info.name(rela_tag) << " appear?";
|
|
throw std::runtime_error(msg.str());
|
|
}
|
|
}
|
|
Elf_Off relent =
|
|
dyn_info.contains(DT_RELENT) ? dyn_info[DT_RELENT] : dyn_info[DT_RELAENT];
|
|
|
|
// Estimate the size of the unpacked relative relocations corresponding
|
|
// to the SHT_RELR section.
|
|
auto relr = read_vector_at<Elf_Addr>(f, relr_off, relrsz / sizeof(Elf_Addr));
|
|
size_t relocs = 0;
|
|
for (const auto& entry : relr) {
|
|
if ((entry & 1) == 0) {
|
|
// LSB is 0, this is a pointer for a single relocation.
|
|
relocs++;
|
|
} else {
|
|
// LSB is 1, remaining bits are a bitmap. Each bit represents a
|
|
// relocation.
|
|
relocs += __builtin_popcount(entry) - 1;
|
|
}
|
|
}
|
|
// If the packed relocations + some overhead (we pick 4K arbitrarily, the
|
|
// real size would require digging into the section sizes of the injected
|
|
// .o file, which is not worth the error) is larger than the estimated
|
|
// unpacked relocations, we'll just relink without packed relocations.
|
|
if (relocs * relent < relrsz + 4096) {
|
|
return false;
|
|
}
|
|
|
|
if (set_relrhack_bit) {
|
|
// Change DT_RELR* tags to add DT_RELRHACK_BIT.
|
|
for (const auto tag : {DT_RELR, DT_RELRSZ, DT_RELRENT}) {
|
|
write_one_at(f, dyn_info.offset(tag), tag | DT_RELRHACK_BIT);
|
|
}
|
|
}
|
|
|
|
bool is_glibc = false;
|
|
|
|
if (dyn_info.contains(DT_VERNEEDNUM) && dyn_info.contains(DT_VERNEED) &&
|
|
dyn_info.contains(DT_STRSZ) && dyn_info.contains(DT_STRTAB)) {
|
|
// Scan SHT_VERNEED for the GLIBC_ABI_DT_RELR version on the libc
|
|
// library.
|
|
Elf_Addr verneed_off = get_offset(phdr, dyn_info[DT_VERNEED]);
|
|
Elf_Off verneednum = dyn_info[DT_VERNEEDNUM];
|
|
// SHT_STRTAB section, which contains the string table for, among other
|
|
// things, the symbol versions in the SHT_VERNEED section.
|
|
auto strtab = read_vector_at<char>(f, get_offset(phdr, dyn_info[DT_STRTAB]),
|
|
dyn_info[DT_STRSZ]);
|
|
// Guarantee a nul character at the end of the string table.
|
|
strtab.push_back(0);
|
|
while (verneednum--) {
|
|
auto verneed = read_one_at<Elf_Verneed>(f, verneed_off);
|
|
if (std::string_view{"libc.so.6"} == &strtab.at(verneed.vn_file)) {
|
|
is_glibc = true;
|
|
Elf_Addr vernaux_off = verneed_off + verneed.vn_aux;
|
|
Elf_Addr relr = 0;
|
|
Elf_Vernaux reuse;
|
|
for (auto n = 0; n < verneed.vn_cnt; n++) {
|
|
auto vernaux = read_one_at<Elf_Vernaux>(f, vernaux_off);
|
|
if (std::string_view{"GLIBC_ABI_DT_RELR"} ==
|
|
&strtab.at(vernaux.vna_name)) {
|
|
relr = vernaux_off;
|
|
} else {
|
|
reuse = vernaux;
|
|
}
|
|
vernaux_off += vernaux.vna_next;
|
|
}
|
|
// In the case where we do have the GLIBC_ABI_DT_RELR version, we
|
|
// need to edit the binary to make the following changes:
|
|
// - Remove the GLIBC_ABI_DT_RELR version, we replace it with an
|
|
// arbitrary other version entry, which is simpler than completely
|
|
// removing it. We need to remove it because older versions of glibc
|
|
// don't have the version (after all, that's why the symbol version
|
|
// is there in the first place, to avoid running against older versions
|
|
// of glibc that don't support packed relocations).
|
|
// - Alter the DT_RELR* tags in the dynamic section, so that they
|
|
// are not recognized by ld.so, because, while all versions of ld.so
|
|
// ignore tags they don't know, glibc's ld.so versions that support
|
|
// packed relocations don't want to load a binary that has DT_RELR*
|
|
// tags but *not* a dependency on the GLIBC_ABI_DT_RELR version.
|
|
if (relr) {
|
|
// Don't overwrite vn_aux.
|
|
write_at(f, relr, reinterpret_cast<char*>(&reuse),
|
|
sizeof(reuse) - sizeof(Elf_Word));
|
|
}
|
|
}
|
|
verneed_off += verneed.vn_next;
|
|
}
|
|
}
|
|
|
|
// Location of the .rel.plt section.
|
|
Elf_Addr jmprel = dyn_info.contains(DT_JMPREL) ? dyn_info[DT_JMPREL] : 0;
|
|
if (is_glibc) {
|
|
#ifndef MOZ_STDCXX_COMPAT
|
|
try {
|
|
#endif
|
|
// ld.so in glibc 2.16 to 2.23 expects .rel.plt to strictly follow
|
|
// .rel.dyn. (https://sourceware.org/bugzilla/show_bug.cgi?id=14341)
|
|
// BFD ld places .relr.dyn after .rel.plt, so this works fine, but lld
|
|
// places it between both sections, which doesn't work out for us. In that
|
|
// case, we want to swap .relr.dyn and .rel.plt.
|
|
Elf_Addr rel_end = dyn_info.contains(DT_REL)
|
|
? (dyn_info[DT_REL] + dyn_info[DT_RELSZ])
|
|
: (dyn_info[DT_RELA] + dyn_info[DT_RELASZ]);
|
|
if (dyn_info.contains(DT_JMPREL) && dyn_info[DT_PLTRELSZ] &&
|
|
dyn_info[DT_JMPREL] != rel_end) {
|
|
if (dyn_info[DT_RELR] != rel_end) {
|
|
throw CantSwapSections("RELR section doesn't follow REL/RELA?");
|
|
}
|
|
if (dyn_info[DT_JMPREL] != dyn_info[DT_RELR] + dyn_info[DT_RELRSZ]) {
|
|
throw CantSwapSections("PLT REL/RELA doesn't follow RELR?");
|
|
}
|
|
auto plt_rel = read_vector_at<char>(
|
|
f, get_offset(phdr, dyn_info[DT_JMPREL]), dyn_info[DT_PLTRELSZ]);
|
|
// Write the content of both sections swapped, and adjust the
|
|
// corresponding PT_DYNAMIC entries.
|
|
write_vector_at(f, relr_off, plt_rel);
|
|
write_vector_at(f, relr_off + plt_rel.size(), relr);
|
|
dyn_info[DT_JMPREL] = rel_end;
|
|
dyn_info[DT_RELR] = rel_end + plt_rel.size();
|
|
for (const auto tag : {DT_JMPREL, DT_RELR}) {
|
|
write_one_at(f, dyn_info.offset(tag) + sizeof(typename DynInfo::Tag),
|
|
dyn_info[tag]);
|
|
}
|
|
}
|
|
#ifndef MOZ_STDCXX_COMPAT
|
|
} catch (const CantSwapSections& err) {
|
|
// When binary compatibility with older libstdc++/glibc is not enabled, we
|
|
// only emit a warning about why swapping the sections is not happening.
|
|
std::cerr << "WARNING: " << err.what() << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
off_t shdr_offset = ehdr.e_shoff;
|
|
auto shdr = read_vector_at<Elf_Shdr>(f, ehdr.e_shoff, ehdr.e_shnum);
|
|
for (auto& s : shdr) {
|
|
// Some tools don't like sections of types they don't know, so change
|
|
// SHT_RELR, which might be unknown on older systems, to SHT_PROGBITS.
|
|
if (s.sh_type == SHT_RELR) {
|
|
s.sh_type = SHT_PROGBITS;
|
|
// If DT_RELR has been adjusted to swap with DT_JMPREL, also adjust
|
|
// the corresponding SHT_RELR section header.
|
|
if (s.sh_addr != dyn_info[DT_RELR]) {
|
|
s.sh_offset += dyn_info[DT_RELR] - s.sh_addr;
|
|
s.sh_addr = dyn_info[DT_RELR];
|
|
}
|
|
write_one_at(f, shdr_offset, s);
|
|
} else if (jmprel && (s.sh_addr == jmprel) &&
|
|
(s.sh_addr != dyn_info[DT_JMPREL])) {
|
|
// If DT_JMPREL has been adjusted to swap with DT_RELR, also adjust
|
|
// the corresponding section header.
|
|
s.sh_offset -= s.sh_addr - dyn_info[DT_JMPREL];
|
|
s.sh_addr = dyn_info[DT_JMPREL];
|
|
write_one_at(f, shdr_offset, s);
|
|
}
|
|
shdr_offset += sizeof(Elf_Shdr);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> get_path() {
|
|
std::vector<std::string> result;
|
|
std::stringstream stream{std::getenv("PATH")};
|
|
std::string item;
|
|
|
|
while (std::getline(stream, item, ':')) {
|
|
result.push_back(std::move(item));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::optional<fs::path> next_program(fs::path& this_program,
|
|
std::optional<fs::path>& program) {
|
|
auto program_name = program ? *program : this_program.filename();
|
|
for (const auto& dir : get_path()) {
|
|
auto path = fs::path(dir) / program_name;
|
|
auto status = fs::status(path);
|
|
if ((status.type() == fs::file_type::regular) &&
|
|
((status.permissions() & fs::perms::owner_exec) ==
|
|
fs::perms::owner_exec) &&
|
|
!fs::equivalent(path, this_program))
|
|
return path;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
unsigned char get_elf_class(unsigned char (&e_ident)[EI_NIDENT]) {
|
|
if (std::string_view{reinterpret_cast<char*>(e_ident), SELFMAG} !=
|
|
std::string_view{ELFMAG, SELFMAG}) {
|
|
throw std::runtime_error("Not ELF?");
|
|
}
|
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
if (e_ident[EI_DATA] != ELFDATA2LSB) {
|
|
throw std::runtime_error("Not Little Endian ELF?");
|
|
}
|
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
|
if (e_ident[EI_DATA] != ELFDATA2MSB) {
|
|
throw std::runtime_error("Not Big Endian ELF?");
|
|
}
|
|
#else
|
|
# error Unknown byte order.
|
|
#endif
|
|
if (e_ident[EI_VERSION] != 1) {
|
|
throw std::runtime_error("Not ELF version 1?");
|
|
}
|
|
auto elf_class = e_ident[EI_CLASS];
|
|
if (elf_class != ELFCLASS32 && elf_class != ELFCLASS64) {
|
|
throw std::runtime_error("Not 32 or 64-bits ELF?");
|
|
}
|
|
return elf_class;
|
|
}
|
|
|
|
unsigned char get_elf_class(std::istream& in) {
|
|
unsigned char e_ident[EI_NIDENT];
|
|
in.read(reinterpret_cast<char*>(e_ident), sizeof(e_ident));
|
|
return get_elf_class(e_ident);
|
|
}
|
|
|
|
uint16_t get_elf_machine(std::istream& in) {
|
|
// As far as e_machine is concerned, both Elf32_Ehdr and Elf64_Ehdr are equal.
|
|
Elf32_Ehdr ehdr;
|
|
in.read(reinterpret_cast<char*>(&ehdr), sizeof(ehdr));
|
|
// get_elf_class will throw exceptions for the cases we don't handle.
|
|
get_elf_class(ehdr.e_ident);
|
|
return ehdr.e_machine;
|
|
}
|
|
|
|
int run_command(std::vector<const char*>& args, bool use_response_file) {
|
|
std::string at_file;
|
|
const char** argv = args.data();
|
|
std::array<const char*, 3> args_with_atfile{};
|
|
if (use_response_file) {
|
|
const char* tmpdir = getenv("TMPDIR");
|
|
if (!tmpdir) {
|
|
tmpdir = "/tmp";
|
|
}
|
|
std::string tmpfile = tmpdir;
|
|
tmpfile += "/relrhackXXXXXX";
|
|
int fd = mkstemp(tmpfile.data());
|
|
if (fd < 0) {
|
|
std::cerr << "Failed to create temporary file." << std::endl;
|
|
return 1;
|
|
}
|
|
close(fd);
|
|
std::ofstream f{tmpfile, f.binary};
|
|
for (auto arg = std::next(args.begin()); arg != args.end(); ++arg) {
|
|
f << *arg << "\n";
|
|
}
|
|
at_file = "@";
|
|
at_file += tmpfile;
|
|
args_with_atfile = {args.front(), at_file.c_str(), nullptr};
|
|
argv = args_with_atfile.data();
|
|
}
|
|
auto guard = mozilla::MakeScopeExit([&] {
|
|
if (!at_file.empty()) {
|
|
unlink(at_file.c_str() + 1);
|
|
}
|
|
});
|
|
pid_t child_pid;
|
|
if (posix_spawn(&child_pid, args[0], nullptr, nullptr,
|
|
const_cast<char* const*>(argv), environ) != 0) {
|
|
throw std::runtime_error("posix_spawn failed");
|
|
}
|
|
|
|
int status;
|
|
waitpid(child_pid, &status, 0);
|
|
return WEXITSTATUS(status);
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
auto this_program = fs::absolute(argv[0]);
|
|
|
|
std::vector<const char*> args;
|
|
|
|
int i, crti = 0;
|
|
std::optional<fs::path> output = std::nullopt;
|
|
std::optional<fs::path> real_linker = std::nullopt;
|
|
bool shared = false;
|
|
bool is_android = false;
|
|
bool use_response_file = false;
|
|
std::vector<char> response_file;
|
|
std::vector<const char*> response_file_args;
|
|
uint16_t elf_machine = EM_NONE;
|
|
// Scan argv in order to prepare the following:
|
|
// - get the output file. That's the file we may need to adjust.
|
|
// - get the --real-linker if one was passed.
|
|
// - detect whether we're linking a shared library or something else. As of
|
|
// now, only shared libraries are handled. Technically speaking, programs
|
|
// could be handled as well, but for the purpose of Firefox, that actually
|
|
// doesn't work because programs contain a memory allocator that ends up
|
|
// being called before the injected code has any chance to apply relocations,
|
|
// and the allocator itself needs the relocations to have been applied.
|
|
// - detect the position of crti.o so that we can inject our own object
|
|
// right after it, and also to detect the machine type to pick the right
|
|
// object to inject.
|
|
//
|
|
// At the same time, we also construct a new list of arguments, with
|
|
// --real-linker filtered out. We'll later inject arguments in that list.
|
|
if (argc == 2 && argv[1] && argv[1][0] == '@') {
|
|
// When GCC is given a response file, it wraps all arguments into a
|
|
// new response file with all arguments, even if originally there were
|
|
// arguments and a response file.
|
|
// In that case, we can't scan for arguments, so we need to read the
|
|
// response file. And as we change the arguments, we'll need to write
|
|
// a new one.
|
|
std::ifstream f{argv[1] + 1, f.binary | f.ate};
|
|
if (!f) {
|
|
std::cerr << "Failed to read " << argv[1] + 1 << std::endl;
|
|
return 1;
|
|
}
|
|
size_t len = f.tellg();
|
|
response_file = read_vector_at<char>(f, 0, len);
|
|
std::replace(response_file.begin(), response_file.end(), '\n', '\0');
|
|
if (len && response_file[len - 1] != '\0') {
|
|
response_file.push_back('\0');
|
|
}
|
|
response_file_args.push_back(argv[0]);
|
|
for (const char* a = response_file.data();
|
|
a < response_file.data() + response_file.size(); a += strlen(a) + 1) {
|
|
response_file_args.push_back(a);
|
|
}
|
|
argv = const_cast<char**>(response_file_args.data());
|
|
argc = response_file_args.size();
|
|
use_response_file = true;
|
|
}
|
|
for (i = 1, argv++; i < argc && *argv; argv++, i++) {
|
|
std::string_view arg{*argv};
|
|
if (arg == "-shared") {
|
|
shared = true;
|
|
} else if (arg == "-o") {
|
|
args.push_back(*(argv++));
|
|
++i;
|
|
output = *argv;
|
|
} else if (arg == "--real-linker") {
|
|
++i;
|
|
real_linker = *(++argv);
|
|
continue;
|
|
} else if (elf_machine == EM_NONE) {
|
|
auto filename = fs::path(arg).filename();
|
|
if (filename == "crti.o" || filename == "crtbegin_so.o") {
|
|
is_android = (filename == "crtbegin_so.o");
|
|
crti = i;
|
|
std::fstream f{std::string(arg), f.binary | f.in};
|
|
f.exceptions(f.failbit);
|
|
elf_machine = get_elf_machine(f);
|
|
}
|
|
}
|
|
args.push_back(*argv);
|
|
}
|
|
|
|
if (!output) {
|
|
std::cerr << "Could not determine output file." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if (!crti) {
|
|
std::cerr << "Could not find CRT object on the command line." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
if (!real_linker || !real_linker->has_parent_path()) {
|
|
auto linker = next_program(this_program, real_linker);
|
|
if (!linker) {
|
|
std::cerr << "Could not find next "
|
|
<< (real_linker ? real_linker->filename()
|
|
: this_program.filename())
|
|
<< std::endl;
|
|
return 1;
|
|
}
|
|
real_linker = linker;
|
|
}
|
|
args.insert(args.begin(), real_linker->c_str());
|
|
args.push_back(nullptr);
|
|
|
|
std::string stem;
|
|
switch (elf_machine) {
|
|
case EM_NONE:
|
|
std::cerr << "Could not determine target machine type." << std::endl;
|
|
return 1;
|
|
case EM_386:
|
|
stem = "x86";
|
|
break;
|
|
case EM_X86_64:
|
|
stem = "x86_64";
|
|
break;
|
|
case EM_ARM:
|
|
stem = "arm";
|
|
break;
|
|
case EM_AARCH64:
|
|
stem = "aarch64";
|
|
break;
|
|
default:
|
|
std::cerr << "Unsupported target machine type." << std::endl;
|
|
return 1;
|
|
}
|
|
if (is_android) {
|
|
stem += "-android";
|
|
}
|
|
|
|
if (shared) {
|
|
std::vector<const char*> hacked_args(args);
|
|
auto inject = this_program.parent_path() / "inject" / (stem + ".o");
|
|
hacked_args.insert(hacked_args.begin() + crti + 1, inject.c_str());
|
|
hacked_args.insert(hacked_args.end() - 1, {"-z", "pack-relative-relocs",
|
|
"-init=_relrhack_wrap_init"});
|
|
int status = run_command(hacked_args, use_response_file);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
bool hacked = false;
|
|
try {
|
|
std::fstream f{*output, f.binary | f.in | f.out};
|
|
f.exceptions(f.failbit);
|
|
auto elf_class = get_elf_class(f);
|
|
f.seekg(0, std::ios::beg);
|
|
// On Android, we don't set the relrhack bit so that the system linker
|
|
// can handle the RELR relocations when supported. On desktop, the
|
|
// glibc unfortunately rejects the binaries without the bit set.
|
|
// (see comment about GLIBC_ABI_DT_RELR)
|
|
if (elf_class == ELFCLASS32) {
|
|
hacked = RelR<32>::hack(f, /* set_relrhack_bit = */ !is_android);
|
|
} else if (elf_class == ELFCLASS64) {
|
|
hacked = RelR<64>::hack(f, /* set_relrhack_bit = */ !is_android);
|
|
}
|
|
} catch (const std::runtime_error& err) {
|
|
std::cerr << "Failed to hack " << output->string() << ": " << err.what()
|
|
<< std::endl;
|
|
return 1;
|
|
}
|
|
if (hacked) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return run_command(args, use_response_file);
|
|
}
|