/* Copyright 2017 - 2024 R. Thomas
 * Copyright 2017 - 2024 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 <algorithm>

#include "logging.hpp"

#include "Object.tcc"
#include "Binary.tcc"
#include "paging.hpp"

#include "LIEF/Visitor.hpp"
#include "LIEF/utils.hpp"
#include "LIEF/BinaryStream/SpanStream.hpp"

#include "LIEF/MachO/Binary.hpp"
#include "LIEF/MachO/BuildVersion.hpp"
#include "LIEF/MachO/Builder.hpp"
#include "LIEF/MachO/ChainedBindingInfo.hpp"
#include "LIEF/MachO/CodeSignature.hpp"
#include "LIEF/MachO/CodeSignatureDir.hpp"
#include "LIEF/MachO/DataInCode.hpp"
#include "LIEF/MachO/DyldBindingInfo.hpp"
#include "LIEF/MachO/DyldChainedFixups.hpp"
#include "LIEF/MachO/DyldEnvironment.hpp"
#include "LIEF/MachO/DyldExportsTrie.hpp"
#include "LIEF/MachO/DyldInfo.hpp"
#include "LIEF/MachO/DylibCommand.hpp"
#include "LIEF/MachO/DylinkerCommand.hpp"
#include "LIEF/MachO/DynamicSymbolCommand.hpp"
#include "LIEF/MachO/EncryptionInfo.hpp"
#include "LIEF/MachO/ExportInfo.hpp"
#include "LIEF/MachO/FunctionStarts.hpp"
#include "LIEF/MachO/IndirectBindingInfo.hpp"
#include "LIEF/MachO/LinkEdit.hpp"
#include "LIEF/MachO/LinkerOptHint.hpp"
#include "LIEF/MachO/MainCommand.hpp"
#include "LIEF/MachO/RPathCommand.hpp"
#include "LIEF/MachO/Relocation.hpp"
#include "LIEF/MachO/RelocationFixup.hpp"
#include "LIEF/MachO/Routine.hpp"
#include "LIEF/MachO/Section.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/SegmentSplitInfo.hpp"
#include "LIEF/MachO/SourceVersion.hpp"
#include "LIEF/MachO/SubClient.hpp"
#include "LIEF/MachO/SubFramework.hpp"
#include "LIEF/MachO/Symbol.hpp"
#include "LIEF/MachO/SymbolCommand.hpp"
#include "LIEF/MachO/ThreadCommand.hpp"
#include "LIEF/MachO/TwoLevelHints.hpp"
#include "LIEF/MachO/UUIDCommand.hpp"
#include "LIEF/MachO/VersionMin.hpp"
#include "MachO/Structures.hpp"

#include "internal_utils.hpp"

namespace LIEF {
namespace MachO {

bool Binary::KeyCmp::operator() (const Relocation* lhs, const Relocation* rhs) const {
  return *lhs < *rhs;
}

Binary::Binary() :
  LIEF::Binary(LIEF::Binary::FORMATS::MACHO)
{}

LIEF::Binary::sections_t Binary::get_abstract_sections() {
  LIEF::Binary::sections_t result;
  it_sections sections = this->sections();
  std::transform(std::begin(sections), std::end(sections),
                 std::back_inserter(result),
                 [] (Section& s) { return &s; });

  return result;
}
// LIEF Interface
// ==============

void Binary::patch_address(uint64_t address,
                           const std::vector<uint8_t>& patch_value,
                           LIEF::Binary::VA_TYPES)
{
  // Find the segment associated with the virtual address
  SegmentCommand* segment_topatch = segment_from_virtual_address(address);
  if (segment_topatch == nullptr) {
    LIEF_ERR("Unable to find segment associated with address: 0x{:x}", address);
    return;
  }
  const uint64_t offset = address - segment_topatch->virtual_address();
  span<uint8_t> content = segment_topatch->writable_content();
  if (offset > content.size() || (offset + patch_value.size()) > content.size()) {
    LIEF_ERR("The patch value ({} bytes @0x{:x}) is out of bounds of the segment (limit: 0x{:x})",
             patch_value.size(), offset, content.size());
    return;
  }
  std::move(std::begin(patch_value), std::end(patch_value), content.data() + offset);
}

void Binary::patch_address(uint64_t address, uint64_t patch_value, size_t size, LIEF::Binary::VA_TYPES) {
  if (size > sizeof(patch_value)) {
    LIEF_ERR("Invalid size: 0x{:x}", size);
    return;
  }

  SegmentCommand* segment_topatch = segment_from_virtual_address(address);

  if (segment_topatch == nullptr) {
    LIEF_ERR("Unable to find segment associated with address: 0x{:x}", address);
    return;
  }
  const uint64_t offset = address - segment_topatch->virtual_address();
  span<uint8_t> content = segment_topatch->writable_content();

  if (offset > content.size() || (offset + size) > content.size()) {
    LIEF_ERR("The patch value ({} bytes @0x{:x}) is out of bounds of the segment (limit: 0x{:x})",
             size, offset, content.size());
    return;
  }

  switch (size) {
    case sizeof(uint8_t):
      {
        auto X = static_cast<uint8_t>(patch_value);
        memcpy(content.data() + offset, &X, sizeof(uint8_t));
        break;
      }

    case sizeof(uint16_t):
      {
        auto X = static_cast<uint16_t>(patch_value);
        memcpy(content.data() + offset, &X, sizeof(uint16_t));
        break;
      }

    case sizeof(uint32_t):
      {
        auto X = static_cast<uint32_t>(patch_value);
        memcpy(content.data() + offset, &X, sizeof(uint32_t));
        break;
      }

    case sizeof(uint64_t):
      {
        auto X = static_cast<uint64_t>(patch_value);
        memcpy(content.data() + offset, &X, sizeof(uint64_t));
        break;
      }

    default:
      {
        LIEF_ERR("The provided size ({}) does not match the size of an integer", size);
        return;
      }
  }
}

span<const uint8_t> Binary::get_content_from_virtual_address(
    uint64_t virtual_address, uint64_t size, LIEF::Binary::VA_TYPES) const
{
  const SegmentCommand* segment = segment_from_virtual_address(virtual_address);

  if (segment == nullptr) {
    LIEF_ERR("Unable to find segment associated with address: 0x{:x}", virtual_address);
    return {};
  }

  span<const uint8_t> content = segment->content();
  const uint64_t offset = virtual_address - segment->virtual_address();

  uint64_t checked_size = size;
  if (offset > content.size() || (offset + checked_size) > content.size()) {
    checked_size = checked_size - (offset + checked_size - content.size());
  }

  return {content.data() + offset, static_cast<size_t>(checked_size)};
}


uint64_t Binary::entrypoint() const {
  if (const MainCommand* cmd = main_command()) {
    return imagebase() + cmd->entrypoint();
  }

  if (const ThreadCommand* cmd = thread_command()) {
    auto range = va_ranges();
    const uint64_t pc = cmd->pc();
    if (range.start <= pc && pc < range.end) {
      return pc;
    }
    return imagebase() + cmd->pc();
  }

  LIEF_WARN("Can't find LC_MAIN nor LC_THREAD/LC_UNIXTHREAD");
  return 0;
}

LIEF::Binary::symbols_t Binary::get_abstract_symbols() {
  LIEF::Binary::symbols_t syms;
  syms.reserve(symbols_.size());
  std::transform(std::begin(symbols_), std::end(symbols_),
                 std::back_inserter(syms),
                 [] (const std::unique_ptr<Symbol>& s) {
                   return s.get();
                 });
  return syms;
}


LIEF::Binary::functions_t Binary::get_abstract_exported_functions() const {
  LIEF::Binary::functions_t result;
  it_const_exported_symbols syms = exported_symbols();
  std::transform(std::begin(syms), std::end(syms), std::back_inserter(result),
    [] (const Symbol& s) {
      return Function(s.name(), s.value(), Function::FLAGS::EXPORTED);
    }
  );
  return result;
}

LIEF::Binary::functions_t Binary::get_abstract_imported_functions() const {
  LIEF::Binary::functions_t result;
  it_const_imported_symbols syms = imported_symbols();
  std::transform(std::begin(syms), std::end(syms), std::back_inserter(result),
    [] (const Symbol& s) {
      return Function(s.name(), s.value(), Function::FLAGS::IMPORTED);
    }
  );
  return result;
}


std::vector<std::string> Binary::get_abstract_imported_libraries() const {
  std::vector<std::string> result;
  for (const DylibCommand& lib : libraries()) {
    result.push_back(lib.name());
  }
  return result;
}

// Relocations
Binary::it_relocations Binary::relocations() {
  relocations_t result;
  for (SegmentCommand* segment : segments_) {
    std::transform(std::begin(segment->relocations_), std::end(segment->relocations_),
                   std::inserter(result, std::begin(result)),
                   [] (const std::unique_ptr<Relocation>& r) {
                     return r.get();
                   });
  }

  for (Section* section : sections_) {
    std::transform(std::begin(section->relocations_), std::end(section->relocations_),
                   std::inserter(result, std::begin(result)),
                   [] (const std::unique_ptr<Relocation>& r) {
                     return r.get();
                   });
  }

  relocations_ = std::move(result);
  return relocations_;
}

Binary::it_const_relocations Binary::relocations() const {
  relocations_t result;
  for (const SegmentCommand* segment : segments_) {
    std::transform(std::begin(segment->relocations_), std::end(segment->relocations_),
                   std::inserter(result, std::begin(result)),
                   [] (const std::unique_ptr<Relocation>& r) {
                     return r.get();
                   });
  }

  for (const Section* section : sections_) {
    std::transform(std::begin(section->relocations_), std::end(section->relocations_),
                   std::inserter(result, std::begin(result)),
                   [] (const std::unique_ptr<Relocation>& r) {
                     return r.get();
                   });
  }

  relocations_ = std::move(result);
  return relocations_;
}


LIEF::Binary::relocations_t Binary::get_abstract_relocations() {
  LIEF::Binary::relocations_t relocations;
  it_relocations macho_relocations = this->relocations();
  relocations.reserve(macho_relocations.size());

  for (Relocation& r : macho_relocations) {
    relocations.push_back(&r);
  }

  return relocations;
}


// Symbols
// =======

bool Binary::is_exported(const Symbol& symbol) {
  return !symbol.is_external() && symbol.has_export_info();
}


bool Binary::is_imported(const Symbol& symbol) {
  return symbol.is_external() && !symbol.has_export_info();
}

const Symbol* Binary::get_symbol(const std::string& name) const {
  const auto it_symbol = std::find_if(
      std::begin(symbols_), std::end(symbols_),
      [&name] (const std::unique_ptr<Symbol>& sym) {
        return sym->name() == name;
      });

  if (it_symbol == std::end(symbols_)) {
    return nullptr;
  }

  return it_symbol->get();
}

void Binary::write(const std::string& filename) {
  Builder::write(*this, filename);
}

void Binary::write(const std::string& filename, Builder::config_t config) {
  Builder::write(*this, filename, config);
}

void Binary::write(std::ostream& os) {
  Builder::write(*this, os);
}

const Section* Binary::section_from_offset(uint64_t offset) const {
  const auto it_section = std::find_if(
      sections_.cbegin(), sections_.cend(),
      [offset] (const Section* section) {
        return section->offset() <= offset &&
               offset < (section->offset() + section->size());
      });

  if (it_section == sections_.cend()) {
    return nullptr;
  }

  return *it_section;
}

const Section* Binary::section_from_virtual_address(uint64_t address) const {
  const auto it_section = std::find_if(
      std::begin(sections_), std::end(sections_),
      [address] (const Section* section) {
        return section->virtual_address() <= address &&
               address < (section->virtual_address() + section->size());
      });

  if (it_section == std::end(sections_)) {
    return nullptr;
  }

  return *it_section;
}

const SegmentCommand* Binary::segment_from_virtual_address(uint64_t virtual_address) const {
  auto it_segment = std::find_if(
      std::begin(segments_), std::end(segments_),
      [virtual_address] (const SegmentCommand* segment) {
        return segment->virtual_address() <= virtual_address &&
               virtual_address < (segment->virtual_address() + segment->virtual_size());
      });

  if (it_segment == std::end(segments_)) {
    return nullptr;
  }

  return *it_segment;
}

size_t Binary::segment_index(const SegmentCommand& segment) const {
  return segment.index();
}

const SegmentCommand* Binary::segment_from_offset(uint64_t offset) const {
  if (offset_seg_.empty()) {
    return nullptr;
  }

  const auto it_begin = std::begin(offset_seg_);
  if (offset < it_begin->first) {
    return nullptr;
  }

  auto it = offset_seg_.lower_bound(offset);
  if (it != std::end(offset_seg_) && (it->first == offset || it == it_begin)) {
    SegmentCommand* seg = it->second;
    if (seg->file_offset() <= offset && offset < (seg->file_offset() + seg->file_size())) {
      return seg;
    }
  }

  const auto it_end = offset_seg_.crbegin();
  if (it == std::end(offset_seg_) && offset >= it_end->first) {
    SegmentCommand* seg = it_end->second;
    if (seg->file_offset() <= offset && offset < (seg->file_offset() + seg->file_size())) {
      return seg;
    }
  }

  if (it == it_begin) {
    /* It did not pass the first check */
    return nullptr;
  }

  --it;

  SegmentCommand* seg = it->second;
  if (seg->file_offset() <= offset && offset < (seg->file_offset() + seg->file_size())) {
    return seg;
  }
  return nullptr;
}

