current commit
260929.11 · deps: update lief to 1.0.0
/* Copyright 2017 - 2026 R. Thomas
 * Copyright 2017 - 2026 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <sstream>

#include <spdlog/fmt/fmt.h>

#include "logging.hpp"
#include "frozen.hpp"
#include "LIEF/iostream.hpp"

#include "LIEF/Abstract/Binary.hpp"

#include "LIEF/BinaryStream/SpanStream.hpp"

#include "LIEF/Visitor.hpp"

#include "LIEF/MachO/Binary.hpp"
#include "LIEF/MachO/DyldInfo.hpp"
#include "LIEF/MachO/DyldBindingInfo.hpp"
#include "LIEF/MachO/ExportInfo.hpp"
#include "LIEF/MachO/RelocationDyld.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/Symbol.hpp"
#include "LIEF/MachO/DylibCommand.hpp"

#include "MachO/exports_trie.hpp"
#include "MachO/Structures.hpp"
#include "MachO/dyld_opcodes.hpp"

#include "Object.tcc"


namespace LIEF::MachO {

struct ThreadedBindData {
  std::string symbol_name;
  int64_t addend = 0;
  int64_t library_ordinal = 0;
  uint8_t symbol_flags = 0;
  uint8_t type = 0;
};

DyldInfo::DyldInfo() = default;
DyldInfo::~DyldInfo() = default;

DyldInfo& DyldInfo::operator=(DyldInfo other) {
  swap(other);
  return *this;
}

DyldInfo::DyldInfo(const DyldInfo& copy) :
  LoadCommand::LoadCommand{copy},
  rebase_{copy.rebase_},
  rebase_opcodes_{copy.rebase_opcodes_},
  bind_{copy.bind_},
  bind_opcodes_{copy.bind_opcodes_},
  weak_bind_{copy.weak_bind_},
  weak_bind_opcodes_{copy.weak_bind_opcodes_},
  lazy_bind_{copy.lazy_bind_},
  lazy_bind_opcodes_{copy.lazy_bind_opcodes_},
  export_{copy.export_},
  export_trie_{copy.export_trie_} {}

DyldInfo::DyldInfo(const details::dyld_info_command& dyld_info_cmd) :
  LoadCommand::LoadCommand{LoadCommand::TYPE(dyld_info_cmd.cmd),
                           dyld_info_cmd.cmdsize},
  rebase_{dyld_info_cmd.rebase_off, dyld_info_cmd.rebase_size},
  bind_{dyld_info_cmd.bind_off, dyld_info_cmd.bind_size},
  weak_bind_{dyld_info_cmd.weak_bind_off, dyld_info_cmd.weak_bind_size},
  lazy_bind_{dyld_info_cmd.lazy_bind_off, dyld_info_cmd.lazy_bind_size},
  export_{dyld_info_cmd.export_off, dyld_info_cmd.export_size} {}


void DyldInfo::swap(DyldInfo& other) noexcept {
  LoadCommand::swap(other);

  std::swap(rebase_, other.rebase_);
  std::swap(rebase_opcodes_, other.rebase_opcodes_);

  std::swap(bind_, other.bind_);
  std::swap(bind_opcodes_, other.bind_opcodes_);

  std::swap(weak_bind_, other.weak_bind_);
  std::swap(weak_bind_opcodes_, other.weak_bind_opcodes_);

  std::swap(lazy_bind_, other.lazy_bind_);
  std::swap(lazy_bind_opcodes_, other.lazy_bind_opcodes_);

  std::swap(export_, other.export_);
  std::swap(export_trie_, other.export_trie_);

  std::swap(export_info_, other.export_info_);
  std::swap(binding_info_, other.binding_info_);

  std::swap(binary_, other.binary_);
}

void DyldInfo::rebase_opcodes(buffer_t raw) {
  if (raw.size() > rebase_opcodes_.size()) {
    LIEF_WARN("Rebase opcodes update failed: data exceeds original size");
    return;
  }
  std::move(raw.begin(), raw.end(), rebase_opcodes_.data());
}

inline std::string safe_segment_name(Binary::it_segments& segments, size_t idx) {
  return idx < segments.size() ? segments[idx].name() : "<invalid>";
}

inline std::string safe_library_name(Binary::it_libraries& libraries,
                                     int64_t idx) {
  if (idx >= 0 && static_cast<size_t>(idx) < libraries.size()) {
    return libraries[idx].name();
  }
  return "<invalid>";
}

std::string DyldInfo::show_rebases_opcodes() const {
  static constexpr char tab[] = "    ";

  if (binary_ == nullptr) {
    LIEF_WARN("Failed to print rebase opcode");
    return "";
  }

  size_t pint_v = static_cast<LIEF::Binary*>(binary_)->header().is_64() ?
                      sizeof(uint64_t) :
                      sizeof(uint32_t);
  std::ostringstream output;

  bool done = false;
  uint8_t type = 0;
  uint32_t segment_index = 0;
  uint64_t segment_offset = 0;
  uint32_t count = 0;
  uint32_t skip = 0;
  SpanStream rebase_stream = rebase_opcodes();

  Binary::it_segments segments = binary_->segments();

  while (!done && rebase_stream.pos() < rebase_stream.size()) {
    auto val = rebase_stream.read<uint8_t>();
    if (!val) {
      break;
    }
    uint8_t imm = *val & IMMEDIATE_MASK;
    auto opcode = REBASE_OPCODES(*val & OPCODE_MASK);

    switch (opcode) {
      case REBASE_OPCODES::DONE:
      {
        output << "[" << to_string(opcode) << "]" << '\n';
        done = true;
        break;
      }

      case REBASE_OPCODES::SET_TYPE_IMM:
      {
        type = imm;
        output << "[" << to_string(opcode) << "] ";
        output << "Type: " << to_string(REBASE_TYPE(type));
        output << '\n';
        break;
      }

      case REBASE_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {
        if (auto val = rebase_stream.read_uleb128()) {
          segment_offset = *val;
        } else {
          LIEF_ERR("Failed to read REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB "
                   "segment offset");
          break;
        }

        segment_index = imm;

        output << "[" << to_string(opcode) << "] ";
        output << fmt::format("Segment Index := {} ({}) ", segment_index,
                              safe_segment_name(segments, segment_index));
        output << fmt::format("Segment Offset := {:#x}\n", segment_offset);

        break;
      }

      case REBASE_OPCODES::ADD_ADDR_ULEB:
      {
        uint64_t val = 0;
        if (auto res = rebase_stream.read_uleb128()) {
          val = *res;
        } else {
          LIEF_ERR("Failed to read REBASE_OPCODE_ADD_ADDR_ULEB segment offset");
          break;
        }

        segment_offset += val;

        output << "[" << to_string(opcode) << "] ";
        output << fmt::format("Segment Offset += {:#x} ({:#x})\n", val,
                              segment_offset);
        break;
      }

      case REBASE_OPCODES::ADD_ADDR_IMM_SCALED:
      {
        segment_offset += (imm * pint_v);

        output << "[" << to_string(opcode) << "]";
        output << fmt::format("Segment Offset += {:#x} ({:#x})\n", imm * pint_v,
                              segment_offset);
        break;
      }

      case REBASE_OPCODES::DO_REBASE_IMM_TIMES:
      {
        output << "[" << to_string(opcode) << "]" << '\n';
        output << fmt::format("{}for i in range({}):\n", tab,
                              static_cast<uint32_t>(imm));
        for (size_t i = 0; i < imm; ++i) {
          output << tab << tab;
          output << fmt::format("rebase({}, {}, {:#x})\n",
                                to_string(REBASE_TYPE(type)),
                                safe_segment_name(segments, segment_index),
                                segment_offset);

          segment_offset += pint_v;

          output << tab << tab;
          output << fmt::format("Segment Offset += {:#x} ({:#x})\n\n", pint_v,
                                segment_offset);
        }
        output << '\n';
        break;
      }
      case REBASE_OPCODES::DO_REBASE_ULEB_TIMES:
      {

        if (auto res = rebase_stream.read_uleb128()) {
          count = *res;
        } else {
          LIEF_ERR("Failed to read REBASE_OPCODE_DO_REBASE_ULEB_TIMES count");
          break;
        }

        count = safe_dyld_opcode_count<uint32_t>(segment_index < segments.size() ?
                                                     &segments[segment_index] :
                                                     nullptr,
                                                 segment_offset, pint_v, count);

        output << "[" << to_string(opcode) << "]" << '\n';

        output << fmt::format("{}for i in range({}):\n", tab,
                              static_cast<uint32_t>(count));
        for (size_t i = 0; i < count; ++i) {
          output << tab << tab;
          output << fmt::format("rebase({}, {}, {:#x})\n",
                                to_string(REBASE_TYPE(type)),
                                safe_segment_name(segments, segment_index),
                                segment_offset);

          segment_offset += pint_v;

          output << tab << tab;
          output << fmt::format("Segment Offset += {:#x} ({:#x})\n\n", pint_v,
                                segment_offset);
        }

        output << '\n';
        break;
      }

      case REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB:
      {

        output << "[" << to_string(opcode) << "]" << '\n';

        output << tab;
        output << fmt::format("rebase({}, {}, {:#x})\n",
                              to_string(REBASE_TYPE(type)),
                              safe_segment_name(segments, segment_index),
                              segment_offset);

        uint64_t val = 0;
        if (auto res = rebase_stream.read_uleb128()) {
          val = *res;
        } else {
          LIEF_ERR("Failed to read REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB value");
          break;
        }
        segment_offset += val + pint_v;


        output << tab;
        output << fmt::format("Segment Offset += {:#x} ({:#x})\n", val + pint_v,
                              segment_offset);

        break;
      }

      case REBASE_OPCODES::DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
      {

        output << "[" << to_string(static_cast<REBASE_OPCODES>(opcode)) << "]"
               << '\n';

        // Count
        if (auto res = rebase_stream.read_uleb128()) {
          count = *res;
        } else {
          LIEF_ERR("Failed to read "
                   "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB count");
          break;
        }

        // Skip
        if (auto res = rebase_stream.read_uleb128()) {
          skip = *res;
        } else {
          LIEF_ERR("Failed to read "
                   "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB skip");
          break;
        }

        count = safe_dyld_opcode_count<uint32_t>(segment_index < segments.size() ?
                                                     &segments[segment_index] :
                                                     nullptr,
                                                 segment_offset, pint_v, count);

        output << fmt::format("{}for i in range({}):\n", tab,
                              static_cast<uint32_t>(count));
        for (size_t i = 0; i < count; ++i) {
          output << tab << tab;
          output << fmt::format("rebase({}, {}, {:#x})\n",
                                to_string(REBASE_TYPE(type)),
                                safe_segment_name(segments, segment_index),
                                segment_offset);

          segment_offset += skip + pint_v;

          output << tab << tab;
          output << fmt::format("Segment Offset += {:#x} + {:#x} ({:#x})\n\n",
                                skip, pint_v, segment_offset);
        }

        break;
      }

      default:
      {
        output << fmt::format("[UNSUPPORTED OPCODE - {:#x}]\n",
                              static_cast<uint32_t>(opcode));
        break;
      }
    }
  }

  return output.str();
}

void DyldInfo::bind_opcodes(buffer_t raw) {
  if (raw.size() > bind_opcodes_.size()) {
    LIEF_WARN("Bind opcodes update failed: data exceeds original size");
    return;
  }
  std::move(raw.begin(), raw.end(), bind_opcodes_.data());
}


std::string DyldInfo::show_bind_opcodes() const {
  std::ostringstream output;
  show_bindings(output, bind_opcodes(), /* is_lazy = */ false);
  return output.str();
}

