reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    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
//===- lib/ReaderWriter/MachO/ShimPass.cpp -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This linker pass updates branch-sites whose target is a different mode
// (thumb vs arm).
//
// Arm code has two instruction encodings thumb and arm.  When branching from
// one code encoding to another, you need to use an instruction that switches
// the instruction mode.  Usually the transition only happens at call sites, and
// the linker can transform a BL instruction in BLX (or vice versa).  But if the
// compiler did a tail call optimization and a function ends with a branch (not
// branch and link), there is no pc-rel BX instruction.
//
// The ShimPass looks for pc-rel B instructions that will need to switch mode.
// For those cases it synthesizes a shim which does the transition, then
// modifies the original atom with the B instruction to target to the shim atom.
//
//===----------------------------------------------------------------------===//

#include "ArchHandler.h"
#include "File.h"
#include "MachOPasses.h"
#include "lld/Common/LLVM.h"
#include "lld/Core/DefinedAtom.h"
#include "lld/Core/File.h"
#include "lld/Core/Reference.h"
#include "lld/Core/Simple.h"
#include "lld/ReaderWriter/MachOLinkingContext.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"

namespace lld {
namespace mach_o {

class ShimPass : public Pass {
public:
  ShimPass(const MachOLinkingContext &context)
      : _ctx(context), _archHandler(_ctx.archHandler()),
        _stubInfo(_archHandler.stubInfo()),
        _file(*_ctx.make_file<MachOFile>("<mach-o shim pass>")) {
    _file.setOrdinal(_ctx.getNextOrdinalAndIncrement());
  }

  llvm::Error perform(SimpleFile &mergedFile) override {
    // Scan all references in all atoms.
    for (const DefinedAtom *atom : mergedFile.defined()) {
      for (const Reference *ref : *atom) {
        // Look at non-call branches.
        if (!_archHandler.isNonCallBranch(*ref))
          continue;
        const Atom *target = ref->target();
        assert(target != nullptr);
        if (const lld::DefinedAtom *daTarget = dyn_cast<DefinedAtom>(target)) {
          bool atomIsThumb = _archHandler.isThumbFunction(*atom);
          bool targetIsThumb = _archHandler.isThumbFunction(*daTarget);
          if (atomIsThumb != targetIsThumb)
            updateBranchToUseShim(atomIsThumb, *daTarget, ref);
        }
      }
    }
    // Exit early if no shims needed.
    if (_targetToShim.empty())
      return llvm::Error::success();

    // Sort shim atoms so the layout order is stable.
    std::vector<const DefinedAtom *> shims;
    shims.reserve(_targetToShim.size());
    for (auto element : _targetToShim) {
      shims.push_back(element.second);
    }
    std::sort(shims.begin(), shims.end(),
              [](const DefinedAtom *l, const DefinedAtom *r) {
                return (l->name() < r->name());
              });

    // Add all shims to master file.
    for (const DefinedAtom *shim : shims)
      mergedFile.addAtom(*shim);

    return llvm::Error::success();
  }

private:

  void updateBranchToUseShim(bool thumbToArm, const DefinedAtom& target,
                             const Reference *ref) {
    // Make file-format specific stub and other support atoms.
    const DefinedAtom *shim = this->getShim(thumbToArm, target);
    assert(shim != nullptr);
    // Switch branch site to target shim atom.
    const_cast<Reference *>(ref)->setTarget(shim);
  }

  const DefinedAtom* getShim(bool thumbToArm, const DefinedAtom& target) {
    auto pos = _targetToShim.find(&target);
    if ( pos != _targetToShim.end() ) {
      // Reuse an existing shim.
      assert(pos->second != nullptr);
      return pos->second;
    } else {
      // There is no existing shim, so create a new one.
      const DefinedAtom *shim = _archHandler.createShim(_file, thumbToArm,
                                                        target);
       _targetToShim[&target] = shim;
       return shim;
    }
  }

  const MachOLinkingContext &_ctx;
  mach_o::ArchHandler                            &_archHandler;
  const ArchHandler::StubInfo                    &_stubInfo;
  MachOFile                                      &_file;
  llvm::DenseMap<const Atom*, const DefinedAtom*> _targetToShim;
};



void addShimPass(PassManager &pm, const MachOLinkingContext &ctx) {
  pm.add(std::make_unique<ShimPass>(ctx));
}

} // end namespace mach_o
} // end namespace lld