ok_error_t Binary::shift_linkedit(size_t width) {
  SegmentCommand* linkedit = get_segment("__LINKEDIT");
  if (linkedit == nullptr) {
    LIEF_INFO("Can't find __LINKEDIT");
    return make_error_code(lief_errors::not_found);
  }
  const uint64_t lnk_offset = linkedit->file_offset();
  /* const uint64_t lnk_size   = linkedit->file_size(); */
  /* const uint64_t lnk_end    = lnk_offset + lnk_size; */

  if (SymbolCommand* sym_cmd = symbol_command()) {
    if (lnk_offset <= sym_cmd->symbol_offset()) {
      sym_cmd->symbol_offset(sym_cmd->symbol_offset() + width);
    }

    if (lnk_offset <= sym_cmd->strings_offset()) {
      sym_cmd->strings_offset(sym_cmd->strings_offset() + width);
    }
  }


  if (DataInCode* data_code_cmd = data_in_code()) {
    if (lnk_offset <= data_code_cmd->data_offset()) {
      data_code_cmd->data_offset(data_code_cmd->data_offset() + width);
    }
  }

  if (CodeSignature* sig = code_signature()) {
    if (lnk_offset <= sig->data_offset()) {
      sig->data_offset(sig->data_offset() + width);
    }
  }

  if (CodeSignatureDir* sig_dir = code_signature_dir()) {
    if (lnk_offset <= sig_dir->data_offset()) {
      sig_dir->data_offset(sig_dir->data_offset() + width);
    }
  }

  if (SegmentSplitInfo* ssi = segment_split_info()) {
    if (lnk_offset <= ssi->data_offset()) {
      ssi->data_offset(ssi->data_offset() + width);
    }
  }

  if (FunctionStarts* fs = function_starts()) {
    if (lnk_offset <= fs->data_offset()) {
      fs->data_offset(fs->data_offset() + width);
    }
  }

  if (DynamicSymbolCommand* dyn_cmd = dynamic_symbol_command()) {
    if (lnk_offset <= dyn_cmd->toc_offset()) {
      dyn_cmd->toc_offset(dyn_cmd->toc_offset() + width);
    }

    if (lnk_offset <= dyn_cmd->module_table_offset()) {
      dyn_cmd->module_table_offset(dyn_cmd->module_table_offset() + width);
    }

    if (lnk_offset <= dyn_cmd->external_reference_symbol_offset()) {
      dyn_cmd->external_reference_symbol_offset(dyn_cmd->external_reference_symbol_offset() + width);
    }

    if (lnk_offset <= dyn_cmd->indirect_symbol_offset()) {
      dyn_cmd->indirect_symbol_offset(dyn_cmd->indirect_symbol_offset() + width);
    }

    if (lnk_offset <= dyn_cmd->external_relocation_offset()) {
      dyn_cmd->external_relocation_offset(dyn_cmd->external_relocation_offset() + width);
    }

    if (lnk_offset <= dyn_cmd->local_relocation_offset()) {
      dyn_cmd->local_relocation_offset(dyn_cmd->local_relocation_offset() + width);
    }
  }

  if (DyldInfo* dyld = dyld_info()) {
    if (lnk_offset <= dyld->rebase().first) {
      dyld->set_rebase_offset(dyld->rebase().first + width);
    }

    if (lnk_offset <= dyld->bind().first) {
      dyld->set_bind_offset(dyld->bind().first + width);
    }

    if (lnk_offset <= dyld->weak_bind().first) {
      dyld->set_weak_bind_offset(dyld->weak_bind().first + width);
    }

    if (lnk_offset <= dyld->lazy_bind().first) {
      dyld->set_lazy_bind_offset(dyld->lazy_bind().first + width);
    }

    if (lnk_offset <= dyld->export_info().first) {
      dyld->set_export_offset(dyld->export_info().first + width);
    }
  }

  if (DyldChainedFixups* fixups = dyld_chained_fixups()) {
    fixups->data_offset(fixups->data_offset() + width);
  }

  if (DyldExportsTrie* exports = dyld_exports_trie()) {
    exports->data_offset(exports->data_offset() + width);
  }

  if (LinkerOptHint* opt = linker_opt_hint()) {
    opt->data_offset(opt->data_offset() + width);
  }

  if (TwoLevelHints* two = two_level_hints()) {
    two->offset(two->offset() + width);
  }

  linkedit->file_offset(linkedit->file_offset() + width);
  linkedit->virtual_address(linkedit->virtual_address() + width);
  for (const std::unique_ptr<Section>& section : linkedit->sections_) {
    if (lnk_offset <= section->offset()) {
      section->offset(section->offset() + width);
      section->virtual_address(section->virtual_address() + width);
    }
  }
  refresh_seg_offset();
  return ok();
}

uint32_t Binary::page_size() const {
  return get_pagesize(*this);
}

void Binary::sort_segments() {
  commands_t::iterator start = commands_.end();
  commands_t::iterator end = commands_.end();

  for (auto it = commands_.begin(); it != commands_.end(); ++it) {
    if (start == commands_.end() && SegmentCommand::classof(it->get())) {
      start = it;
    }

    if (SegmentCommand::classof(it->get())) {
      end = it;
    }
  }
  ++end;

  bool all_segments = std::all_of(start, end, [] (const std::unique_ptr<LoadCommand>& cmd) {
    return SegmentCommand::classof(cmd.get());
  });

  if (!all_segments) {
    LIEF_ERR("Segment commands non contiguous. Sort aborted!");
    return;
  }

  std::sort(start, end,
    [] (const std::unique_ptr<LoadCommand>& lhs, const std::unique_ptr<LoadCommand>& rhs) {
      return lhs->as<SegmentCommand>()->virtual_address() < rhs->as<SegmentCommand>()->virtual_address();
    }
  );

  segments_.clear();
  offset_seg_.clear();

  for (auto it = start; it != end; ++it) {
    SegmentCommand& seg = *(*it)->as<SegmentCommand>();
    seg.index_ = segments_.size();
    if (can_cache_segment(seg)) {
      offset_seg_[seg.file_offset()] = &seg;
    }
    segments_.push_back(&seg);
  }
}