void DyldInfo::show_bindings(std::ostream& output,
                             span<const uint8_t> bind_opcodes,
                             bool is_lazy) const {

  if (binary_ == nullptr) {
    LIEF_WARN("Failed to print bind opcodes");
    return;
  }

  size_t pint_v = static_cast<LIEF::Binary*>(binary_)->header().is_64() ?
                      sizeof(uint64_t) :
                      sizeof(uint32_t);

  uint8_t type =
      is_lazy ? static_cast<uint8_t>(DyldBindingInfo::TYPE::POINTER) : 0;
  uint8_t segment_idx = 0;
  uint64_t segment_offset = 0;
  std::string symbol_name;
  int64_t library_ordinal = 0;

  int64_t addend = 0;
  uint32_t count = 0;
  uint32_t skip = 0;

  bool is_weak_import = false;
  bool done = false;

  size_t ordinal_table_size = 0;
  bool use_threaded_rebase_bind = false;
  uint8_t symbol_flags = 0;
  std::vector<ThreadedBindData> ordinal_table;

  Binary::it_segments segments = binary_->segments();
  Binary::it_libraries libraries = binary_->libraries();

  const std::string tab = "    ";

  SpanStream bind_stream = bind_opcodes;

  while (!done && bind_stream.pos() < bind_stream.size()) {
    auto val = bind_stream.read<uint8_t>();
    if (!val) {
      break;
    }
    uint8_t imm = *val & IMMEDIATE_MASK;
    auto opcode = BIND_OPCODES(*val & OPCODE_MASK);

    switch (opcode) {
      case BIND_OPCODES::DONE:
      {
        output << "[" << to_string(opcode) << "]" << '\n';
        if (!is_lazy) {
          done = true;
        }
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_IMM:
      {
        output << "[" << to_string(opcode) << "]" << '\n';

        library_ordinal = imm;

        output << tab
               << fmt::format("Library Ordinal := {}\n",
                              static_cast<uint32_t>(imm));
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB:
      {
        output << "[" << to_string(opcode) << "]" << '\n';

        if (auto res = bind_stream.read_uleb128()) {
          library_ordinal = *res;
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB library "
                   "ordinal value");
          break;
        }

        output << tab << fmt::format("Library Ordinal := {}\n", library_ordinal);

        break;
      }

      case BIND_OPCODES::SET_DYLIB_SPECIAL_IMM:
      {

        output << "[" << to_string(opcode) << "]" << '\n';
        // the special ordinals are negative numbers
        if (imm == 0) {
          library_ordinal = 0;
        } else {
          int8_t sign_extended = static_cast<int8_t>(OPCODE_MASK) | imm;
          library_ordinal = sign_extended;
        }

        output << tab << fmt::format("Library Ordinal := {}\n", library_ordinal);
        break;
      }

      case BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM:
      {

        output << "[" << to_string(opcode) << "]" << '\n';

        if (auto res = bind_stream.read_string()) {
          symbol_name = std::move(*res);
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM "
                   "symbol name");
          break;
        }
        symbol_flags = imm;

        is_weak_import = (imm & BIND_SYMBOL_FLAGS::WEAK_IMPORT) != 0;

        output << tab << "Symbol name := " << symbol_name << '\n';
        output << tab << fmt::format("Is Weak ? {}\n", is_weak_import);
        break;
      }

      case BIND_OPCODES::SET_TYPE_IMM:
      {
        output << "[" << to_string(opcode) << "]" << '\n';

        type = imm;

        output << tab << "Type := " << to_string(DyldBindingInfo::TYPE(type))
               << '\n';

        break;
      }

      case BIND_OPCODES::SET_ADDEND_SLEB:
      {
        output << "[" << to_string(opcode) << "]" << '\n';

        if (auto res = bind_stream.read_sleb128()) {
          addend = *res;
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_SET_ADDEND_SLEB addend");
          break;
        }

        output << tab << fmt::format("Addend := {}\n", addend);
        break;
      }

      case BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {

        output << "[" << to_string(opcode) << "]" << '\n';
        segment_idx = imm;

        if (auto res = bind_stream.read_uleb128()) {
          segment_offset = *res;
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB "
                   "segment offset");
          break;
        }

        output << tab << "Segment := " << safe_segment_name(segments, segment_idx)
               << '\n';
        output << tab << fmt::format("Segment Offset := {:#x}\n", segment_offset);

        break;
      }

      case BIND_OPCODES::ADD_ADDR_ULEB:
      {

        output << "[" << to_string(opcode) << "]" << '\n';

        uint64_t val = 0;
        if (auto res = bind_stream.read_uleb128()) {
          val = *res;
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_ADD_ADDR_ULEB value");
          break;
        }
        segment_offset += val;

        output << tab
               << fmt::format("Segment Offset += {:#x} ({:#x})\n", val,
                              segment_offset);
        break;
      }

      case BIND_OPCODES::DO_BIND:
      {
        if (!use_threaded_rebase_bind) {
          output << "[" << to_string(opcode) << "]" << '\n';

          output << tab
                 << fmt::format("bind({}, {}, {:#x}, {}, library_ordinal={}, "
                                "addend={}, is_weak_import={})\n",
                                to_string(DyldBindingInfo::TYPE(type)),
                                safe_segment_name(segments, segment_idx),
                                segment_offset, symbol_name,
                                library_ordinal > 0 ?
                                    safe_library_name(libraries,
                                                      library_ordinal - 1) :
                                    std::to_string(library_ordinal),
                                addend, is_weak_import);

          segment_offset += pint_v;

          output << tab
                 << fmt::format("Segment Offset += {:#x} ({:#x})\n", pint_v,
                                segment_offset);
        } else {
          ordinal_table.push_back(ThreadedBindData{symbol_name, addend,
                                                   library_ordinal, symbol_flags,
                                                   type});
        }
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB:
      {

        output << "[" << to_string(opcode) << "]" << '\n';

        output << tab
               << fmt::format("bind({}, {}, {:#x}, {}, library_ordinal={}, "
                              "addend={}, is_weak_import={})\n",
                              to_string(DyldBindingInfo::TYPE(type)),
                              safe_segment_name(segments, segment_idx),
                              segment_offset, symbol_name,
                              library_ordinal > 0 ?
                                  safe_library_name(libraries,
                                                    library_ordinal - 1) :
                                  std::to_string(library_ordinal),
                              addend, is_weak_import);

        uint64_t v = 0;
        if (auto res = bind_stream.read_uleb128()) {
          v = *res;
        } else {
          LIEF_ERR("Failed to read BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB value");
          break;
        }
        segment_offset += v + pint_v;

        output << tab
               << fmt::format("Segment Offset += {:#x} ({:#x})\n", v + pint_v,
                              segment_offset);
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED:
      {

        output << "[" << to_string(opcode) << "]" << '\n';

        output << tab
               << fmt::format("bind({}, {}, {:#x}, {}, library_ordinal={}, "
                              "addend={}, is_weak_import={})\n",
                              to_string(DyldBindingInfo::TYPE(type)),
                              safe_segment_name(segments, segment_idx),
                              segment_offset, symbol_name,
                              library_ordinal > 0 ?
                                  safe_library_name(libraries,
                                                    library_ordinal - 1) :
                                  std::to_string(library_ordinal),
                              addend, is_weak_import);

        segment_offset += imm * pint_v + pint_v;

        output << tab
               << fmt::format("Segment Offset += {:#x} ({:#x})\n",
                              imm * pint_v + pint_v, segment_offset);

        break;
      }

      case BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
      {

        output << "[" << to_string(opcode) << "]" << '\n';
        // Count
        if (auto res = bind_stream.read_uleb128()) {
          count = *res;
        } else {
          LIEF_ERR(
              "Failed to read BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB count"
          );
          break;
        }

        // Skip
        if (auto res = bind_stream.read_uleb128()) {
          skip = *res;
        } else {
          LIEF_ERR(
              "Failed to read BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB skip"
          );
          break;
        }

        count = safe_dyld_opcode_count<uint32_t>(segment_idx < segments.size() ?
                                                     &segments[segment_idx] :
                                                     nullptr,
                                                 segment_offset, pint_v, count);

        output << tab
               << fmt::format("for i in range({}):\n",
                              static_cast<uint32_t>(count));
        for (size_t i = 0; i < count; ++i) {
          output << tab << tab
                 << fmt::format("bind({}, {}, {:#x}, {}, library_ordinal={}, "
                                "addend={}, is_weak_import={})\n",
                                to_string(DyldBindingInfo::TYPE(type)),
                                safe_segment_name(segments, segment_idx),
                                segment_offset, symbol_name,
                                library_ordinal > 0 ?
                                    safe_library_name(libraries,
                                                      library_ordinal - 1) :
                                    std::to_string(library_ordinal),
                                addend, is_weak_import);


          segment_offset += skip + pint_v;

          output << fmt::format("Segment Offset += {:#x} + {:#x} ({:#x})\n\n",
                                skip, pint_v, segment_offset);
        }
        break;
      }
      case BIND_OPCODES::THREADED:
      {
        const auto subopcode = static_cast<BIND_SUBOPCODE_THREADED>(imm);
        output << fmt::format("[{}]\n", to_string(BIND_OPCODES::THREADED));
        switch (subopcode) {
          case BIND_SUBOPCODE_THREADED::APPLY:
          {
            output << tab << fmt::format("[{}]\n", to_string(subopcode));
            uint64_t delta = 0;
            if (segment_idx >= segments.size()) {
              LIEF_WARN("Invalid segment index {} in THREADED bind", segment_idx);
              break;
            }
            const SegmentCommand& current_segment = segments[segment_idx];
            do {
              const uint64_t address =
                  current_segment.virtual_address() + segment_offset;
              span<const uint8_t> content = current_segment.content();
              if (segment_offset >= content.size() ||
                  (segment_offset + sizeof(uint64_t)) >= content.size())
              {
                LIEF_WARN("Invalid segment offset: {:#x}", segment_offset);
                break;
              }
              auto value = *reinterpret_cast<const uint64_t*>(content.data() +
                                                              segment_offset);
              bool is_rebase = (value & (static_cast<uint64_t>(1) << 62)) == 0;

              if (is_rebase) {
                output << tab << tab
                       << fmt::format("rebase({}, {}, {:#x})\n", "THREADED_REBASE",
                                      current_segment.name(), segment_offset);
              } else {
                uint16_t ordinal = value & 0xFFFF;
                if (ordinal >= ordinal_table_size ||
                    ordinal >= ordinal_table.size())
                {
                  LIEF_WARN(
                      "Bind ordinal {:d} out of range (max {:d}) at {:#06x} in "
                      "segment '{}' (offset: {:#06x})",
                      ordinal, ordinal_table_size, value, current_segment.name(),
                      segment_offset
                  );
                  break;
                }
                if (address < current_segment.virtual_address() ||
                    address >= (current_segment.virtual_address() +
                                current_segment.virtual_size()))
                {
                  LIEF_WARN("Invalid binding address");
                  break;
                }
                const ThreadedBindData& th_bind_data = ordinal_table[ordinal];
                const int64_t library_ordinal = th_bind_data.library_ordinal;
                output << tab << tab
                       << fmt::format(
                              "threaded_bind({}/{}, {:#x}, {}, {}, "
                              "library_ordinal={}, "
                              "addend={}, is_weak_import={})\n",
                              "THREADED_BIND",
                              to_string(DyldBindingInfo::TYPE(th_bind_data.type)),
                              segment_offset, current_segment.name(),
                              th_bind_data.symbol_name,
                              library_ordinal > 0 ?
                                  safe_library_name(libraries,
                                                    library_ordinal - 1) :
                                  std::to_string(library_ordinal),
                              th_bind_data.addend, th_bind_data.symbol_flags
                          );
              }
              // The delta is bits [51..61]
              // And bit 62 is to tell us if we are a rebase (0) or bind (1)
              value &= ~(1ull << 62);
              delta = (value & 0x3FF8000000000000) >> 51;
              segment_offset += delta * sizeof(pint_v);
              output << tab << tab
                     << fmt::format("Segment Offset += {:#x} ({:#x})\n",
                                    delta * sizeof(pint_v), segment_offset);
            } while (delta != 0);
            break;
          }
          case BIND_SUBOPCODE_THREADED::SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
          {
            static constexpr size_t MAX_COUNT = 65535;
            output << tab << fmt::format("[{}]\n", to_string(subopcode));
            auto val = bind_stream.read_uleb128();
            if (!val) {
              LIEF_ERR(
                  "Failed to read "
                  "BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB count"
              );
              break;
            }
            count = *val;
            if (count > MAX_COUNT) {
              LIEF_ERR("BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB "
                       "count too large: {}",
                       *val);
              break;
            }
            ordinal_table_size =
                count + 1; // the +1 comes from: 'ld64 wrote the wrong value here
                           // and we need to offset by 1 for now.'
            use_threaded_rebase_bind = true;
            ordinal_table.reserve(ordinal_table_size);
            output << tab << tab
                   << fmt::format("Ordinal table size := {:d}\n", count);
            break;
          }
        }
        break;
      }

      default:
      {
        LIEF_ERR("Unsupported opcode: {:#x}", static_cast<uint32_t>(opcode));
        output << fmt::format("[UNKNOWN OP {:#x}]\n",
                              static_cast<uint32_t>(opcode));
        break;
      }
    }
  }
}

// Weak Binding
// ============

void DyldInfo::weak_bind_opcodes(buffer_t raw) {
  if (raw.size() > weak_bind_opcodes_.size()) {
    LIEF_WARN("Weak bind opcodes update failed: data exceeds original size");
    return;
  }
  std::move(raw.begin(), raw.end(), weak_bind_opcodes_.data());
}


std::string DyldInfo::show_weak_bind_opcodes() const {
  std::ostringstream output;
  show_bindings(output, weak_bind_opcodes(), /* is_lazy = */ false);
  return output.str();
}

// Lazy Binding
// ============
void DyldInfo::lazy_bind_opcodes(buffer_t raw) {
  if (raw.size() > lazy_bind_opcodes_.size()) {
    LIEF_WARN("Lazy bind opcodes update failed: data exceeds original size");
    return;
  }
  std::move(raw.begin(), raw.end(), lazy_bind_opcodes_.data());
}

std::string DyldInfo::show_lazy_bind_opcodes() const {
  std::ostringstream output;
  show_bindings(output, lazy_bind_opcodes(), true);
  return output.str();
}

// Export Info
// ===========
std::string DyldInfo::show_export_trie() const {
  if (binary_ == nullptr) {
    LIEF_WARN("Failed to print bind opcodes");
    return "";
  }

  std::ostringstream output;
  span<const uint8_t> buffer = export_trie();

  SpanStream stream = buffer;
  show_trie(output, "", stream, 0, buffer.size(), "");

  return output.str();
}

void DyldInfo::show_trie(std::ostream& output, std::string output_prefix,
                         BinaryStream& stream, uint64_t start, uint64_t end,
                         const std::string& prefix) const {
  MachO::show_trie(output, std::move(output_prefix), stream, start, end, prefix);
}

void DyldInfo::export_trie(buffer_t raw) {
  if (raw.size() > export_trie_.size()) {
    LIEF_WARN("Export trie update failed: data exceeds original size");
    return;
  }
  std::move(raw.begin(), raw.end(), export_trie_.data());
}

void DyldInfo::accept(Visitor& visitor) const {
  visitor.visit(*this);
}

bool operator==(uint8_t lhs, DyldInfo::REBASE_OPCODES rhs) {
  return lhs == static_cast<uint8_t>(rhs);
}

bool operator!=(uint8_t lhs, DyldInfo::REBASE_OPCODES rhs) {
  return lhs != static_cast<uint8_t>(rhs);
}

DyldInfo& DyldInfo::update_rebase_info(vector_iostream& stream) {
  auto cmp = [](const RelocationDyld* lhs, const RelocationDyld* rhs) {
    return *lhs < *rhs;
  };

  // In recent versions of dyld, relocations are merged with bindings
  if (binding_encoding_version_ != BINDING_ENCODING_VERSION::V1) {
    return *this;
  }

  std::set<RelocationDyld*, decltype(cmp)> rebases(cmp);
  Binary::relocations_t relocations = binary_->relocations_list();
  for (Relocation* r : relocations) {
    if (r->origin() == Relocation::ORIGIN::DYLDINFO) {
      rebases.insert(r->as<RelocationDyld>());
    }
  }


  uint64_t current_segment_start = 0;
  uint64_t current_segment_end = 0;
  uint32_t current_segment_index = 0;
  uint8_t type = 0;
  auto address = static_cast<uint64_t>(-1);
  std::vector<details::rebase_instruction> output;

  for (RelocationDyld* rebase : rebases) {
    if (type != rebase->type()) {
      output.emplace_back(static_cast<uint8_t>(REBASE_OPCODES::SET_TYPE_IMM),
                          rebase->type());
      type = rebase->type();
    }

    if (address != rebase->address()) {
      if (rebase->address() < current_segment_start ||
          rebase->address() >= current_segment_end)
      {
        SegmentCommand* segment = rebase->segment();
        if (segment == nullptr) {
          LIEF_ERR("No segment associated with RebaseInfo");
          return *this;
        }
        size_t index = segment->index();

        current_segment_start = segment->virtual_address();
        current_segment_end = segment->virtual_address() + segment->virtual_size();
        current_segment_index = index;

        output.emplace_back(
            static_cast<uint8_t>(REBASE_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB),
            current_segment_index, rebase->address() - current_segment_start
        );
      } else {
        output.emplace_back(static_cast<uint8_t>(REBASE_OPCODES::ADD_ADDR_ULEB),
                            rebase->address() - address);
      }
      address = rebase->address();
    }

    output.emplace_back(static_cast<uint8_t>(REBASE_OPCODES::DO_REBASE_ULEB_TIMES),
                        1);
    address += binary_->pointer_size();

    if (address >= current_segment_end) {
      address = 0;
    }
  }
  output.emplace_back(static_cast<uint8_t>(REBASE_OPCODES::DONE), 0);


  // ===========================================
  // 1. First optimization
  // Compress packed runs of pointers
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:239
  // ===========================================
  auto dst = output.begin();
  for (auto it = output.begin(); it->opcode != REBASE_OPCODES::DONE; ++it) {
    if (it->opcode == REBASE_OPCODES::DO_REBASE_ULEB_TIMES && it->op1 == 1) {
      *dst = *it++;

      while (it->opcode == REBASE_OPCODES::DO_REBASE_ULEB_TIMES) {
        dst->op1 += it->op1;
        ++it;
      }

      --it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  // ===========================================
  // 2. Second optimization
  // Combine rebase/add pairs
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:257
  // ===========================================
  dst = output.begin();
  for (auto it = output.begin(); it->opcode != REBASE_OPCODES::DONE; ++it) {

    if ((it->opcode == REBASE_OPCODES::DO_REBASE_ULEB_TIMES) && it->op1 == 1 &&
        it[1].opcode == REBASE_OPCODES::ADD_ADDR_ULEB)
    {
      dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB);
      dst->op1 = it[1].op1;
      ++it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  // ===========================================
  // 3. Third optimization
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:274
  // ===========================================
  dst = output.begin();
  for (auto it = output.begin(); it->opcode != REBASE_OPCODES::DONE; ++it) {
    uint64_t delta = it->op1;
    if ((it->opcode == REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB) &&
        (it[1].opcode == REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB) &&
        (it[2].opcode == REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB) &&
        (it[1].op1 == delta) && (it[2].op1 == delta))
    {

      dst->opcode =
          static_cast<uint8_t>(REBASE_OPCODES::DO_REBASE_ULEB_TIMES_SKIPPING_ULEB);
      dst->op1 = 1;
      dst->op2 = delta;
      ++it;
      while (it->opcode == REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB &&
             it->op1 == delta)
      {
        dst->op1++;
        ++it;
      }
      --it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  // ===========================================
  // 4. Fourth optimization
  // Use immediate encodings
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:303
  // ===========================================
  const size_t pint_size = binary_->pointer_size();
  for (auto it = output.begin(); it->opcode != REBASE_OPCODES::DONE; ++it) {

    if (it->opcode == REBASE_OPCODES::ADD_ADDR_ULEB &&
        it->op1 < (15 * pint_size) && (it->op1 % pint_size) == 0)
    {
      it->opcode = static_cast<uint8_t>(REBASE_OPCODES::ADD_ADDR_IMM_SCALED);
      it->op1 = it->op1 / pint_size;
    } else if ((it->opcode == REBASE_OPCODES::DO_REBASE_ULEB_TIMES) &&
               (it->op1 < 15))
    {
      it->opcode = static_cast<uint8_t>(REBASE_OPCODES::DO_REBASE_IMM_TIMES);
    }
  }

  vector_iostream raw_output;
  bool done = false;
  for (auto it = output.begin(); !done && it != output.end(); ++it) {
    const details::rebase_instruction& inst = *it;

    switch (static_cast<REBASE_OPCODES>(inst.opcode)) {
      case REBASE_OPCODES::DONE:
      {
        done = true;
        break;
      }

      case REBASE_OPCODES::SET_TYPE_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::SET_TYPE_IMM) |
                                  inst.op1);
        break;
      }

      case REBASE_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {
        raw_output
            .write<uint8_t>(uint8_t(REBASE_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB) |
                            inst.op1)
            .write_uleb128(inst.op2);

        break;
      }

      case REBASE_OPCODES::ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);

        break;
      }

      case REBASE_OPCODES::ADD_ADDR_IMM_SCALED:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::ADD_ADDR_IMM_SCALED) |
                                  inst.op1);

        break;
      }

      case REBASE_OPCODES::DO_REBASE_IMM_TIMES:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::DO_REBASE_IMM_TIMES) |
                                  inst.op1);

        break;
      }

      case REBASE_OPCODES::DO_REBASE_ULEB_TIMES:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::DO_REBASE_ULEB_TIMES))
            .write_uleb128(inst.op1);

        break;
      }

      case REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(REBASE_OPCODES::DO_REBASE_ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);

        break;
      }

      case REBASE_OPCODES::DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
      {
        raw_output
            .write<uint8_t>(
                uint8_t(REBASE_OPCODES::DO_REBASE_ULEB_TIMES_SKIPPING_ULEB)
            )
            .write_uleb128(inst.op1)
            .write_uleb128(inst.op2);

        break;
      }

      default:
      {
        LIEF_ERR("Unknown opcode: {:#x}", static_cast<uint32_t>(inst.opcode));
      }
    }
  }
  raw_output.align(pint_size);
  if (raw_output.size() > rebase_opcodes_.size()) {
    LIEF_INFO("New rebase opcodes exceed original size: {:#08x} -> {:#08x}",
              rebase_opcodes_.size(), raw_output.size());
  }
  stream.write(raw_output.raw());
  return *this;
}

