tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

relrhack.cpp (22930B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // This program acts as a linker wrapper. Its executable name is meant
      6 // to be that of a linker, and it will find the next linker with the same
      7 // name in $PATH. However, if for some reason the next linker cannot be
      8 // found this way, the caller may pass its path via the --real-linker
      9 // option.
     10 //
     11 // More in-depth background on https://glandium.org/blog/?p=4297
     12 
     13 #include "relrhack.h"
     14 #include <algorithm>
     15 #include <array>
     16 #include <cstdio>
     17 #include <cstring>
     18 #include <filesystem>
     19 #include <fstream>
     20 #include <iostream>
     21 #include <optional>
     22 #include <spawn.h>
     23 #include <sstream>
     24 #include <stdexcept>
     25 #include <sys/wait.h>
     26 #include <unistd.h>
     27 #include <unordered_map>
     28 #include <utility>
     29 #include <vector>
     30 
     31 #include "mozilla/ScopeExit.h"
     32 
     33 namespace fs = std::filesystem;
     34 
     35 class CantSwapSections : public std::runtime_error {
     36 public:
     37  CantSwapSections(const char* what) : std::runtime_error(what) {}
     38 };
     39 
     40 template <int bits>
     41 struct Elf {};
     42 
     43 #define ELF(bits)                        \
     44  template <>                            \
     45  struct Elf<bits> {                     \
     46    using Ehdr = Elf##bits##_Ehdr;       \
     47    using Phdr = Elf##bits##_Phdr;       \
     48    using Shdr = Elf##bits##_Shdr;       \
     49    using Dyn = Elf##bits##_Dyn;         \
     50    using Addr = Elf##bits##_Addr;       \
     51    using Word = Elf##bits##_Word;       \
     52    using Off = Elf##bits##_Off;         \
     53    using Verneed = Elf##bits##_Verneed; \
     54    using Vernaux = Elf##bits##_Vernaux; \
     55  }
     56 
     57 ELF(32);
     58 ELF(64);
     59 
     60 template <int bits>
     61 struct RelR : public Elf<bits> {
     62  using Elf_Ehdr = typename Elf<bits>::Ehdr;
     63  using Elf_Phdr = typename Elf<bits>::Phdr;
     64  using Elf_Shdr = typename Elf<bits>::Shdr;
     65  using Elf_Dyn = typename Elf<bits>::Dyn;
     66  using Elf_Addr = typename Elf<bits>::Addr;
     67  using Elf_Word = typename Elf<bits>::Word;
     68  using Elf_Off = typename Elf<bits>::Off;
     69  using Elf_Verneed = typename Elf<bits>::Verneed;
     70  using Elf_Vernaux = typename Elf<bits>::Vernaux;
     71 
     72 #define TAG_NAME(t) {t, #t}
     73  class DynInfo {
     74   public:
     75    using Tag = decltype(Elf_Dyn::d_tag);
     76    using Value = decltype(Elf_Dyn::d_un.d_val);
     77    bool is_wanted(Tag tag) const { return tag_names.count(tag); }
     78    void insert(off_t offset, Tag tag, Value val) {
     79      data[tag] = std::make_pair(offset, val);
     80    }
     81    off_t offset(Tag tag) const { return data.at(tag).first; }
     82    bool contains(Tag tag) const { return data.count(tag); }
     83    Value& operator[](Tag tag) {
     84      if (!is_wanted(tag)) {
     85        std::stringstream msg;
     86        msg << "Tag 0x" << std::hex << tag << " is not in DynInfo::tag_names";
     87        throw std::runtime_error(msg.str());
     88      }
     89      return data[tag].second;
     90    }
     91    const char* name(Tag tag) const { return tag_names.at(tag); }
     92 
     93   private:
     94    std::unordered_map<Tag, std::pair<off_t, Value>> data;
     95 
     96    const std::unordered_map<Tag, const char*> tag_names = {
     97        TAG_NAME(DT_JMPREL),  TAG_NAME(DT_PLTRELSZ), TAG_NAME(DT_RELR),
     98        TAG_NAME(DT_RELRENT), TAG_NAME(DT_RELRSZ),   TAG_NAME(DT_RELA),
     99        TAG_NAME(DT_RELASZ),  TAG_NAME(DT_RELAENT),  TAG_NAME(DT_REL),
    100        TAG_NAME(DT_RELSZ),   TAG_NAME(DT_RELENT),   TAG_NAME(DT_STRTAB),
    101        TAG_NAME(DT_STRSZ),   TAG_NAME(DT_VERNEED),  TAG_NAME(DT_VERNEEDNUM),
    102    };
    103  };
    104 
    105  // Translate a virtual address into an offset in the file based on the program
    106  // headers' PT_LOAD.
    107  static Elf_Addr get_offset(const std::vector<Elf_Phdr>& phdr, Elf_Addr addr) {
    108    for (const auto& p : phdr) {
    109      if (p.p_type == PT_LOAD && addr >= p.p_vaddr &&
    110          addr < p.p_vaddr + p.p_filesz) {
    111        return addr - (p.p_vaddr - p.p_paddr);
    112      }
    113    }
    114    return 0;
    115  }
    116 
    117  static bool hack(std::fstream& f, bool set_relrhack_bit);
    118 };
    119 
    120 template <typename T>
    121 T read_one_at(std::istream& in, off_t pos) {
    122  T result;
    123  in.seekg(pos, std::ios::beg);
    124  in.read(reinterpret_cast<char*>(&result), sizeof(T));
    125  return result;
    126 }
    127 
    128 template <typename T>
    129 std::vector<T> read_vector_at(std::istream& in, off_t pos, size_t num) {
    130  std::vector<T> result(num);
    131  in.seekg(pos, std::ios::beg);
    132  in.read(reinterpret_cast<char*>(result.data()), num * sizeof(T));
    133  return result;
    134 }
    135 
    136 void write_at(std::ostream& out, off_t pos, const char* buf, size_t len) {
    137  out.seekp(pos, std::ios::beg);
    138  out.write(buf, len);
    139 }
    140 
    141 template <typename T>
    142 void write_one_at(std::ostream& out, off_t pos, const T& data) {
    143  write_at(out, pos, reinterpret_cast<const char*>(&data), sizeof(T));
    144 }
    145 
    146 template <typename T>
    147 void write_vector_at(std::ostream& out, off_t pos, const std::vector<T>& vec) {
    148  write_at(out, pos, reinterpret_cast<const char*>(&vec.front()),
    149           vec.size() * sizeof(T));
    150 }
    151 
    152 template <int bits>
    153 bool RelR<bits>::hack(std::fstream& f, bool set_relrhack_bit) {
    154  auto ehdr = read_one_at<Elf_Ehdr>(f, 0);
    155  if (ehdr.e_phentsize != sizeof(Elf_Phdr)) {
    156    throw std::runtime_error("Invalid ELF?");
    157  }
    158  auto phdr = read_vector_at<Elf_Phdr>(f, ehdr.e_phoff, ehdr.e_phnum);
    159  const auto& dyn_phdr =
    160      std::find_if(phdr.begin(), phdr.end(),
    161                   [](const auto& p) { return p.p_type == PT_DYNAMIC; });
    162  if (dyn_phdr == phdr.end()) {
    163    return false;
    164  }
    165  if (dyn_phdr->p_filesz % sizeof(Elf_Dyn)) {
    166    throw std::runtime_error("Invalid ELF?");
    167  }
    168  auto dyn = read_vector_at<Elf_Dyn>(f, dyn_phdr->p_offset,
    169                                     dyn_phdr->p_filesz / sizeof(Elf_Dyn));
    170  off_t dyn_offset = dyn_phdr->p_offset;
    171  DynInfo dyn_info;
    172  for (const auto& d : dyn) {
    173    if (d.d_tag == DT_NULL) {
    174      break;
    175    }
    176 
    177    if (dyn_info.is_wanted(d.d_tag)) {
    178      if (dyn_info.contains(d.d_tag)) {
    179        std::stringstream msg;
    180        msg << dyn_info.name(d.d_tag) << " appears twice?";
    181        throw std::runtime_error(msg.str());
    182      }
    183      dyn_info.insert(dyn_offset, d.d_tag, d.d_un.d_val);
    184    }
    185    dyn_offset += sizeof(Elf_Dyn);
    186  }
    187 
    188  // Find the location and size of the SHT_RELR section, which contains the
    189  // packed-relative-relocs.
    190  Elf_Addr relr_off =
    191      dyn_info.contains(DT_RELR) ? get_offset(phdr, dyn_info[DT_RELR]) : 0;
    192  Elf_Off relrsz = dyn_info[DT_RELRSZ];
    193  const decltype(Elf_Dyn::d_tag) rel_tags[3][2] = {
    194      {DT_REL, DT_RELA}, {DT_RELSZ, DT_RELASZ}, {DT_RELENT, DT_RELAENT}};
    195  for (const auto& [rel_tag, rela_tag] : rel_tags) {
    196    if (dyn_info.contains(rel_tag) && dyn_info.contains(rela_tag)) {
    197      std::stringstream msg;
    198      msg << "Both " << dyn_info.name(rel_tag) << " and "
    199          << dyn_info.name(rela_tag) << " appear?";
    200      throw std::runtime_error(msg.str());
    201    }
    202  }
    203  Elf_Off relent =
    204      dyn_info.contains(DT_RELENT) ? dyn_info[DT_RELENT] : dyn_info[DT_RELAENT];
    205 
    206  // Estimate the size of the unpacked relative relocations corresponding
    207  // to the SHT_RELR section.
    208  auto relr = read_vector_at<Elf_Addr>(f, relr_off, relrsz / sizeof(Elf_Addr));
    209  size_t relocs = 0;
    210  for (const auto& entry : relr) {
    211    if ((entry & 1) == 0) {
    212      // LSB is 0, this is a pointer for a single relocation.
    213      relocs++;
    214    } else {
    215      // LSB is 1, remaining bits are a bitmap. Each bit represents a
    216      // relocation.
    217      relocs += __builtin_popcount(entry) - 1;
    218    }
    219  }
    220  // If the packed relocations + some overhead (we pick 4K arbitrarily, the
    221  // real size would require digging into the section sizes of the injected
    222  // .o file, which is not worth the error) is larger than the estimated
    223  // unpacked relocations, we'll just relink without packed relocations.
    224  if (relocs * relent < relrsz + 4096) {
    225    return false;
    226  }
    227 
    228  if (set_relrhack_bit) {
    229    // Change DT_RELR* tags to add DT_RELRHACK_BIT.
    230    for (const auto tag : {DT_RELR, DT_RELRSZ, DT_RELRENT}) {
    231      write_one_at(f, dyn_info.offset(tag), tag | DT_RELRHACK_BIT);
    232    }
    233  }
    234 
    235  bool is_glibc = false;
    236 
    237  if (dyn_info.contains(DT_VERNEEDNUM) && dyn_info.contains(DT_VERNEED) &&
    238      dyn_info.contains(DT_STRSZ) && dyn_info.contains(DT_STRTAB)) {
    239    // Scan SHT_VERNEED for the GLIBC_ABI_DT_RELR version on the libc
    240    // library.
    241    Elf_Addr verneed_off = get_offset(phdr, dyn_info[DT_VERNEED]);
    242    Elf_Off verneednum = dyn_info[DT_VERNEEDNUM];
    243    // SHT_STRTAB section, which contains the string table for, among other
    244    // things, the symbol versions in the SHT_VERNEED section.
    245    auto strtab = read_vector_at<char>(f, get_offset(phdr, dyn_info[DT_STRTAB]),
    246                                       dyn_info[DT_STRSZ]);
    247    // Guarantee a nul character at the end of the string table.
    248    strtab.push_back(0);
    249    while (verneednum--) {
    250      auto verneed = read_one_at<Elf_Verneed>(f, verneed_off);
    251      if (std::string_view{"libc.so.6"} == &strtab.at(verneed.vn_file)) {
    252        is_glibc = true;
    253        Elf_Addr vernaux_off = verneed_off + verneed.vn_aux;
    254        Elf_Addr relr = 0;
    255        Elf_Vernaux reuse;
    256        for (auto n = 0; n < verneed.vn_cnt; n++) {
    257          auto vernaux = read_one_at<Elf_Vernaux>(f, vernaux_off);
    258          if (std::string_view{"GLIBC_ABI_DT_RELR"} ==
    259              &strtab.at(vernaux.vna_name)) {
    260            relr = vernaux_off;
    261          } else {
    262            reuse = vernaux;
    263          }
    264          vernaux_off += vernaux.vna_next;
    265        }
    266        // In the case where we do have the GLIBC_ABI_DT_RELR version, we
    267        // need to edit the binary to make the following changes:
    268        // - Remove the GLIBC_ABI_DT_RELR version, we replace it with an
    269        // arbitrary other version entry, which is simpler than completely
    270        // removing it. We need to remove it because older versions of glibc
    271        // don't have the version (after all, that's why the symbol version
    272        // is there in the first place, to avoid running against older versions
    273        // of glibc that don't support packed relocations).
    274        // - Alter the DT_RELR* tags in the dynamic section, so that they
    275        // are not recognized by ld.so, because, while all versions of ld.so
    276        // ignore tags they don't know, glibc's ld.so versions that support
    277        // packed relocations don't want to load a binary that has DT_RELR*
    278        // tags but *not* a dependency on the GLIBC_ABI_DT_RELR version.
    279        if (relr) {
    280          // Don't overwrite vn_aux.
    281          write_at(f, relr, reinterpret_cast<char*>(&reuse),
    282                   sizeof(reuse) - sizeof(Elf_Word));
    283        }
    284      }
    285      verneed_off += verneed.vn_next;
    286    }
    287  }
    288 
    289  // Location of the .rel.plt section.
    290  Elf_Addr jmprel = dyn_info.contains(DT_JMPREL) ? dyn_info[DT_JMPREL] : 0;
    291  if (is_glibc) {
    292 #ifndef MOZ_STDCXX_COMPAT
    293    try {
    294 #endif
    295      // ld.so in glibc 2.16 to 2.23 expects .rel.plt to strictly follow
    296      // .rel.dyn. (https://sourceware.org/bugzilla/show_bug.cgi?id=14341)
    297      // BFD ld places .relr.dyn after .rel.plt, so this works fine, but lld
    298      // places it between both sections, which doesn't work out for us. In that
    299      // case, we want to swap .relr.dyn and .rel.plt.
    300      Elf_Addr rel_end = dyn_info.contains(DT_REL)
    301                             ? (dyn_info[DT_REL] + dyn_info[DT_RELSZ])
    302                             : (dyn_info[DT_RELA] + dyn_info[DT_RELASZ]);
    303      if (dyn_info.contains(DT_JMPREL) && dyn_info[DT_PLTRELSZ] &&
    304          dyn_info[DT_JMPREL] != rel_end) {
    305        if (dyn_info[DT_RELR] != rel_end) {
    306          throw CantSwapSections("RELR section doesn't follow REL/RELA?");
    307        }
    308        if (dyn_info[DT_JMPREL] != dyn_info[DT_RELR] + dyn_info[DT_RELRSZ]) {
    309          throw CantSwapSections("PLT REL/RELA doesn't follow RELR?");
    310        }
    311        auto plt_rel = read_vector_at<char>(
    312            f, get_offset(phdr, dyn_info[DT_JMPREL]), dyn_info[DT_PLTRELSZ]);
    313        // Write the content of both sections swapped, and adjust the
    314        // corresponding PT_DYNAMIC entries.
    315        write_vector_at(f, relr_off, plt_rel);
    316        write_vector_at(f, relr_off + plt_rel.size(), relr);
    317        dyn_info[DT_JMPREL] = rel_end;
    318        dyn_info[DT_RELR] = rel_end + plt_rel.size();
    319        for (const auto tag : {DT_JMPREL, DT_RELR}) {
    320          write_one_at(f, dyn_info.offset(tag) + sizeof(typename DynInfo::Tag),
    321                       dyn_info[tag]);
    322        }
    323      }
    324 #ifndef MOZ_STDCXX_COMPAT
    325    } catch (const CantSwapSections& err) {
    326      // When binary compatibility with older libstdc++/glibc is not enabled, we
    327      // only emit a warning about why swapping the sections is not happening.
    328      std::cerr << "WARNING: " << err.what() << std::endl;
    329    }
    330 #endif
    331  }
    332 
    333  off_t shdr_offset = ehdr.e_shoff;
    334  auto shdr = read_vector_at<Elf_Shdr>(f, ehdr.e_shoff, ehdr.e_shnum);
    335  for (auto& s : shdr) {
    336    // Some tools don't like sections of types they don't know, so change
    337    // SHT_RELR, which might be unknown on older systems, to SHT_PROGBITS.
    338    if (s.sh_type == SHT_RELR) {
    339      s.sh_type = SHT_PROGBITS;
    340      // If DT_RELR has been adjusted to swap with DT_JMPREL, also adjust
    341      // the corresponding SHT_RELR section header.
    342      if (s.sh_addr != dyn_info[DT_RELR]) {
    343        s.sh_offset += dyn_info[DT_RELR] - s.sh_addr;
    344        s.sh_addr = dyn_info[DT_RELR];
    345      }
    346      write_one_at(f, shdr_offset, s);
    347    } else if (jmprel && (s.sh_addr == jmprel) &&
    348               (s.sh_addr != dyn_info[DT_JMPREL])) {
    349      // If DT_JMPREL has been adjusted to swap with DT_RELR, also adjust
    350      // the corresponding section header.
    351      s.sh_offset -= s.sh_addr - dyn_info[DT_JMPREL];
    352      s.sh_addr = dyn_info[DT_JMPREL];
    353      write_one_at(f, shdr_offset, s);
    354    }
    355    shdr_offset += sizeof(Elf_Shdr);
    356  }
    357  return true;
    358 }
    359 
    360 std::vector<std::string> get_path() {
    361  std::vector<std::string> result;
    362  std::stringstream stream{std::getenv("PATH")};
    363  std::string item;
    364 
    365  while (std::getline(stream, item, ':')) {
    366    result.push_back(std::move(item));
    367  }
    368 
    369  return result;
    370 }
    371 
    372 std::optional<fs::path> next_program(fs::path& this_program,
    373                                     std::optional<fs::path>& program) {
    374  auto program_name = program ? *program : this_program.filename();
    375  for (const auto& dir : get_path()) {
    376    auto path = fs::path(dir) / program_name;
    377    auto status = fs::status(path);
    378    if ((status.type() == fs::file_type::regular) &&
    379        ((status.permissions() & fs::perms::owner_exec) ==
    380         fs::perms::owner_exec) &&
    381        !fs::equivalent(path, this_program))
    382      return path;
    383  }
    384  return std::nullopt;
    385 }
    386 
    387 unsigned char get_elf_class(unsigned char (&e_ident)[EI_NIDENT]) {
    388  if (std::string_view{reinterpret_cast<char*>(e_ident), SELFMAG} !=
    389      std::string_view{ELFMAG, SELFMAG}) {
    390    throw std::runtime_error("Not ELF?");
    391  }
    392 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    393  if (e_ident[EI_DATA] != ELFDATA2LSB) {
    394    throw std::runtime_error("Not Little Endian ELF?");
    395  }
    396 #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    397  if (e_ident[EI_DATA] != ELFDATA2MSB) {
    398    throw std::runtime_error("Not Big Endian ELF?");
    399  }
    400 #else
    401 #  error Unknown byte order.
    402 #endif
    403  if (e_ident[EI_VERSION] != 1) {
    404    throw std::runtime_error("Not ELF version 1?");
    405  }
    406  auto elf_class = e_ident[EI_CLASS];
    407  if (elf_class != ELFCLASS32 && elf_class != ELFCLASS64) {
    408    throw std::runtime_error("Not 32 or 64-bits ELF?");
    409  }
    410  return elf_class;
    411 }
    412 
    413 unsigned char get_elf_class(std::istream& in) {
    414  unsigned char e_ident[EI_NIDENT];
    415  in.read(reinterpret_cast<char*>(e_ident), sizeof(e_ident));
    416  return get_elf_class(e_ident);
    417 }
    418 
    419 uint16_t get_elf_machine(std::istream& in) {
    420  // As far as e_machine is concerned, both Elf32_Ehdr and Elf64_Ehdr are equal.
    421  Elf32_Ehdr ehdr;
    422  in.read(reinterpret_cast<char*>(&ehdr), sizeof(ehdr));
    423  // get_elf_class will throw exceptions for the cases we don't handle.
    424  get_elf_class(ehdr.e_ident);
    425  return ehdr.e_machine;
    426 }
    427 
    428 int run_command(std::vector<const char*>& args, bool use_response_file) {
    429  std::string at_file;
    430  const char** argv = args.data();
    431  std::array<const char*, 3> args_with_atfile{};
    432  if (use_response_file) {
    433    const char* tmpdir = getenv("TMPDIR");
    434    if (!tmpdir) {
    435      tmpdir = "/tmp";
    436    }
    437    std::string tmpfile = tmpdir;
    438    tmpfile += "/relrhackXXXXXX";
    439    int fd = mkstemp(tmpfile.data());
    440    if (fd < 0) {
    441      std::cerr << "Failed to create temporary file." << std::endl;
    442      return 1;
    443    }
    444    close(fd);
    445    std::ofstream f{tmpfile, f.binary};
    446    for (auto arg = std::next(args.begin()); arg != args.end(); ++arg) {
    447      f << *arg << "\n";
    448    }
    449    at_file = "@";
    450    at_file += tmpfile;
    451    args_with_atfile = {args.front(), at_file.c_str(), nullptr};
    452    argv = args_with_atfile.data();
    453  }
    454  auto guard = mozilla::MakeScopeExit([&] {
    455    if (!at_file.empty()) {
    456      unlink(at_file.c_str() + 1);
    457    }
    458  });
    459  pid_t child_pid;
    460  if (posix_spawn(&child_pid, args[0], nullptr, nullptr,
    461                  const_cast<char* const*>(argv), environ) != 0) {
    462    throw std::runtime_error("posix_spawn failed");
    463  }
    464 
    465  int status;
    466  waitpid(child_pid, &status, 0);
    467  return WEXITSTATUS(status);
    468 }
    469 
    470 int main(int argc, char* argv[]) {
    471  auto this_program = fs::absolute(argv[0]);
    472 
    473  std::vector<const char*> args;
    474 
    475  int i, crti = 0;
    476  std::optional<fs::path> output = std::nullopt;
    477  std::optional<fs::path> real_linker = std::nullopt;
    478  bool shared = false;
    479  bool is_android = false;
    480  bool use_response_file = false;
    481  std::vector<char> response_file;
    482  std::vector<const char*> response_file_args;
    483  uint16_t elf_machine = EM_NONE;
    484  // Scan argv in order to prepare the following:
    485  // - get the output file. That's the file we may need to adjust.
    486  // - get the --real-linker if one was passed.
    487  // - detect whether we're linking a shared library or something else. As of
    488  // now, only shared libraries are handled. Technically speaking, programs
    489  // could be handled as well, but for the purpose of Firefox, that actually
    490  // doesn't work because programs contain a memory allocator that ends up
    491  // being called before the injected code has any chance to apply relocations,
    492  // and the allocator itself needs the relocations to have been applied.
    493  // - detect the position of crti.o so that we can inject our own object
    494  // right after it, and also to detect the machine type to pick the right
    495  // object to inject.
    496  //
    497  // At the same time, we also construct a new list of arguments, with
    498  // --real-linker filtered out. We'll later inject arguments in that list.
    499  if (argc == 2 && argv[1] && argv[1][0] == '@') {
    500    // When GCC is given a response file, it wraps all arguments into a
    501    // new response file with all arguments, even if originally there were
    502    // arguments and a response file.
    503    // In that case, we can't scan for arguments, so we need to read the
    504    // response file. And as we change the arguments, we'll need to write
    505    // a new one.
    506    std::ifstream f{argv[1] + 1, f.binary | f.ate};
    507    if (!f) {
    508      std::cerr << "Failed to read " << argv[1] + 1 << std::endl;
    509      return 1;
    510    }
    511    size_t len = f.tellg();
    512    response_file = read_vector_at<char>(f, 0, len);
    513    std::replace(response_file.begin(), response_file.end(), '\n', '\0');
    514    if (len && response_file[len - 1] != '\0') {
    515      response_file.push_back('\0');
    516    }
    517    response_file_args.push_back(argv[0]);
    518    for (const char* a = response_file.data();
    519         a < response_file.data() + response_file.size(); a += strlen(a) + 1) {
    520      response_file_args.push_back(a);
    521    }
    522    argv = const_cast<char**>(response_file_args.data());
    523    argc = response_file_args.size();
    524    use_response_file = true;
    525  }
    526  for (i = 1, argv++; i < argc && *argv; argv++, i++) {
    527    std::string_view arg{*argv};
    528    if (arg == "-shared") {
    529      shared = true;
    530    } else if (arg == "-o") {
    531      args.push_back(*(argv++));
    532      ++i;
    533      output = *argv;
    534    } else if (arg == "--real-linker") {
    535      ++i;
    536      real_linker = *(++argv);
    537      continue;
    538    } else if (elf_machine == EM_NONE) {
    539      auto filename = fs::path(arg).filename();
    540      if (filename == "crti.o" || filename == "crtbegin_so.o") {
    541        is_android = (filename == "crtbegin_so.o");
    542        crti = i;
    543        std::fstream f{std::string(arg), f.binary | f.in};
    544        f.exceptions(f.failbit);
    545        elf_machine = get_elf_machine(f);
    546      }
    547    }
    548    args.push_back(*argv);
    549  }
    550 
    551  if (!output) {
    552    std::cerr << "Could not determine output file." << std::endl;
    553    return 1;
    554  }
    555 
    556  if (!crti) {
    557    std::cerr << "Could not find CRT object on the command line." << std::endl;
    558    return 1;
    559  }
    560 
    561  if (!real_linker || !real_linker->has_parent_path()) {
    562    auto linker = next_program(this_program, real_linker);
    563    if (!linker) {
    564      std::cerr << "Could not find next "
    565                << (real_linker ? real_linker->filename()
    566                                : this_program.filename())
    567                << std::endl;
    568      return 1;
    569    }
    570    real_linker = linker;
    571  }
    572  args.insert(args.begin(), real_linker->c_str());
    573  args.push_back(nullptr);
    574 
    575  std::string stem;
    576  switch (elf_machine) {
    577    case EM_NONE:
    578      std::cerr << "Could not determine target machine type." << std::endl;
    579      return 1;
    580    case EM_386:
    581      stem = "x86";
    582      break;
    583    case EM_X86_64:
    584      stem = "x86_64";
    585      break;
    586    case EM_ARM:
    587      stem = "arm";
    588      break;
    589    case EM_AARCH64:
    590      stem = "aarch64";
    591      break;
    592    default:
    593      std::cerr << "Unsupported target machine type." << std::endl;
    594      return 1;
    595  }
    596  if (is_android) {
    597    stem += "-android";
    598  }
    599 
    600  if (shared) {
    601    std::vector<const char*> hacked_args(args);
    602    auto inject = this_program.parent_path() / "inject" / (stem + ".o");
    603    hacked_args.insert(hacked_args.begin() + crti + 1, inject.c_str());
    604    hacked_args.insert(hacked_args.end() - 1, {"-z", "pack-relative-relocs",
    605                                               "-init=_relrhack_wrap_init"});
    606    int status = run_command(hacked_args, use_response_file);
    607    if (status) {
    608      return status;
    609    }
    610    bool hacked = false;
    611    try {
    612      std::fstream f{*output, f.binary | f.in | f.out};
    613      f.exceptions(f.failbit);
    614      auto elf_class = get_elf_class(f);
    615      f.seekg(0, std::ios::beg);
    616      // On Android, we don't set the relrhack bit so that the system linker
    617      // can handle the RELR relocations when supported. On desktop, the
    618      // glibc unfortunately rejects the binaries without the bit set.
    619      // (see comment about GLIBC_ABI_DT_RELR)
    620      if (elf_class == ELFCLASS32) {
    621        hacked = RelR<32>::hack(f, /* set_relrhack_bit = */ !is_android);
    622      } else if (elf_class == ELFCLASS64) {
    623        hacked = RelR<64>::hack(f, /* set_relrhack_bit = */ !is_android);
    624      }
    625    } catch (const std::runtime_error& err) {
    626      std::cerr << "Failed to hack " << output->string() << ": " << err.what()
    627                << std::endl;
    628      return 1;
    629    }
    630    if (hacked) {
    631      return 0;
    632    }
    633  }
    634 
    635  return run_command(args, use_response_file);
    636 }