void Binary::shift_command(size_t width, uint64_t from_offset) {
  const SegmentCommand* segment = segment_from_offset(from_offset);

  uint64_t __text_base_addr = 0;
  uint64_t virtual_address = 0;

  if (segment != nullptr) {
    virtual_address = segment->virtual_address() + from_offset - segment->file_offset();
  }

  if (const SegmentCommand* text = get_segment("__TEXT")) {
    __text_base_addr = text->virtual_address();
  }


  // Shift symbols command
  // =====================
  if (SymbolCommand* sym_cmd = symbol_command()) {

    if (sym_cmd->symbol_offset() > from_offset) {
      sym_cmd->symbol_offset(sym_cmd->symbol_offset() + width);
    }

    if (sym_cmd->strings_offset() > from_offset) {
      sym_cmd->strings_offset(sym_cmd->strings_offset() + width);
    }

    for (std::unique_ptr<Symbol>& s : symbols_) {
      if (s->type() == Symbol::TYPE::SECTION) {
        if (s->value() > virtual_address) {
          s->value(s->value() + width);
        }
      }
    }
  }

  // Data In Code
  // ============
  if (DataInCode* data_code_cmd = data_in_code()) {
    if (data_code_cmd->data_offset() > from_offset) {
      data_code_cmd->data_offset(data_code_cmd->data_offset() + width);
    }
  }


  // Code Signature
  // ==============
  if (CodeSignature* sig = code_signature()) {
    if (sig->data_offset() > from_offset) {
      sig->data_offset(sig->data_offset() + width);
    }
  }

  if (CodeSignatureDir* sig_dir = code_signature_dir()) {
    if (sig_dir->data_offset() > from_offset) {
      sig_dir->data_offset(sig_dir->data_offset() + width);
    }
  }

  if (SegmentSplitInfo* ssi = segment_split_info()) {
    if (ssi->data_offset() > from_offset) {
      ssi->data_offset(ssi->data_offset() + width);
    }
  }

  // Shift Main Command
  // ==================
  if (MainCommand* main_cmd = main_command()) {
     if ((__text_base_addr + main_cmd->entrypoint()) > virtual_address) {
      main_cmd->entrypoint(main_cmd->entrypoint() + width);
    }
  }

  // Patch function starts
  // =====================
  if (FunctionStarts* fs = function_starts()) {
    if (fs->data_offset() > from_offset) {
      fs->data_offset(fs->data_offset() + width);
    }
    for (uint64_t& address : fs->functions()) {
      if ((__text_base_addr + address) > virtual_address) {
        address += width;
      }
    }
  }

  // Dynamic symbol command
  // ======================
  if (DynamicSymbolCommand* dyn_cmd = dynamic_symbol_command()) {
    if (dyn_cmd->toc_offset() > from_offset) {
      dyn_cmd->toc_offset(dyn_cmd->toc_offset() + width);
    }

    if (dyn_cmd->module_table_offset() > from_offset) {
      dyn_cmd->module_table_offset(dyn_cmd->module_table_offset() + width);
    }

    if (dyn_cmd->external_reference_symbol_offset() > from_offset) {
      dyn_cmd->external_reference_symbol_offset(dyn_cmd->external_reference_symbol_offset() + width);
    }

    if (dyn_cmd->indirect_symbol_offset() > from_offset) {
      dyn_cmd->indirect_symbol_offset(dyn_cmd->indirect_symbol_offset() + width);
    }

    if (dyn_cmd->external_relocation_offset() > from_offset) {
      dyn_cmd->external_relocation_offset(dyn_cmd->external_relocation_offset() + width);
    }

    if (dyn_cmd->local_relocation_offset() > from_offset) {
      dyn_cmd->local_relocation_offset(dyn_cmd->local_relocation_offset() + width);
    }
  }

  // Patch Dyld
  // ==========
  if (DyldInfo* dyld = dyld_info()) {

    // Shift underlying containers offset
    if (dyld->rebase().first > from_offset) {
      dyld->set_rebase_offset(dyld->rebase().first + width);
    }

    if (dyld->bind().first > from_offset) {
      dyld->set_bind_offset(dyld->bind().first + width);
    }

    if (dyld->weak_bind().first > from_offset) {
      dyld->set_weak_bind_offset(dyld->weak_bind().first + width);
    }

    if (dyld->lazy_bind().first > from_offset) {
      dyld->set_lazy_bind_offset(dyld->lazy_bind().first + width);
    }

    if (dyld->export_info().first > from_offset) {
      dyld->set_export_offset(dyld->export_info().first + width);
    }


    // Shift Relocations
    // -----------------
    // TODO: Optimize this code
    for (Relocation& reloc : relocations()) {
      if (reloc.address() > virtual_address) {
        if (is64_) {
          patch_relocation<uint64_t>(reloc, /* from */ virtual_address, /* shift */ width);
        } else {
          patch_relocation<uint32_t>(reloc, /* from */ virtual_address, /* shift */ width);
        }
        reloc.address(reloc.address() + width);
      }
    }

    // Shift Export Info
    // -----------------
    for (ExportInfo& info : dyld->exports()) {
      if (info.address() > from_offset) {
        info.address(info.address() + width);
      }
    }

    // Shift bindings
    // --------------
    for (DyldBindingInfo& info : dyld->bindings()) {
      if (info.address() > virtual_address) {
        info.address(info.address() + width);
      }
    }
  }

  if (DyldChainedFixups* fixups = dyld_chained_fixups()) {
    fixups->data_offset(fixups->data_offset() + width);

    // Update relocations
    for (auto& entry : fixups->chained_starts_in_segments()) {
      for (auto& reloc : entry.segment.relocations()) {
        if (auto* fixup = reloc.cast<RelocationFixup>()) {
          if (fixup->offset() > from_offset) {
            fixup->offset(fixup->offset() + width);
          }
          if (fixup->target() > virtual_address) {
            fixup->target(fixup->target() + width);
          }
          // No need to update the virtual address since
          // it is bound to the offset
        }
      }
    }
    for (ChainedBindingInfo& bind : fixups->bindings()) {
      if (bind.offset() > from_offset) {
        bind.offset(bind.offset() + width);
      }

      if (bind.address() > virtual_address) {
        bind.address(bind.address() + width);
      }
      // We don't need to update the virtual address,
      // as it is bound to the offset
    }
  }

  if (DyldExportsTrie* exports = dyld_exports_trie()) {
    for (ExportInfo& info : exports->exports()) {
      if (info.address() > from_offset) {
        info.address(info.address() + width);
      }
    }
    if (exports->data_offset() > from_offset) {
      exports->data_offset(exports->data_offset() + width);
    }
  }

  if (LinkerOptHint* opt = linker_opt_hint()) {
    if (opt->data_offset() > from_offset) {
      opt->data_offset(opt->data_offset() + width);
    }
  }

  if (TwoLevelHints* two = two_level_hints()) {
    if (two->offset() > from_offset) {
      two->offset(two->offset() + width);
    }
  }

  if (Routine* routine = routine_command()) {
    if (routine->init_address() > virtual_address) {
      routine->init_address(routine->init_address() + width);
    }
  }

  for_commands<EncryptionInfo>([from_offset, width] (EncryptionInfo& enc) {
    if (enc.crypt_offset() > from_offset) {
      enc.crypt_offset(enc.crypt_offset() + width);
    }
  });

}


ok_error_t Binary::shift(size_t value) {
  Header& header = this->header();

  // Offset of the load commands table
  const uint64_t loadcommands_start = is64_ ? sizeof(details::mach_header_64) :
                                              sizeof(details::mach_header);

  // +------------------------+ <---------- __TEXT.start
  // |      Mach-O Header     |
  // +------------------------+ <===== loadcommands_start
  // |                        |
  // | Load Command Table     |
  // |                        |
  // +------------------------+ <===== loadcommands_end
  // |************************|
  // |************************| Assembly code
  // |************************|
  // +------------------------+ <---------- __TEXT.end
  const uint64_t loadcommands_end = loadcommands_start + header.sizeof_cmds();

  // Segment that wraps this load command table
  SegmentCommand* load_cmd_segment = segment_from_offset(loadcommands_end);
  if (load_cmd_segment == nullptr) {
    LIEF_WARN("Can't find segment associated with last load command");
    return make_error_code(lief_errors::file_format_error);
  }
  LIEF_DEBUG("LC Table wrapped by {} / End offset: 0x{:x} (size: {:x})",
             load_cmd_segment->name(), loadcommands_end, load_cmd_segment->data_.size());
  load_cmd_segment->content_insert(loadcommands_end, value);

  // 1. Shift all commands
  // =====================
  for (std::unique_ptr<LoadCommand>& cmd : commands_) {
    if (cmd->command_offset() >= loadcommands_end) {
      cmd->command_offset(cmd->command_offset() + value);
    }
  }

  shift_command(value, loadcommands_end);
  const uint64_t loadcommands_end_va = loadcommands_end + load_cmd_segment->virtual_address();

  LIEF_DEBUG("loadcommands_end:    0x{:016x}", loadcommands_end);
  LIEF_DEBUG("loadcommands_end_va: 0x{:016x}", loadcommands_end_va);

  // Shift Segment and sections
  // ==========================
  for (SegmentCommand* segment : segments_) {
    // Extend the virtual size of the segment containing our shift
    if (segment->file_offset() <= loadcommands_end &&
        loadcommands_end < (segment->file_offset() + segment->file_size()))
    {
      LIEF_DEBUG("Extending '{}' by {:x}", segment->name(), value);
      segment->virtual_size(segment->virtual_size() + value);
      segment->file_size(segment->file_size() + value);

      for (const std::unique_ptr<Section>& section : segment->sections_) {
        if (section->offset() >= loadcommands_end) {
          section->offset(section->offset() + value);
          section->virtual_address(section->virtual_address() + value);
        }
      }
    } else {
      if (segment->virtual_address() >= loadcommands_end_va) {
        segment->virtual_address(segment->virtual_address() + value);
      }

      if (segment->file_offset() >= loadcommands_end) {
        segment->file_offset(segment->file_offset() + value);
      }

      for (const std::unique_ptr<Section>& section : segment->sections_) {
        if (section->virtual_address() >= loadcommands_end_va) {
          section->virtual_address(section->virtual_address() + value);
        }
        if (section->offset() >= loadcommands_end) {
          section->offset(section->offset() + value);
        }
      }
    }
  }
  refresh_seg_offset();
  return ok();
}