DyldInfo& DyldInfo::update_binding_info(vector_iostream& stream,
                                        details::dyld_info_command& cmd) {
  auto cmp = [](const DyldBindingInfo* lhs, const DyldBindingInfo* rhs) {
    if (lhs->library_ordinal() != rhs->library_ordinal()) {
      return lhs->library_ordinal() < rhs->library_ordinal();
    }

    if (lhs->has_symbol() && rhs->has_symbol()) {
      if (lhs->symbol()->name() != rhs->symbol()->name()) {
        return lhs->symbol()->name() < rhs->symbol()->name();
      }
    } else {
      LIEF_ERR("No symbol in LHS/RHS");
    }

    if (lhs->binding_type() != rhs->binding_type()) {
      return lhs->binding_type() < rhs->binding_type();
    }

    return lhs->address() < rhs->address();
  };

  auto cmp_weak_binding = [](const DyldBindingInfo* lhs,
                             const DyldBindingInfo* rhs) {
    if (lhs->has_symbol() && rhs->has_symbol()) {
      if (lhs->symbol()->name() != rhs->symbol()->name()) {
        return lhs->symbol()->name() < rhs->symbol()->name();
      }
    } else {
      LIEF_ERR("No symbol in LHS/RHS");
    }

    if (lhs->binding_type() != rhs->binding_type()) {
      return lhs->binding_type() < rhs->binding_type();
    }

    return lhs->address() < rhs->address();
  };

  auto cmp_lazy_binding = [](const DyldBindingInfo* lhs,
                             const DyldBindingInfo* rhs) {
    return lhs->address() < rhs->address();
  };


  DyldInfo::bind_container_t standard_binds(cmp);
  DyldInfo::bind_container_t weak_binds(cmp_weak_binding);
  DyldInfo::bind_container_t lazy_binds(cmp_lazy_binding);

  for (const std::unique_ptr<DyldBindingInfo>& binfo : binding_info_) {
    switch (binfo->binding_class()) {
      case DyldBindingInfo::CLASS::THREADED:
      case DyldBindingInfo::CLASS::STANDARD:
      {
        standard_binds.insert(binfo.get());
        break;
      }

      case DyldBindingInfo::CLASS::WEAK:
      {
        weak_binds.insert(binfo.get());
        break;
      }

      case DyldBindingInfo::CLASS::LAZY:
      {
        lazy_binds.insert(binfo.get());
        break;
      }
    }
  }

  if (!standard_binds.empty()) {
    cmd.bind_off = stream.size();
    {
      update_standard_bindings(standard_binds, stream);
    }
    cmd.bind_size = stream.size() - cmd.bind_off;

    // LIEF_DEBUG("LC_DYLD_INFO.bind_off : {:#08x} -> {:#08x}",
    //            this->bind().first, cmd.bind_off);
    // LIEF_DEBUG("LC_DYLD_INFO.bind_off : {:#08x} -> {:#08x}",
    //            this->bind().second, cmd.bind_size);
  }
  if (!weak_binds.empty()) {
    cmd.weak_bind_off = stream.size();
    {
      update_weak_bindings(weak_binds, stream);
    }
    cmd.weak_bind_size = stream.size() - cmd.weak_bind_off;
  }
  if (!lazy_binds.empty()) {
    cmd.lazy_bind_off = stream.size();
    {
      update_lazy_bindings(lazy_binds, stream);
    }
    cmd.lazy_bind_size = stream.size() - cmd.lazy_bind_off;
  }
  return *this;
}

bool operator==(uint8_t lhs, DyldInfo::BIND_OPCODES rhs) {
  return lhs == static_cast<uint8_t>(rhs);
}

bool operator!=(uint8_t lhs, DyldInfo::BIND_OPCODES rhs) {
  return lhs != static_cast<uint8_t>(rhs);
}

DyldInfo&
    DyldInfo::update_weak_bindings(const DyldInfo::bind_container_t& bindings,
                                   vector_iostream& stream) {
  std::vector<details::binding_instruction> instructions;

  uint64_t current_segment_start = 0;
  uint64_t current_segment_end = 0;
  uint32_t current_segment_index = 0;

  uint8_t type = 0;
  auto address = static_cast<uint64_t>(-1);
  std::string symbol_name;
  int64_t addend = 0;
  const size_t pint_size = binary_->pointer_size();


  for (DyldBindingInfo* info : bindings) {
    Symbol* sym = info->symbol();
    if (sym != nullptr) {
      if (sym->name() != symbol_name) {
        uint64_t flag = info->is_non_weak_definition() ?
                            BIND_SYMBOL_FLAGS::NON_WEAK_DEFINITION :
                            0;
        instructions.emplace_back(
            uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM), flag, 0,
            sym->name()
        );
        symbol_name = sym->name();
      }
    } else {
      LIEF_ERR("No symbol associated with the binding info");
    }

    if (info->binding_type() != DyldBindingInfo::TYPE(type)) {
      type = static_cast<uint8_t>(info->binding_type());
      instructions.emplace_back(static_cast<uint8_t>(BIND_OPCODES::SET_TYPE_IMM),
                                type);
    }

    if (info->address() != address) {
      if (info->address() < current_segment_start ||
          current_segment_end <= info->address())
      {
        SegmentCommand* segment = info->segment();
        if (segment == nullptr) {
          LIEF_ERR("No segment associated with weak binding info");
          return *this;
        }

        size_t index = segment->index();

        current_segment_start = segment->virtual_address();
        current_segment_end = segment->virtual_address() + segment->virtual_size();
        current_segment_index = index;

        instructions.emplace_back(
            static_cast<uint8_t>(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB),
            current_segment_index, info->address() - current_segment_start
        );

      } else {
        instructions.emplace_back(
            static_cast<uint8_t>(BIND_OPCODES::ADD_ADDR_ULEB),
            info->address() - address
        );
      }
      address = info->address();
    }

    if (addend != info->addend()) {
      instructions.emplace_back(
          static_cast<uint8_t>(BIND_OPCODES::SET_ADDEND_SLEB), info->addend()
      );
      addend = info->addend();
    }

    instructions.emplace_back(static_cast<uint8_t>(BIND_OPCODES::DO_BIND), 0);
    address += binary_->pointer_size();
  }

  instructions.emplace_back(static_cast<uint8_t>(BIND_OPCODES::DONE), 0);


  // ===========================================
  // 1. First optimization
  // combine bind/add pairs
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:469
  // ===========================================
  auto dst = instructions.begin();
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    if (it->opcode == BIND_OPCODES::DO_BIND &&
        it[1].opcode == BIND_OPCODES::ADD_ADDR_ULEB)
    {
      dst->opcode = static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB);
      dst->op1 = it[1].op1;
      ++it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  // ===========================================
  // 2. Second optimization
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:485
  // ===========================================
  dst = instructions.begin();
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    uint64_t delta = it->op1;
    if (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB &&
        it[1].opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB && it[1].op1 == delta)
    {
      dst->opcode =
          static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB);
      dst->op1 = 1;
      dst->op2 = delta;
      ++it;
      while (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB && it->op1 == delta)
      {
        dst->op1++;
        ++it;
      }
      --it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);


  // ===========================================
  // 3. Third optimization
  // Use immediate encodings
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:512
  // ===========================================
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    if (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB &&
        it->op1 < (15 * pint_size) && (it->op1 % pint_size) == 0)
    {
      it->opcode = static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED);
      it->op1 = it->op1 / pint_size;
    } else if (it->opcode == BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB && it->op1 <= 15)
    {
      it->opcode = static_cast<uint8_t>(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM);
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  bool done = false;
  vector_iostream raw_output;
  for (auto it = instructions.begin(); !done && it != instructions.end(); ++it) {
    const details::binding_instruction& inst = *it;
    switch (static_cast<BIND_OPCODES>(inst.opcode)) {
      case BIND_OPCODES::DONE:
      {
        done = true;
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM) |
                                  inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB:
      {
        raw_output
            .write<uint8_t>(
                static_cast<uint8_t>(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB)
            )
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_SPECIAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM) |
                                  (inst.op1 & IMMEDIATE_MASK));
        break;
      }

      case BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM) |
                            inst.op1)
            .write(inst.name);
        break;
      }

      case BIND_OPCODES::SET_TYPE_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_TYPE_IMM) | inst.op1);
        break;
      }

      case BIND_OPCODES::SET_ADDEND_SLEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_ADDEND_SLEB))
            .write_sleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB) |
                            inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      case BIND_OPCODES::ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::DO_BIND));
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED:
      {
        raw_output.write<uint8_t>(
            uint8_t(BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED) | inst.op1
        );
        break;
      }

      case BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
      {
        raw_output
            .write<uint8_t>(
                uint8_t(BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB)
            )
            .write_uleb128(inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      default:
      {
        LIEF_WARN("Unhandled opcode {} ({:d}) for weak bindings",
                  to_string(static_cast<BIND_OPCODES>(inst.opcode)), inst.opcode);
        break;
      }
    }
  }
  raw_output.align(pint_size);
  if (raw_output.size() > weak_bind_opcodes_.size()) {
    LIEF_INFO("New weak bind opcodes exceed original size: {:#08x} -> {:#08x}",
              weak_bind_opcodes_.size(), raw_output.size());
  }
  stream.write(raw_output.raw());
  return *this;
}