LoadCommand* Binary::add(std::unique_ptr<LoadCommand> command) {
  static constexpr uint32_t shift_value = 0x4000;
  const int32_t size_aligned = align(command->size(), pointer_size());

  // Check there is enough spaces between the load command table
  // and the raw content
  if (available_command_space_ < size_aligned) {
    if (!shift(shift_value)) {
      return nullptr;
    }
    available_command_space_ += shift_value;
    return add(std::move(command));
  }

  available_command_space_ -= size_aligned;

  Header& header = this->header();

  // Get border of the load command table
  const uint64_t loadcommands_start = is64_ ? sizeof(details::mach_header_64) :
                                              sizeof(details::mach_header);
  const uint64_t loadcommands_end = loadcommands_start + header.sizeof_cmds();

  // Update the Header according to the command that will be added
  header.sizeof_cmds(header.sizeof_cmds() + size_aligned);
  header.nb_cmds(header.nb_cmds() + 1);

  // Get the segment handling the LC table
  SegmentCommand* load_cmd_segment = segment_from_offset(loadcommands_end);
  if (load_cmd_segment == nullptr) {
    LIEF_WARN("Can't get the last load command");
    return nullptr;
  }

  span<const uint8_t> content_ref = load_cmd_segment->content();
  std::vector<uint8_t> content = {std::begin(content_ref), std::end(content_ref)};

  // Copy the command data
  std::copy(std::begin(command->data()), std::end(command->data()),
            std::begin(content) + loadcommands_end);

  load_cmd_segment->content(std::move(content));

  // Add the command in the Binary
  command->command_offset(loadcommands_end);


  // Update cache
  if (DylibCommand::classof(command.get())) {
    libraries_.push_back(command->as<DylibCommand>());
  }

  if (SegmentCommand::classof(command.get())) {
    add_cached_segment(*command->as<SegmentCommand>());
  }
  LoadCommand* ptr = command.get();
  commands_.push_back(std::move(command));
  return ptr;
}

LoadCommand* Binary::add(const LoadCommand& command, size_t index) {
  static constexpr uint32_t shift_value = 0x4000;

  // If index is "too" large <=> push_back
  if (index >= commands_.size()) {
    return add(command);
  }

  int32_t size_aligned = align(command.size(), pointer_size());
  LIEF_DEBUG("available_command_space_: 0x{:06x} (required: 0x{:06x})",
             available_command_space_, size_aligned);

  // Check that we have enough space
  if (available_command_space_ <= size_aligned) {
    shift(shift_value);
    available_command_space_ += shift_value;
    return add(command, index);
  }
  LIEF_DEBUG("No need to shift");

  available_command_space_ -= size_aligned;


  // Update the Header according to the new command
  Header& header = this->header();

  header.sizeof_cmds(header.sizeof_cmds() + size_aligned);
  header.nb_cmds(header.nb_cmds() + 1);

  // Get offset of the LC border
  LoadCommand* cmd_border = commands_[index].get();
  uint64_t border_off = cmd_border->command_offset();

  std::unique_ptr<LoadCommand> copy{command.clone()};
  copy->command_offset(cmd_border->command_offset());

  // Patch LC offsets that follow the LC border
  for (std::unique_ptr<LoadCommand>& lc : commands_) {
    if (lc->command_offset() >= border_off) {
      lc->command_offset(lc->command_offset() + size_aligned);
    }
  }

  if (auto* lib = copy->cast<DylibCommand>()) {
    libraries_.push_back(lib);
  }

  if (auto* segment = copy->cast<SegmentCommand>()) {
    add_cached_segment(*segment);
  }
  LoadCommand* copy_ptr = copy.get();
  commands_.insert(std::begin(commands_) + index, std::move(copy));
  return copy_ptr;
}

bool Binary::remove(const LoadCommand& command) {

  const auto it = std::find_if(
      std::begin(commands_), std::end(commands_),
      [&command] (const std::unique_ptr<LoadCommand>& cmd) {
        return *cmd == command;
      });

  if (it == std::end(commands_)) {
    LIEF_ERR("Unable to find command: {}", to_string(command));
    return false;
  }

  LoadCommand* cmd_rm = it->get();

  if (auto* lib = cmd_rm->cast<DylibCommand>()) {
    auto it_cache = std::find(std::begin(libraries_), std::end(libraries_), cmd_rm);
    if (it_cache == std::end(libraries_)) {
      LIEF_WARN("Library {} not found in cache. The binary object is likely in an inconsistent state", lib->name());
    } else {
      libraries_.erase(it_cache);
    }
  }

  if (const auto* seg = cmd_rm->cast<const SegmentCommand>()) {
    auto it_cache = std::find(std::begin(segments_), std::end(segments_), cmd_rm);
    if (it_cache == std::end(segments_)) {
      LIEF_WARN("Segment {} not found in cache. The binary object is likely in an inconsistent state", seg->name());
    } else {
      // Update the indexes to keep a consistent state
      for (auto it = it_cache; it != std::end(segments_); ++it) {
        (*it)->index_--;
      }
      segments_.erase(it_cache);
    }
  }

  const uint64_t cmd_rm_offset = cmd_rm->command_offset();
  for (std::unique_ptr<LoadCommand>& cmd : commands_) {
    if (cmd->command_offset() >= cmd_rm_offset) {
      cmd->command_offset(cmd->command_offset() - cmd_rm->size());
    }
  }


  Header& header = this->header();
  header.sizeof_cmds(header.sizeof_cmds() - cmd_rm->size());
  header.nb_cmds(header.nb_cmds() - 1);
  available_command_space_ += cmd_rm->size();

  commands_.erase(it);
  refresh_seg_offset();
  return true;
}


bool Binary::remove(LoadCommand::TYPE type) {
  bool removed = false;

  while (LoadCommand* cmd = get(type)) {
    removed = remove(*cmd);
  }
  return removed;
}

bool Binary::remove_command(size_t index) {
  if (index >= commands_.size()) {
    return false;
  }
  return remove(*commands_[index]);
}

bool Binary::has(LoadCommand::TYPE type) const {
  const auto it = std::find_if(
      std::begin(commands_), std::end(commands_),
      [type] (const std::unique_ptr<LoadCommand>& cmd) {
        return cmd->command() == type;
      });
  return it != std::end(commands_);
}

const LoadCommand* Binary::get(LoadCommand::TYPE type) const {
  const auto it = std::find_if(
      std::begin(commands_), std::end(commands_),
      [type] (const std::unique_ptr<LoadCommand>& cmd) {
        return cmd->command() == type;
      });

  if (it == std::end(commands_)) {
    return nullptr;
  }
  return it->get();
}

bool Binary::extend(const LoadCommand& command, uint64_t size) {
  static constexpr uint32_t shift_value = 0x10000;

  const auto it = std::find_if(
      std::begin(commands_), std::end(commands_),
      [&command] (const std::unique_ptr<LoadCommand>& cmd) {
        return *cmd == command;
      });

  if (it == std::end(commands_)) {
    LIEF_ERR("Unable to find command: {}", to_string(command));
    return false;
  }

  LoadCommand* cmd = it->get();
  const int32_t size_aligned = align(cmd->size() + size, pointer_size());
  const uint32_t extension = size_aligned - cmd->size();
  if (available_command_space_ < size_aligned) {
    shift(shift_value);
    available_command_space_ += shift_value;
    return extend(command, size);
  }

  for (std::unique_ptr<LoadCommand>& lc : commands_) {
    if (lc->command_offset() > cmd->command_offset()) {
      lc->command_offset(lc->command_offset() + extension);
    }
  }

  cmd->size(size_aligned);
  cmd->original_data_.resize(size_aligned);

  // Update Header
  // =============
  Header& header = this->header();
  header.sizeof_cmds(header.sizeof_cmds() + extension);

  return true;
}


bool Binary::extend_segment(const SegmentCommand& segment, size_t size) {

  const auto it_segment = std::find_if(
      std::begin(segments_), std::end(segments_),
      [&segment] (const SegmentCommand* s) {
        return segment == *s;
      });

  if (it_segment == std::end(segments_)) {
    LIEF_ERR("Unable to find segment: '{}'", segment.name());
    return false;
  }

  SegmentCommand* target_segment = *it_segment;
  const uint64_t last_offset = target_segment->file_offset() + target_segment->file_size();
  const uint64_t last_va     = target_segment->virtual_address() + target_segment->virtual_size();

  const int32_t size_aligned = align(size, pointer_size());

  shift_command(size_aligned, last_offset - 4);

  // Shift Segment and sections
  // ==========================
  for (SegmentCommand* segment : segments_) {
    if (segment->virtual_address() >= last_va) {
      segment->virtual_address(segment->virtual_address() + size_aligned);
    }
    if (segment->file_offset() >= last_offset) {
      segment->file_offset(segment->file_offset() + size_aligned);
    }

    for (const std::unique_ptr<Section>& section : segment->sections_) {
      if (section->virtual_address() >= last_va) {
        section->virtual_address(section->virtual_address() + size_aligned);
      }
      if (section->offset() >= last_offset) {
        section->offset(section->offset() + size_aligned);
      }
    }
  }


  target_segment->virtual_size(target_segment->virtual_size() + size_aligned);
  target_segment->file_size(target_segment->file_size() + size_aligned);
  target_segment->content_resize(target_segment->file_size());
  refresh_seg_offset();
  return true;
}

void Binary::remove_section(const std::string& name, bool clear) {
  Section* sec_to_delete = get_section(name);
  if (sec_to_delete == nullptr) {
    LIEF_ERR("Can't find section '{}'", name);
    return;
  }
  SegmentCommand* segment = sec_to_delete->segment();
  if (segment == nullptr) {
    LIEF_ERR("The section {} is in an inconsistent state (missing segment). Can't remove it",
             sec_to_delete->name());
    return;
  }

  remove_section(segment->name(), name, clear);
}

void Binary::remove_section(const std::string& segname, const std::string& secname, bool clear) {
  Section* sec_to_delete = get_section(segname, secname);
  if (sec_to_delete == nullptr) {
    LIEF_ERR("Can't find section '{}' in segment '{}'", secname, segname);
    return;
  }
  SegmentCommand* segment = sec_to_delete->segment();
  if (segment == nullptr) {
    LIEF_ERR("The section {} is in an inconsistent state (missing segment). Can't remove it",
             sec_to_delete->name());
    return;
  }

  if (clear) {
    sec_to_delete->clear(0);
  }

  segment->numberof_sections(segment->numberof_sections() - 1);
  auto it_section = std::find_if(
      std::begin(segment->sections_), std::end(segment->sections_),
      [sec_to_delete] (const std::unique_ptr<Section>& s) {
        return *s == *sec_to_delete;
      });

  if (it_section == std::end(segment->sections_)) {
    LIEF_WARN("Can't find the section");
    return;
  }

  const size_t lc_offset = segment->command_offset();
  const size_t section_struct_size = is64_ ? sizeof(details::section_64) :
                                             sizeof(details::section_32);
  segment->size_ -= section_struct_size;

  header().sizeof_cmds(header().sizeof_cmds() - section_struct_size);

  for (std::unique_ptr<LoadCommand>& lc : commands_) {
    if (lc->command_offset() > lc_offset) {
      lc->command_offset(lc->command_offset() - section_struct_size);
    }
  }

  available_command_space_ += section_struct_size;

  std::unique_ptr<Section>& section = *it_section;
  // Remove from cache
  auto it_cache = std::find_if(std::begin(sections_), std::end(sections_),
      [&section] (const Section* sec) {
        return section.get() == sec;
      });

  if (it_cache == std::end(sections_)) {
    LIEF_WARN("Can find the section {} in the cache. The binary object is likely in an inconsistent state",
              section->name());
  } else {
    sections_.erase(it_cache);
  }

  segment->sections_.erase(it_section);
}

Section* Binary::add_section(const Section& section) {
  SegmentCommand* _TEXT_segment = get_segment("__TEXT");
  if (_TEXT_segment == nullptr) {
    LIEF_ERR("Unable to get '__TEXT' segment");
    return nullptr;
  }
  return add_section(*_TEXT_segment, section);
}


Section* Binary::add_section(const SegmentCommand& segment, const Section& section) {

  const auto it_segment = std::find_if(
      std::begin(segments_), std::end(segments_),
      [&segment] (const SegmentCommand* s) {
        return segment == *s;
      });

  if (it_segment == std::end(segments_)) {
    LIEF_ERR("Unable to find segment: '{}'", segment.name());
    return nullptr;
  }
  SegmentCommand* target_segment = *it_segment;

  span<const uint8_t> content_ref = section.content();
  Section::content_t content = {std::begin(content_ref), std::end(content_ref)};

  const size_t sec_size = is64_ ? sizeof(details::section_64) :
                                  sizeof(details::section_32);
  const size_t data_size = content.size();
  const int32_t needed_size = align(sec_size + data_size, page_size());
  if (available_command_space_ < needed_size) {
    shift(needed_size);
    available_command_space_ += needed_size;
    return add_section(segment, section);
  }

  if (!extend(*target_segment, sec_size)) {
    LIEF_ERR("Unable to extend segment '{}' by 0x{:x}", segment.name(), sec_size);
    return nullptr;
  }

  available_command_space_ -= needed_size;

  auto new_section = std::make_unique<Section>(section);
  // Compute offset, virtual address etc for the new section
  // =======================================================

  // Section raw data will be located just after commands table
  if (section.offset() == 0) {
    uint64_t new_offset = is64_ ? sizeof(details::mach_header_64) :
                                  sizeof(details::mach_header);
    new_offset += header().sizeof_cmds();
    new_offset += available_command_space_;
    new_section->offset(new_offset);
  }

  if (section.size() == 0) {
    new_section->size(data_size);
  }

  if (section.virtual_address() == 0) {
    new_section->virtual_address(target_segment->virtual_address() + new_section->offset());
  }

  new_section->segment_ = target_segment;
  target_segment->numberof_sections(target_segment->numberof_sections() + 1);

  // Copy the new section in the cache
  sections_.push_back(new_section.get());

  // Copy data to segment
  const uint64_t relative_offset = new_section->offset() - target_segment->file_offset();

  std::move(std::begin(content), std::end(content),
            std::begin(target_segment->data_) + relative_offset);

  target_segment->sections_.push_back(std::move(new_section));
  return target_segment->sections_.back().get();
}