DyldInfo&
    DyldInfo::update_lazy_bindings(const DyldInfo::bind_container_t& bindings,
                                   vector_iostream& stream) {

  vector_iostream raw_output;
  for (DyldBindingInfo* info : bindings) {
    SegmentCommand* segment = info->segment();
    if (segment == nullptr) {
      LIEF_ERR("No segment associated with lazy binding info");
      return *this;
    }
    size_t index = segment->index();

    uint64_t current_segment_start = segment->virtual_address();
    uint32_t current_segment_index = index;

    raw_output
        .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB) |
                        current_segment_index)
        .write_uleb128(info->address() - current_segment_start);

    if (info->library_ordinal() <= 0) {
      raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM) |
                                (info->library_ordinal() & IMMEDIATE_MASK));
    } else if (info->library_ordinal() <= 15) {
      raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM) |
                                info->library_ordinal());
    } else {
      raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB))
          .write_uleb128(info->library_ordinal());
    }

    uint64_t flags = info->is_weak_import() ? BIND_SYMBOL_FLAGS::WEAK_IMPORT : 0;
    flags |= info->is_non_weak_definition() ?
                 BIND_SYMBOL_FLAGS::NON_WEAK_DEFINITION :
                 0;
    if (!info->has_symbol()) {
      LIEF_ERR("Missing symbol for update");
      return *this;
    }
    raw_output
        .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM) |
                        flags)
        .write(info->symbol()->name());

    raw_output.write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::DO_BIND))
        .write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::DONE));
  }

  raw_output.align(binary_->pointer_size());

  LIEF_DEBUG("Lazy bind size: {:#x} vs {:#x}", raw_output.size(),
             lazy_bind_opcodes_.size());

  if (raw_output.size() > lazy_bind_opcodes_.size()) {
    LIEF_INFO("New lazy bind opcodes exceed original size: {:#08x} -> {:#08x}",
              lazy_bind_opcodes_.size(), raw_output.size());
  }
  stream.write(raw_output.raw());
  return *this;
}