LoadCommand* Binary::add(const SegmentCommand& segment) {
  /*
   * To add a new segment in a Mach-O file, we need to:
   *
   * 1. Allocate space for a new Load command: LC_SEGMENT_64 / LC_SEGMENT
   *    which must include the sections
   * 2. Allocate space for the content of the provided segment
   *
   * For #1, the logic is to shift all the content after the end of the load command table.
   * This modification is described in doc/sphinx/tutorials/11_macho_modification.rst.
   *
   * For #2, the easiest way is to place the content at the end of the Mach-O file and
   * to make the LC_SEGMENT point to this area. It works as expected as long as
   * the binary does not need to be signed.
   *
   * If the binary has to be signed, codesign and the underlying Apple libraries
   * enforce that there is not data after the __LINKEDIT segment, otherwise we get
   * this kind of error: "main executable failed strict validation".
   * To comply with this check, we can shift the __LINKEDIT segment (c.f. ``shift_linkedit(...)``)
   * such as the data of the new segment are located before __LINKEDIT.
   * Nevertheless, we can't shift __LINKEDIT by an arbitrary value. For ARM and ARM64,
   * ld/dyld enforces a segment alignment of "4 * 4096" as coded in ``Options::reconfigureDefaults``
   * of ``ld64-609/src/ld/Option.cpp``:
   *
   * ```cpp
   * ...
   * <rdar://problem/13070042> Only third party apps should have 16KB page segments by default
   * if (fEncryptable) {
   *  if (fSegmentAlignment == 4096)
   *    fSegmentAlignment = 4096*4;
   * }
   *
   * // <rdar://problem/12258065> ARM64 needs 16KB page size for user land code
   * // <rdar://problem/15974532> make armv7[s] use 16KB pages in user land code for iOS 8 or later
   * if (fArchitecture == CPU_TYPE_ARM64 || (fArchitecture == CPU_TYPE_ARM) ) {
   *   fSegmentAlignment = 4096*4;
   * }
   * ```
   * Therefore, we must shift __LINKEDIT by at least 4 * 0x1000 for Mach-O files targeting ARM
   */
  LIEF_DEBUG("Adding the new segment '{}' ({} bytes)", segment.name(), segment.content().size());
  const uint32_t alignment = page_size();
  const uint64_t new_fsize = align(segment.content().size(), alignment);
  SegmentCommand new_segment = segment;

  if (new_segment.file_size() == 0) {
    new_segment.file_size(new_fsize);
    new_segment.content_resize(new_fsize);
  }

  if (new_segment.virtual_size() == 0) {
    const uint64_t new_size = align(new_segment.file_size(), alignment);
    new_segment.virtual_size(new_size);
  }

  if (segment.sections().size() > 0) {
    new_segment.nb_sections_ = segment.sections().size();
  }

  if (is64_) {
    new_segment.command(LoadCommand::TYPE::SEGMENT_64);
    size_t needed_size = sizeof(details::segment_command_64);
    needed_size += new_segment.numberof_sections() * sizeof(details::section_64);
    new_segment.size(needed_size);
  } else {
    new_segment.command(LoadCommand::TYPE::SEGMENT);
    size_t needed_size = sizeof(details::segment_command_32);
    needed_size += new_segment.numberof_sections() * sizeof(details::section_32);
    new_segment.size(needed_size);
  }

  LIEF_DEBUG(" -> sizeof(LC_SEGMENT): {}", new_segment.size());

  // Insert the segment before __LINKEDIT
  const auto it_linkedit = std::find_if(std::begin(commands_), std::end(commands_),
      [] (const std::unique_ptr<LoadCommand>& cmd) {
        if (!SegmentCommand::classof(cmd.get())) {
          return false;
        }
        return cmd->as<SegmentCommand>()->name() == "__LINKEDIT";
      });

  const bool has_linkedit = it_linkedit != std::end(commands_);

  size_t pos = std::distance(std::begin(commands_), it_linkedit);

  LIEF_DEBUG(" -> index: {}", pos);

  auto* new_cmd = add(new_segment, pos);

  if (new_cmd == nullptr) {
    LIEF_WARN("Fail to insert new '{}' segment", segment.name());
    return nullptr;
  }

  auto* segment_added = new_cmd->as<SegmentCommand>();

  if (!has_linkedit) {
    /* If there are not __LINKEDIT segment we can point the Segment's content to the EOF
     * NOTE(romain): I don't know if a binary without a __LINKEDIT segment exists
     */
    range_t new_va_ranges  = this->va_ranges();
    range_t new_off_ranges = off_ranges();
    if (segment.virtual_address() == 0 && segment_added->virtual_size() != 0) {
      const uint64_t new_va = align(new_va_ranges.end, alignment);
      segment_added->virtual_address(new_va);
      uint64_t current_va = segment_added->virtual_address();
      for (Section& section : segment_added->sections()) {
        section.virtual_address(current_va);
        current_va += section.size();
      }
    }

    if (segment.file_offset() == 0 && segment_added->virtual_size() != 0) {
      const uint64_t new_offset = align(new_off_ranges.end, alignment);
      segment_added->file_offset(new_offset);
      uint64_t current_offset = new_offset;
      for (Section& section : segment_added->sections()) {
        section.offset(current_offset);
        current_offset += section.size();
      }
    }
    refresh_seg_offset();
    return segment_added;
  }

  uint64_t lnk_offset = 0;
  uint64_t lnk_va     = 0;

  if (const SegmentCommand* lnk = get_segment("__LINKEDIT")) {
    lnk_offset = lnk->file_offset();
    lnk_va     = lnk->virtual_address();
  }

  // Make space for the content of the new segment
  shift_linkedit(new_fsize);
  LIEF_DEBUG(" -> offset         : 0x{:06x}", lnk_offset);
  LIEF_DEBUG(" -> virtual address: 0x{:06x}", lnk_va);

  segment_added->virtual_address(lnk_va);
  segment_added->virtual_size(segment_added->virtual_size());
  uint64_t current_va = segment_added->virtual_address();
  for (Section& section : segment_added->sections()) {
    section.virtual_address(current_va);
    current_va += section.size();
  }

  segment_added->file_offset(lnk_offset);
  uint64_t current_offset = lnk_offset;
  for (Section& section : segment_added->sections()) {
    section.offset(current_offset);
    current_offset += section.size();
  }

  refresh_seg_offset();
  return segment_added;
}

size_t Binary::add_cached_segment(SegmentCommand& segment) {
  // The new segement should be put **before** the __LINKEDIT segment
  const auto it_linkedit = std::find_if(std::begin(segments_), std::end(segments_),
      [] (SegmentCommand* cmd) { return cmd->name() == "__LINKEDIT"; });

  if (it_linkedit == std::end(segments_)) {
    LIEF_DEBUG("No __LINKEDIT segment found!");
    segment.index_ = segments_.size();
    segments_.push_back(&segment);
  } else {
    segment.index_ = (*it_linkedit)->index();

    // Update indexes
    for (auto it = it_linkedit; it != std::end(segments_); ++it) {
      (*it)->index_++;
    }
    segments_.insert(it_linkedit, &segment);
  }

  offset_seg_[segment.file_offset()] = &segment;
  if (LinkEdit::segmentof(segment)) {
    auto& linkedit = static_cast<LinkEdit&>(segment);
    linkedit.dyld_           = dyld_info();
    linkedit.chained_fixups_ = dyld_chained_fixups();
  }
  refresh_seg_offset();
  return segment.index();
}

bool Binary::unexport(const std::string& name) {
  for (const std::unique_ptr<Symbol>& s : symbols_) {
    if (s->name() == name && s->has_export_info()) {
      return unexport(*s);
    }
  }
  return false;
}

bool Binary::unexport(const Symbol& sym) {
  if (DyldInfo* dyld = dyld_info()) {
    const auto it_export = std::find_if(
        std::begin(dyld->export_info_), std::end(dyld->export_info_),
        [&sym] (const std::unique_ptr<ExportInfo>& info) {
          return info->has_symbol() && *info->symbol() == sym;
        });

    // The symbol is not exported
    if (it_export == std::end(dyld->export_info_)) {
      return false;
    }

    dyld->export_info_.erase(it_export);
    return true;
  }


  if (DyldExportsTrie* exports = dyld_exports_trie()) {
    const auto it_export = std::find_if(
        std::begin(exports->export_info_), std::end(exports->export_info_),
        [&sym] (const std::unique_ptr<ExportInfo>& info) {
          return info->has_symbol() && *info->symbol() == sym;
        });

    // The symbol is not exported
    if (it_export == std::end(exports->export_info_)) {
      return false;
    }

    exports->export_info_.erase(it_export);
    return true;
  }

  LIEF_INFO("Can't find neither LC_DYLD_INFO / LC_DYLD_CHAINED_FIXUPS");
  return false;
}

bool Binary::remove(const Symbol& sym) {
  unexport(sym);
  const auto it_sym = std::find_if(std::begin(symbols_), std::end(symbols_),
      [&sym] (const std::unique_ptr<Symbol>& s) {
        return s.get() == &sym;
      });

  if (it_sym == std::end(symbols_)) {
    return false;
  }

  if (DynamicSymbolCommand* dyst = dynamic_symbol_command()) {
    dyst->indirect_symbols_.erase(
        std::remove_if(std::begin(dyst->indirect_symbols_), std::end(dyst->indirect_symbols_),
                       [&sym] (const Symbol* s) { return s == &sym; }),
        std::end(dyst->indirect_symbols_));
  }

  symbols_.erase(it_sym);
  return true;
}

bool Binary::remove_symbol(const std::string& name) {
  bool removed = false;
  while (const Symbol* s = get_symbol(name)) {
    if (!remove(*s)) {
      break;
    }
    removed = true;
  }
  return removed;
}


bool Binary::can_remove(const Symbol& sym) const {
  /*
   * We consider that a symbol can be removed, if and only if
   * there are no binding associated with
   */
  if (const DyldInfo* dyld = dyld_info()) {
    for (const DyldBindingInfo& binding : dyld->bindings()) {
      if (binding.has_symbol() && binding.symbol()->name() == sym.name()) {
        return false;
      }
    }
  }

  if (const DyldChainedFixups* fixups = dyld_chained_fixups()) {
    for (const ChainedBindingInfo& binding : fixups->bindings()) {
      if (binding.has_symbol() && binding.symbol()->name() == sym.name()) {
        return false;
      }
    }
  }

  return true;
}

bool Binary::can_remove_symbol(const std::string& name) const {
  std::vector<const Symbol*> syms;
  for (const std::unique_ptr<Symbol>& s : symbols_) {
    if (s->name() == name) {
      syms.push_back(s.get());
    }
  }
  return std::all_of(std::begin(syms), std::end(syms),
                     [this] (const Symbol* s) { return can_remove(*s); });
}


bool Binary::remove_signature() {
  if (const CodeSignature* cs = code_signature()) {
    return remove(*cs);
  }
  LIEF_WARN("No signature found");
  return false;
}

LoadCommand* Binary::add(const DylibCommand& library) {
  return add(*library.as<LoadCommand>());
}

LoadCommand* Binary::add_library(const std::string& name) {
  return add(DylibCommand::load_dylib(name));
}

std::vector<uint8_t> Binary::raw() {
  std::vector<uint8_t> buffer;
  Builder::write(*this, buffer);
  return buffer;
}

result<uint64_t> Binary::virtual_address_to_offset(uint64_t virtual_address) const {
  const SegmentCommand* segment = segment_from_virtual_address(virtual_address);
  if (segment == nullptr) {
    return make_error_code(lief_errors::conversion_error);
  }
  const uint64_t base_address = segment->virtual_address() - segment->file_offset();
  return virtual_address - base_address;
}

result<uint64_t> Binary::offset_to_virtual_address(uint64_t offset, uint64_t slide) const {
  const SegmentCommand* segment = segment_from_offset(offset);
  if (segment == nullptr) {
    return slide + offset;
  }
  const uint64_t delta = segment->virtual_address() - segment->file_offset();
  const uint64_t imgbase = imagebase();

  if (slide == 0) {
    return delta + offset;
  }

  if (imgbase == 0) {
    return slide +
           segment->virtual_address() + (offset - segment->file_offset());
  }
  return (segment->virtual_address() - imgbase) + slide + (offset - segment->file_offset());
}

bool Binary::disable_pie() {
  if (is_pie()) {
    header().remove(Header::FLAGS::PIE);
    return true;
  }
  return false;
}

const Section* Binary::get_section(const std::string& name) const {
  const auto it_section = std::find_if(
      std::begin(sections_), std::end(sections_),
      [&name] (const Section* sec) {
        return sec->name() == name;
      });

  if (it_section == std::end(sections_)) {
    return nullptr;
  }

  return *it_section;
}


const Section* Binary::get_section(const std::string& segname, const std::string& secname) const {
  if (const SegmentCommand* seg = get_segment(segname)) {
    if (const Section* sec = seg->get_section(secname)) {
      return sec;
    }
  }
  return nullptr;
}

const SegmentCommand* Binary::get_segment(const std::string& name) const {
  const auto it_segment = std::find_if(
      std::begin(segments_), std::end(segments_),
      [&name] (const SegmentCommand* seg) {
        return seg->name() == name;
      });

  if (it_segment == std::end(segments_)) {
    return nullptr;
  }

  return *it_segment;
}

uint64_t Binary::imagebase() const {
  if (const SegmentCommand* _TEXT = get_segment("__TEXT")) {
    return _TEXT->virtual_address();
  }
  return 0;
}

std::string Binary::loader() const {
  if (const DylinkerCommand* cmd = dylinker()) {
    return cmd->name();
  }
  return "";
}

Binary::range_t Binary::va_ranges() const {
  uint64_t min = uint64_t(-1);
  uint64_t max = 0;

  for (const SegmentCommand* segment : segments_) {
    if (segment->name_ == "__PAGEZERO") {
      continue;
    }
    min = std::min<uint64_t>(min, segment->virtual_address());
    max = std::max(max, segment->virtual_address() + segment->virtual_size());
  }

  if (min == uint64_t(-1)) {
    return {0, 0};
  }

  return {min, max};
}

Binary::range_t Binary::off_ranges() const {
  uint64_t min = uint64_t(-1);
  uint64_t max = 0;

  for (const SegmentCommand* segment : segments_) {
    min = std::min<uint64_t>(min, segment->file_offset());
    max = std::max(max, segment->file_offset() + segment->file_size());
  }

  if (min == uint64_t(-1)) {
    return {0, 0};
  }

  return {min, max};
}


LIEF::Binary::functions_t Binary::ctor_functions() const {
  LIEF::Binary::functions_t functions;
  for (const Section& section : sections()) {
    if (section.type() != Section::TYPE::MOD_INIT_FUNC_POINTERS) {
      continue;
    }

    span<const uint8_t> content = section.content();
    if (is64_) {
      const size_t nb_fnc = content.size() / sizeof(uint64_t);
      const auto* aptr = reinterpret_cast<const uint64_t*>(content.data());
      for (size_t i = 0; i < nb_fnc; ++i) {
        functions.emplace_back("ctor_" + std::to_string(i), aptr[i],
                               Function::FLAGS::CONSTRUCTOR);
      }

    } else {
      const size_t nb_fnc = content.size() / sizeof(uint32_t);
      const auto* aptr = reinterpret_cast<const uint32_t*>(content.data());
      for (size_t i = 0; i < nb_fnc; ++i) {
        functions.emplace_back("ctor_" + std::to_string(i), aptr[i],
                               Function::FLAGS::CONSTRUCTOR);
      }
    }
  }
  return functions;
}


LIEF::Binary::functions_t Binary::functions() const {
  static const auto func_cmd = [] (const Function& lhs, const Function& rhs) {
    return lhs.address() < rhs.address();
  };
  std::set<Function, decltype(func_cmd)> functions_set(func_cmd);

  LIEF::Binary::functions_t unwind_functions = this->unwind_functions();
  LIEF::Binary::functions_t ctor_functions   = this->ctor_functions();
  LIEF::Binary::functions_t exported         = get_abstract_exported_functions();

  std::move(std::begin(unwind_functions), std::end(unwind_functions),
            std::inserter(functions_set, std::end(functions_set)));

  std::move(std::begin(ctor_functions), std::end(ctor_functions),
            std::inserter(functions_set, std::end(functions_set)));

  std::move(std::begin(exported), std::end(exported),
            std::inserter(functions_set, std::end(functions_set)));

  return {std::begin(functions_set), std::end(functions_set)};

}

LIEF::Binary::functions_t Binary::unwind_functions() const {
  static constexpr size_t UNWIND_COMPRESSED = 3;
  static constexpr size_t UNWIND_UNCOMPRESSED = 2;

  // Set container to have functions with unique address
  static const auto fcmd = [] (const Function& l, const Function& r) {
    return l.address() < r.address();
  };
  std::set<Function, decltype(fcmd)> functions(fcmd);

  // Look for the __unwind_info section
  const Section* unwind_section = get_section("__unwind_info");
  if (unwind_section == nullptr) {
    return {};
  }

  SpanStream vs = unwind_section->content();

  // Get section content
  const auto hdr = vs.read<details::unwind_info_section_header>();
  if (!hdr) {
    LIEF_ERR("Can't read unwind section header!");
    return {};
  }
  vs.setpos(hdr->index_section_offset);

  size_t lsda_start = -1lu;
  size_t lsda_stop = 0;
  for (size_t i = 0; i < hdr->index_count; ++i) {
    const auto section_hdr = vs.read<details::unwind_info_section_header_index_entry>();
    if (!section_hdr) {
      LIEF_ERR("Can't read function information at index #{:d}", i);
      break;
    }

    functions.emplace(section_hdr->function_offset);
    const size_t second_lvl_off = section_hdr->second_level_pages_section_offset;
    const size_t lsda_off       = section_hdr->lsda_index_array_section_offset;

    lsda_start = std::min(lsda_off, lsda_start);
    lsda_stop  = std::max(lsda_off, lsda_stop);

    if (second_lvl_off > 0 && vs.can_read<details::unwind_info_regular_second_level_page_header>(second_lvl_off)) {
      const size_t saved_pos = vs.pos();
      {
        vs.setpos(second_lvl_off);
        const auto lvl_hdr = vs.peek<details::unwind_info_regular_second_level_page_header>(second_lvl_off);
        if (!lvl_hdr) {
          break;
        }
        if (lvl_hdr->kind == UNWIND_COMPRESSED) {
          const auto lvl_compressed_hdr = vs.read<details::unwind_info_compressed_second_level_page_header>();
          if (!lvl_compressed_hdr) {
            LIEF_ERR("Can't read lvl_compressed_hdr");
            break;
          }

          vs.setpos(second_lvl_off + lvl_compressed_hdr->entry_page_offset);
          for (size_t j = 0; j < lvl_compressed_hdr->entry_count; ++j) {
            auto entry = vs.read<uint32_t>();
            if (!entry) {
              break;
            }
            uint32_t func_off = section_hdr->function_offset + (*entry & 0xffffff);
            functions.emplace(func_off);
          }
        }
        else if (lvl_hdr->kind == UNWIND_UNCOMPRESSED) {
          LIEF_WARN("UNWIND_UNCOMPRESSED is not supported yet!");
        }
        else {
          LIEF_WARN("Unknown 2nd level kind: {:d}", lvl_hdr->kind);
        }
      }
      vs.setpos(saved_pos);
    }

  }

  const size_t nb_lsda = lsda_stop > lsda_start ? (lsda_stop - lsda_start) / sizeof(details::unwind_info_section_header_lsda_index_entry) : 0;
  vs.setpos(lsda_start);
  for (size_t i = 0; i < nb_lsda; ++i) {
    const auto hdr = vs.read<details::unwind_info_section_header_lsda_index_entry>();
    if (!hdr) {
      LIEF_ERR("Can't read LSDA at index #{:d}", i);
      break;
    }
    functions.emplace(hdr->function_offset);
  }

  return {std::begin(functions), std::end(functions)};
}

// UUID
// ++++
UUIDCommand* Binary::uuid() {
  return command<UUIDCommand>();
}

const UUIDCommand* Binary::uuid() const {
  return command<UUIDCommand>();
}

// MainCommand
// +++++++++++
MainCommand* Binary::main_command() {
  return command<MainCommand>();
}

const MainCommand* Binary::main_command() const {
  return command<MainCommand>();
}

// DylinkerCommand
// +++++++++++++++
DylinkerCommand* Binary::dylinker() {
  return command<DylinkerCommand>();
}

const DylinkerCommand* Binary::dylinker() const {
  return command<DylinkerCommand>();
}

// DyldInfo
// ++++++++
DyldInfo* Binary::dyld_info() {
  return command<DyldInfo>();
}

const DyldInfo* Binary::dyld_info() const {
  return command<DyldInfo>();
}

// Function Starts
// +++++++++++++++
FunctionStarts* Binary::function_starts() {
  return command<FunctionStarts>();
}

const FunctionStarts* Binary::function_starts() const {
  return command<FunctionStarts>();
}

// Source Version
// ++++++++++++++
SourceVersion* Binary::source_version() {
  return command<SourceVersion>();
}

const SourceVersion* Binary::source_version() const {
  return command<SourceVersion>();
}

// Version Min
// +++++++++++
VersionMin* Binary::version_min() {
  return command<VersionMin>();
}

const VersionMin* Binary::version_min() const {
  return command<VersionMin>();
}

// Routine Command
// +++++++++++++++
Routine* Binary::routine_command() {
  return command<Routine>();
}

const Routine* Binary::routine_command() const {
  return command<Routine>();
}

// Thread command
// ++++++++++++++
ThreadCommand* Binary::thread_command() {
  return command<ThreadCommand>();
}

const ThreadCommand* Binary::thread_command() const {
  return command<ThreadCommand>();
}

// RPath command
// +++++++++++++
RPathCommand* Binary::rpath() {
  return command<RPathCommand>();
}

const RPathCommand* Binary::rpath() const {
  return command<RPathCommand>();
}