DyldInfo&
    DyldInfo::update_standard_bindings(const DyldInfo::bind_container_t& bindings,
                                       vector_iostream& stream) {
  switch (binding_encoding_version_) {
    case BINDING_ENCODING_VERSION::V1:
    {
      update_standard_bindings_v1(bindings, stream);
      break;
    }

    case BINDING_ENCODING_VERSION::V2:
    {
      std::vector<RelocationDyld*> rebases;
      Binary::relocations_t relocations = binary_->relocations_list();
      rebases.reserve(relocations.size());
      for (Relocation* r : relocations) {
        if (r->origin() == Relocation::ORIGIN::DYLDINFO) {
          rebases.push_back(r->as<RelocationDyld>());
        }
      }
      LIEF_DEBUG("Bindings V2: #{} relocations", rebases.size());
      update_standard_bindings_v2(bindings, std::move(rebases), stream);
      break;
    }

    case BINDING_ENCODING_VERSION::UNKNOWN:
    default:
    {
      LIEF_WARN("Unsupported version");
      break;
    }
  }
  return *this;
}


DyldInfo& DyldInfo::update_standard_bindings_v1(
    const DyldInfo::bind_container_t& bindings, vector_iostream& stream
) {
  // This function updates the standard bindings opcodes (i.e. not lazy and not
  // weak). The following code is mainly inspired from LinkEdit.hpp:
  // BindingInfoAtom<A>::encodeV1()

  std::vector<details::binding_instruction> instructions;

  uint64_t current_segment_start = 0;
  uint64_t current_segment_end = 0;
  uint32_t current_segment_index = 0;
  uint8_t type = 0;
  auto address = static_cast<uint64_t>(-1);
  int32_t ordinal = 0x80000000;
  std::string symbol_name;
  int64_t addend = 0;
  const size_t pint_size = binary_->pointer_size();

  for (DyldBindingInfo* info : bindings) {
    if (info->library_ordinal() != ordinal) {
      if (info->library_ordinal() <= 0) {
        instructions.emplace_back(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM),
                                  info->library_ordinal());
      } else {
        instructions.emplace_back(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB),
                                  info->library_ordinal());
      }
      ordinal = info->library_ordinal();
    }

    if (!info->has_symbol()) {
      LIEF_ERR("Missing symbol for updating v1 binding");
      return *this;
    }
    if (info->symbol()->name() != symbol_name) {
      uint64_t flag = info->is_weak_import() ? BIND_SYMBOL_FLAGS::WEAK_IMPORT : 0;
      symbol_name = info->symbol()->name();
      instructions.emplace_back(
          uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM), flag, 0,
          symbol_name
      );
    }

    if (info->binding_type() != DyldBindingInfo::TYPE(type)) {
      type = static_cast<uint8_t>(info->binding_type());
      instructions.emplace_back(uint8_t(BIND_OPCODES::SET_TYPE_IMM), type);
    }

    if (info->address() != address) {
      if (info->address() < current_segment_start ||
          info->address() >= current_segment_end)
      {
        SegmentCommand* segment = info->segment();
        if (segment == nullptr) {
          LIEF_ERR("Segment not found for v1 binding update");
          return *this;
        }
        size_t index = segment->index();

        current_segment_start = segment->virtual_address();
        current_segment_end = segment->virtual_address() + segment->virtual_size();
        current_segment_index = index;

        instructions.emplace_back(
            uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB),
            current_segment_index, info->address() - current_segment_start
        );

      } else {
        instructions.emplace_back(uint8_t(BIND_OPCODES::ADD_ADDR_ULEB),
                                  info->address() - address);
      }
      address = info->address();
    }

    if (addend != info->addend()) {
      instructions.emplace_back(uint8_t(BIND_OPCODES::SET_ADDEND_SLEB),
                                info->addend());
      addend = info->addend();
    }

    instructions.emplace_back(uint8_t(BIND_OPCODES::DO_BIND), 0);
    address += binary_->pointer_size();
  }

  instructions.emplace_back(uint8_t(BIND_OPCODES::DONE), 0);


  // ===========================================
  // 1. First optimization
  // combine bind/add pairs
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:469
  // ===========================================
  auto dst = instructions.begin();
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    if (it->opcode == BIND_OPCODES::DO_BIND &&
        it[1].opcode == BIND_OPCODES::ADD_ADDR_ULEB)
    {
      dst->opcode = static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB);
      dst->op1 = it[1].op1;
      ++it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  // ===========================================
  // 2. Second optimization
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:485
  // ===========================================
  dst = instructions.begin();
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    uint64_t delta = it->op1;
    if (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB &&
        it[1].opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB && it[1].op1 == delta)
    {
      dst->opcode =
          static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB);
      dst->op1 = 1;
      dst->op2 = delta;
      ++it;
      while (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB && it->op1 == delta)
      {
        dst->op1++;
        ++it;
      }
      --it;
      ++dst;
    } else {
      *dst++ = *it;
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);


  // ===========================================
  // 3. Third optimization
  // Use immediate encodings
  // Based on ld64-274.2/src/ld/LinkEdit.hpp:512
  // ===========================================
  for (auto it = instructions.begin(); it->opcode != BIND_OPCODES::DONE; ++it) {
    if (it->opcode == BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB &&
        it->op1 < (15 * pint_size) && (it->op1 % pint_size) == 0)
    {
      it->opcode = static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED);
      it->op1 = it->op1 / pint_size;
    } else if (it->opcode == BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB && it->op1 <= 15)
    {
      it->opcode = static_cast<uint8_t>(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM);
    }
  }
  dst->opcode = static_cast<uint8_t>(REBASE_OPCODES::DONE);

  bool done = false;
  vector_iostream raw_output;
  for (auto it = instructions.begin(); !done && it != instructions.end(); ++it) {
    const details::binding_instruction& inst = *it;
    switch (static_cast<BIND_OPCODES>(inst.opcode)) {
      case BIND_OPCODES::DONE:
      {
        done = true;
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM) |
                                  inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_SPECIAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM) |
                                  (inst.op1 & IMMEDIATE_MASK));
        break;
      }

      case BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM) |
                            inst.op1)
            .write(inst.name);
        break;
      }

      case BIND_OPCODES::SET_TYPE_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_TYPE_IMM) | inst.op1);
        break;
      }

      case BIND_OPCODES::SET_ADDEND_SLEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_ADDEND_SLEB))
            .write_sleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB) |
                            inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      case BIND_OPCODES::ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::DO_BIND));
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED:
      {
        raw_output.write<uint8_t>(
            uint8_t(BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED) | inst.op1
        );
        break;
      }

      case BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
      {
        raw_output
            .write<uint8_t>(
                uint8_t(BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB)
            )
            .write_uleb128(inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      default:
      {
        LIEF_WARN("Unhandled opcode {} ({:d}) for weak bindings",
                  to_string(static_cast<BIND_OPCODES>(inst.opcode)), inst.opcode);
        break;
      }
    }
  }
  raw_output.align(pint_size);
  if (raw_output.size() > bind_opcodes_.size()) {
    LIEF_INFO("New regular bind opcodes exceed original size: {:#08x} -> {:#08x}",
              bind_opcodes_.size(), raw_output.size());
  }
  stream.write(raw_output.raw());
  return *this;
}


DyldInfo& DyldInfo::update_standard_bindings_v2(
    const DyldInfo::bind_container_t& bindings_set,
    std::vector<RelocationDyld*> rebases, vector_iostream& stream
) {
  // v2 encoding as defined in Linkedit.hpp - BindingInfoAtom<A>::encodeV2()
  // This encoding uses THREADED opcodes.
  std::vector<DyldBindingInfo*> bindings = {bindings_set.begin(),
                                            bindings_set.end()};

  std::vector<details::binding_instruction> instructions;
  uint64_t current_segment_start = 0;
  uint64_t current_segment_end = 0;
  uint64_t current_segment_index = 0;
  uint8_t type = 0;
  auto address = static_cast<uint64_t>(-1);
  int32_t ordinal = 0x80000000;
  std::string symbol_name;
  int64_t addend = 0;
  auto num_bindings = static_cast<uint64_t>(-1);
  const size_t pint_size = binary_->pointer_size();

  for (DyldBindingInfo* info : bindings) {
    bool made_changes = false;
    const int32_t lib_ordinal = info->library_ordinal();
    if (ordinal != lib_ordinal) {
      if (lib_ordinal <= 0) {
        instructions.emplace_back(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM),
                                  lib_ordinal);
      } else if (lib_ordinal <= 15) {
        instructions.emplace_back(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM),
                                  lib_ordinal);
      } else {
        instructions.emplace_back(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB),
                                  lib_ordinal);
      }
      ordinal = lib_ordinal;
      made_changes = true;
    }
    if (!info->has_symbol()) {
      LIEF_ERR("Missing symbol for updating bindings v2");
      return *this;
    }
    if (symbol_name != info->symbol()->name()) {
      uint64_t flag = info->is_weak_import() ? BIND_SYMBOL_FLAGS::WEAK_IMPORT : 0;
      symbol_name = info->symbol()->name();
      instructions.emplace_back(
          uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM), flag, 0,
          symbol_name
      );
      made_changes = true;
    }

    if (info->binding_type() != DyldBindingInfo::TYPE(type)) {
      if (info->binding_type() != DyldBindingInfo::TYPE::POINTER) {
        LIEF_ERR("Unsupported bind type with linked list opcodes");
        return *this;
      }
      type = static_cast<uint8_t>(info->binding_type());
      instructions.emplace_back(uint8_t(BIND_OPCODES::SET_TYPE_IMM), type);
      made_changes = true;
    }

    if (address != info->address()) {
      address = info->address();
      SegmentCommand* segment = info->segment();
      if (segment == nullptr) {
        LIEF_ERR("Segment not found for v2 binding update");
        return *this;
      }
      size_t index = segment->index();
      current_segment_start = segment->virtual_address();
      current_segment_end = segment->virtual_address() + segment->virtual_size();
      current_segment_index = index;
      made_changes = true;
    }

    if (addend != info->addend()) {
      addend = info->addend();
      instructions.emplace_back(uint8_t(BIND_OPCODES::SET_ADDEND_SLEB), addend);
      made_changes = true;
    }

    if (made_changes) {
      ++num_bindings;
      instructions.emplace_back(uint8_t(BIND_OPCODES::DO_BIND), 0);
    }
  }

  if (num_bindings > std::numeric_limits<uint16_t>::max() &&
      num_bindings != static_cast<uint64_t>(-1))
  {
    LIEF_ERR("Too many binds ({:d}). The limit being 65536");
    return *this;
  }

  std::vector<uint64_t> threaded_rebase_bind_indices;
  threaded_rebase_bind_indices.reserve(bindings.size() + rebases.size());

  for (int64_t i = 0, e = rebases.size(); i != e; ++i) {
    threaded_rebase_bind_indices.push_back(-i);
  }

  for (int64_t i = 0, e = bindings.size(); i != e; ++i) {
    threaded_rebase_bind_indices.push_back(i + 1);
  }

  std::sort(threaded_rebase_bind_indices.begin(),
            threaded_rebase_bind_indices.end(),
            [&bindings, &rebases](int64_t index_a, int64_t index_b) {
              if (index_a == index_b) {
                return false;
              }
              uint64_t address_a = index_a <= 0 ? rebases[-index_a]->address() :
                                                  bindings[index_a - 1]->address();
              uint64_t address_b = index_b <= 0 ? rebases[-index_b]->address() :
                                                  bindings[index_b - 1]->address();
              return address_a < address_b;
            });

  current_segment_start = 0;
  current_segment_end = 0;
  current_segment_index = 0;

  uint64_t prev_page_index = 0;

  for (int64_t entry_index : threaded_rebase_bind_indices) {
    RelocationDyld* rebase = nullptr;
    DyldBindingInfo* bind = nullptr;

    uint64_t address = 0;
    SegmentCommand* segment = nullptr;
    if (entry_index <= 0) {
      rebase = rebases[-entry_index];
      address = rebase->address();
      segment = rebase->segment();
    } else {
      bind = bindings[entry_index - 1];
      address = bind->address();
      segment = bind->segment();
    }

    if (segment == nullptr) {
      LIEF_ERR("Missing segment while update binding v2");
      return *this;
    }
    if (address % 8 != 0) {
      LIEF_WARN("Address not aligned");
    }

    bool new_segment = false;
    if (address < current_segment_start || address >= current_segment_end) {
      size_t index = segment->index();
      current_segment_start = segment->virtual_address();
      current_segment_end = segment->virtual_address() + segment->virtual_size();
      current_segment_index = index;
      new_segment = true;
    }
    uint64_t page_index = (address - current_segment_start) / 4096;
    if (new_segment || page_index != prev_page_index) {
      instructions.emplace_back(uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB),
                                current_segment_index,
                                address - current_segment_start);
      instructions.emplace_back(uint8_t(BIND_OPCODES::THREADED) |
                                    uint8_t(BIND_SUBOPCODE_THREADED::APPLY),
                                0);
    }
    prev_page_index = page_index;
  }
  instructions.emplace_back(static_cast<uint8_t>(BIND_OPCODES::DONE), 0);
  vector_iostream raw_output;
  raw_output
      .write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::THREADED) |
                      static_cast<uint8_t>(
                          BIND_SUBOPCODE_THREADED::SET_BIND_ORDINAL_TABLE_SIZE_ULEB
                      ))
      .write_uleb128(num_bindings + 1);

  bool done = false;
  for (auto it = instructions.begin(); !done && it != instructions.end(); ++it) {
    const details::binding_instruction& inst = *it;
    switch (static_cast<BIND_OPCODES>(it->opcode)) {
      case BIND_OPCODES::DONE:
      {
        done = true;
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_IMM) |
                                  inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_ORDINAL_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_DYLIB_SPECIAL_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_DYLIB_SPECIAL_IMM) |
                                  (inst.op1 & IMMEDIATE_MASK));
        break;
      }

      case BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SYMBOL_TRAILING_FLAGS_IMM) |
                            inst.op1)
            .write(inst.name);
        break;
      }

      case BIND_OPCODES::SET_TYPE_IMM:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_TYPE_IMM) | inst.op1);
        break;
      }

      case BIND_OPCODES::SET_ADDEND_SLEB:
      {
        raw_output.write<uint8_t>(uint8_t(BIND_OPCODES::SET_ADDEND_SLEB))
            .write_sleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB:
      {
        raw_output
            .write<uint8_t>(uint8_t(BIND_OPCODES::SET_SEGMENT_AND_OFFSET_ULEB) |
                            inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      case BIND_OPCODES::ADD_ADDR_ULEB:
      {
        raw_output
            .write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::ADD_ADDR_ULEB))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND:
      {
        raw_output.write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::DO_BIND));
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB:
      {
        raw_output
            .write<uint8_t>(
                static_cast<uint8_t>(BIND_OPCODES::DO_BIND_ADD_ADDR_ULEB)
            )
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED:
      {
        raw_output.write<uint8_t>(
            uint8_t(BIND_OPCODES::DO_BIND_ADD_ADDR_IMM_SCALED) | inst.op1
        );
        break;
      }

      case BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
      {
        raw_output
            .write<uint8_t>(
                uint8_t(BIND_OPCODES::DO_BIND_ULEB_TIMES_SKIPPING_ULEB)
            )
            .write_uleb128(inst.op1)
            .write_uleb128(inst.op2);
        break;
      }

      case BIND_OPCODES::THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
      {
        raw_output
            .write<uint8_t>(static_cast<uint8_t>(
                BIND_OPCODES::THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB
            ))
            .write_uleb128(inst.op1);
        break;
      }

      case BIND_OPCODES::THREADED_APPLY:
      {
        raw_output.write(static_cast<uint8_t>(BIND_OPCODES::THREADED_APPLY));
        break;
      }

      default:
      {
        LIEF_ERR("Unsupported opcode");
        done = true;
        break;
      }
    }
  }

  raw_output.write<uint8_t>(static_cast<uint8_t>(BIND_OPCODES::DONE));
  raw_output.align(pint_size);

  if (raw_output.size() > bind_opcodes_.size()) {
    LIEF_INFO(
        "New regular v2 bind opcodes exceed original size: {:#08x} -> {:#08x}",
        bind_opcodes_.size(), raw_output.size()
    );
  }
  stream.write(raw_output.raw());
  return *this;
}