Binary::it_rpaths Binary::rpaths() {
  return {commands_, [] (const std::unique_ptr<LoadCommand>& cmd) {
    return RPathCommand::classof(cmd.get());
  }};
}

Binary::it_const_rpaths Binary::rpaths() const {
  return {commands_, [] (const std::unique_ptr<LoadCommand>& cmd) {
    return RPathCommand::classof(cmd.get());
  }};
}

// SymbolCommand command
// +++++++++++++++++++++
SymbolCommand* Binary::symbol_command() {
  return command<SymbolCommand>();
}

const SymbolCommand* Binary::symbol_command() const {
  return command<SymbolCommand>();
}

// DynamicSymbolCommand command
// ++++++++++++++++++++++++++++

DynamicSymbolCommand* Binary::dynamic_symbol_command() {
  return command<DynamicSymbolCommand>();
}

const DynamicSymbolCommand* Binary::dynamic_symbol_command() const {
  return command<DynamicSymbolCommand>();
}

// CodeSignature command
// +++++++++++++++++++++
const CodeSignature* Binary::code_signature() const {
  if (const auto* cmd = get(LoadCommand::TYPE::CODE_SIGNATURE)) {
    return cmd->as<const CodeSignature>();
  }
  return nullptr;
}


// CodeSignatureDir command
// ++++++++++++++++++++++++
const CodeSignatureDir* Binary::code_signature_dir() const {
  if (const auto* cmd = get(LoadCommand::TYPE::DYLIB_CODE_SIGN_DRS)) {
    return cmd->as<const CodeSignatureDir>();
  }
  return nullptr;
}


// DataInCode command
// ++++++++++++++++++
DataInCode* Binary::data_in_code() {
  return command<DataInCode>();
}

const DataInCode* Binary::data_in_code() const {
  return command<DataInCode>();
}


// SegmentSplitInfo command
// ++++++++++++++++++++++++
SegmentSplitInfo* Binary::segment_split_info() {
  return command<SegmentSplitInfo>();
}

const SegmentSplitInfo* Binary::segment_split_info() const {
  return command<SegmentSplitInfo>();
}


// SubClient command
// ++++++++++++++++++++
Binary::it_sub_clients Binary::subclients() {
  return {commands_, [] (const std::unique_ptr<LoadCommand>& cmd) {
    return SubClient::classof(cmd.get());
  }};
}

Binary::it_const_sub_clients Binary::subclients() const {
  return {commands_, [] (const std::unique_ptr<LoadCommand>& cmd) {
    return SubClient::classof(cmd.get());
  }};
}

bool Binary::has_subclients() const {
  return has_command<SubClient>();
}

// SubFramework command
// ++++++++++++++++++++
SubFramework* Binary::sub_framework() {
  return command<SubFramework>();
}

const SubFramework* Binary::sub_framework() const {
  return command<SubFramework>();
}

// DyldEnvironment command
// +++++++++++++++++++++++
DyldEnvironment* Binary::dyld_environment() {
  return command<DyldEnvironment>();
}

const DyldEnvironment* Binary::dyld_environment() const {
  return command<DyldEnvironment>();
}

// EncryptionInfo command
// +++++++++++++++++++++++
EncryptionInfo* Binary::encryption_info() {
  return command<EncryptionInfo>();
}

const EncryptionInfo* Binary::encryption_info() const {
  return command<EncryptionInfo>();
}


// BuildVersion command
// ++++++++++++++++++++
BuildVersion* Binary::build_version() {
  return command<BuildVersion>();
}

const BuildVersion* Binary::build_version() const {
  return command<BuildVersion>();
}

// DyldChainedFixups command
// ++++++++++++++++++++
DyldChainedFixups* Binary::dyld_chained_fixups() {
  return command<DyldChainedFixups>();
}

const DyldChainedFixups* Binary::dyld_chained_fixups() const {
  return command<DyldChainedFixups>();
}

// DyldExportsTrie command
// +++++++++++++++++++++++
DyldExportsTrie* Binary::dyld_exports_trie() {
  return command<DyldExportsTrie>();
}

const DyldExportsTrie* Binary::dyld_exports_trie() const {
  return command<DyldExportsTrie>();
}

// Linker Optimization Hint command
// ++++++++++++++++++++++++++++++++
const LinkerOptHint* Binary::linker_opt_hint() const {
  if (const auto* cmd = get(LoadCommand::TYPE::LINKER_OPTIMIZATION_HINT)) {
    return cmd->as<const LinkerOptHint>();
  }
  return nullptr;
}

// Two Level Hints Command
// ++++++++++++++++++++++++++++++++
const TwoLevelHints* Binary::two_level_hints() const {
  if (const auto* cmd = get(LoadCommand::TYPE::TWOLEVEL_HINTS)) {
    return cmd->as<const TwoLevelHints>();
  }
  return nullptr;
}


Binary::it_bindings Binary::bindings() const {
  if (const DyldInfo* dyld = dyld_info()) {
    auto begin = BindingInfoIterator(*dyld, 0);
    auto end = BindingInfoIterator(*dyld, dyld->binding_info_.size());
    return make_range(std::move(begin), std::move(end));
  }

  if (const DyldChainedFixups* fixup = dyld_chained_fixups()) {
    auto begin = BindingInfoIterator(*fixup, 0);
    auto end = BindingInfoIterator(*fixup, fixup->all_bindings_.size());
    return make_range(std::move(begin), std::move(end));
  }

  auto begin = BindingInfoIterator(*this, 0);
  auto end = BindingInfoIterator(*this, indirect_bindings_.size());

  return make_range(std::move(begin), std::move(end));
}

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

Symbol& Binary::add(const Symbol& symbol) {
  symbols_.push_back(std::make_unique<Symbol>(symbol));
  return *symbols_.back();
}

Symbol* Binary::add_local_symbol(uint64_t address, const std::string& name) {
  Symbol* symbol = nullptr;

  auto sym = std::make_unique<Symbol>();
  sym->category_          = Symbol::CATEGORY::LOCAL;
  sym->origin_            = Symbol::ORIGIN::LC_SYMTAB;
  sym->numberof_sections_ = 0;
  sym->description_       = static_cast<uint16_t>(/* N_NO_DEAD_STRIP */0x20);

  sym->value(address);
  sym->name(name);
  symbol = sym.get();
  symbols_.push_back(std::move(sym));
  return symbol;
}

ExportInfo* Binary::add_exported_function(uint64_t address, const std::string& name) {
  if (Symbol* symbol = add_local_symbol(address, name)) {
    if (DyldExportsTrie* exports = dyld_exports_trie()) {
      auto export_info = std::make_unique<ExportInfo>(address, 0);
      export_info->symbol_ = symbol;
      export_info->address(address);
      symbol->export_info_ = export_info.get();

      auto* info = export_info.get();
      exports->add(std::move(export_info));
      return info;
    }

    if (DyldInfo* info = dyld_info()) {
      auto export_info = std::make_unique<ExportInfo>(address, 0);
      export_info->symbol_ = symbol;
      export_info->address(address);
      symbol->export_info_ = export_info.get();

      auto* info_ptr = export_info.get();
      info->add(std::move(export_info));
      return info_ptr;
    }
  }
  return nullptr;
}


void Binary::refresh_seg_offset() {
  offset_seg_.clear();
  for (SegmentCommand* segment : segments_) {
    if (!can_cache_segment(*segment)) {
      continue;
    }
    offset_seg_[segment->file_offset()] = segment;
  }
}

Binary::stub_iterator Binary::symbol_stubs() const {
  static stub_iterator empty_iterator(
    Stub::Iterator{},
    Stub::Iterator{}
  );

  std::vector<const Section*> stub_sections;
  stub_sections.reserve(3);

  uint32_t total = 0;

  for (const Section& section : sections()) {
    if (section.type() != Section::TYPE::SYMBOL_STUBS) {
      continue;
    }

    const uint32_t stride = section.reserved2();
    if (stride == 0) {
      continue;
    }

    const uint32_t count = section.content().size() / stride;
    if (count == 0) {
      continue;
    }

    total += count;
    stub_sections.push_back(&section);
  }

  if (stub_sections.empty() || total == 0) {
    return empty_iterator;
  }
  Stub::Iterator begin(
      {header_.cpu_type(), header_.cpu_subtype()},
      std::move(stub_sections), 0
  );
  Stub::Iterator end({}, {}, total);

  return make_range(std::move(begin), std::move(end));
}

bool Binary::can_cache_segment(const SegmentCommand& segment) {
  if (segment.file_offset() > 0 && segment.file_size() > 0) {
    return true;
  }

  if (segment.name() == "__TEXT") {
    // In some cases (c.f. <samples>/MachO/issue_1130.macho)
    // the __TEXT segment can have a file_size set to 0 while it is logically
    // revelant to cache it
    return true;
  }

  return false;
}

Binary::~Binary() = default;

std::ostream& Binary::print(std::ostream& os) const {
  os << "Header" << '\n';
  os << "======" << '\n';

  os << header();
  os << '\n';


  os << "Commands" << '\n';
  os << "========" << '\n';
  for (const LoadCommand& cmd : commands()) {
    os << cmd << '\n';
  }

  os << '\n';

  os << "Sections" << '\n';
  os << "========" << '\n';
  for (const Section& section : sections()) {
    os << section << '\n';
  }

  os << '\n';

  os << "Symbols" << '\n';
  os << "=======" << '\n';
  for (const Symbol& symbol : symbols()) {
    os << symbol << '\n';
  }

  os << '\n';
  return os;
}

}
}