DyldInfo& DyldInfo::update_export_trie(vector_iostream& stream) {
  std::vector<uint8_t> raw_output =
      create_trie(export_info_, binary_->pointer_size());
  if (raw_output.size() > export_trie_.size()) {
    LIEF_INFO("New export trie exceeds original size: {:#08x} -> {:#08x}",
              export_trie_.size(), raw_output.size());
  }
  stream.write(raw_output);
  return *this;
}


ExportInfo* DyldInfo::add(std::unique_ptr<ExportInfo> info) {
  return export_info_.emplace_back(std::move(info)).get();
}

std::ostream& DyldInfo::print(std::ostream& os) const {
  LoadCommand::print(os) << '\n';

  os << fmt::format("{:11}: {:10} {:10}", "Type", "Offset", "Size") << '\n'
     << fmt::format("{:11}: {:10} {:10}", "Rebase", std::get<0>(rebase()),
                    std::get<1>(rebase()))
     << '\n'
     << fmt::format("{:11}: {:10} {:10}", "Bind", std::get<0>(bind()),
                    std::get<1>(bind()))
     << '\n'
     << fmt::format("{:11}: {:10} {:10}", "Weak bind", std::get<0>(weak_bind()),
                    std::get<1>(weak_bind()))
     << '\n'
     << fmt::format("{:11}: {:10} {:10}", "Lazy bind", std::get<0>(lazy_bind()),
                    std::get<1>(lazy_bind()))
     << '\n'
     << fmt::format("{:11}: {:10} {:10}", "Export", std::get<0>(export_info()),
                    std::get<1>(export_info()))
     << '\n';

  it_const_binding_info bindings = this->bindings();
  if (!bindings.empty()) {
    os << fmt::format("Binding Info (#{})", bindings.size()) << '\n';
    for (const BindingInfo& info : bindings) {
      os << info << '\n';
    }
  }

  it_const_export_info exports = this->exports();
  if (!exports.empty()) {
    os << fmt::format("Export Info (#{})", exports.size()) << '\n';
    for (const ExportInfo& info : exports) {
      os << info << '\n';
    }
  }

  return os;
}


const char* to_string(DyldInfo::REBASE_TYPE e) {
#define ENTRY(X) std::pair(DyldInfo::REBASE_TYPE::X, #X)
  STRING_MAP enums2str{
      ENTRY(POINTER),
      ENTRY(TEXT_ABSOLUTE32),
      ENTRY(TEXT_PCREL32),
      ENTRY(THREADED),
  };
#undef ENTRY
  if (auto it = enums2str.find(e); it != enums2str.end()) {
    return it->second;
  }
  return "UNKNOWN";
}

const char* to_string(DyldInfo::REBASE_OPCODES e) {
#define ENTRY(X) std::pair(DyldInfo::REBASE_OPCODES::X, #X)
  STRING_MAP enums2str{
      ENTRY(DONE),
      ENTRY(SET_TYPE_IMM),
      ENTRY(SET_SEGMENT_AND_OFFSET_ULEB),
      ENTRY(ADD_ADDR_ULEB),
      ENTRY(ADD_ADDR_IMM_SCALED),
      ENTRY(DO_REBASE_IMM_TIMES),
      ENTRY(DO_REBASE_ULEB_TIMES),
      ENTRY(DO_REBASE_ADD_ADDR_ULEB),
      ENTRY(DO_REBASE_ULEB_TIMES_SKIPPING_ULEB),
  };
#undef ENTRY
  if (auto it = enums2str.find(e); it != enums2str.end()) {
    return it->second;
  }
  return "UNKNOWN";
}

const char* to_string(DyldInfo::BIND_OPCODES e) {
#define ENTRY(X) std::pair(DyldInfo::BIND_OPCODES::X, #X)
  STRING_MAP enums2str{
      ENTRY(DONE),
      ENTRY(SET_DYLIB_ORDINAL_IMM),
      ENTRY(SET_DYLIB_ORDINAL_ULEB),
      ENTRY(SET_DYLIB_SPECIAL_IMM),
      ENTRY(SET_SYMBOL_TRAILING_FLAGS_IMM),
      ENTRY(SET_TYPE_IMM),
      ENTRY(SET_ADDEND_SLEB),
      ENTRY(SET_SEGMENT_AND_OFFSET_ULEB),
      ENTRY(ADD_ADDR_ULEB),
      ENTRY(DO_BIND),
      ENTRY(DO_BIND_ADD_ADDR_ULEB),
      ENTRY(DO_BIND_ADD_ADDR_IMM_SCALED),
      ENTRY(DO_BIND_ULEB_TIMES_SKIPPING_ULEB),
      ENTRY(THREADED),
      ENTRY(THREADED_APPLY),
      ENTRY(THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB),
  };
#undef ENTRY
  if (auto it = enums2str.find(e); it != enums2str.end()) {
    return it->second;
  }
  return "UNKNOWN";
}

const char* to_string(DyldInfo::BIND_SUBOPCODE_THREADED e) {
#define ENTRY(X) std::pair(DyldInfo::BIND_SUBOPCODE_THREADED::X, #X)
  STRING_MAP enums2str{
      ENTRY(SET_BIND_ORDINAL_TABLE_SIZE_ULEB),
      ENTRY(APPLY),
  };
#undef ENTRY
  if (auto it = enums2str.find(e); it != enums2str.end()) {
    return it->second;
  }
  return "UNKNOWN";
}


}