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 "LIEF/BinaryStream/SpanStream.hpp"

#include "LIEF/MachO/FunctionVariants.hpp"

#include "MachO/Structures.hpp"

#include "logging.hpp"
#include "internal_utils.hpp"


namespace LIEF::MachO {

static_assert(sizeof(details::runtime_table_entry_t) == 8);

using FLAGS = FunctionVariants::RuntimeTableEntry::FLAGS;
using KIND = FunctionVariants::RuntimeTable::KIND;

static const char* pretty_name(FLAGS f) {
  switch (f) {

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define FUNCTION_VARIANT_FLAG(name, _, pretty)                                    \
  case FLAGS::name: return pretty;
#include "LIEF/MachO/FunctionVariants/Arm64.def"
#include "LIEF/MachO/FunctionVariants/PerProcess.def"
#include "LIEF/MachO/FunctionVariants/SystemWide.def"
#include "LIEF/MachO/FunctionVariants/X86_64.def"
#undef FUNCTION_VARIANT_FLAG
    case FLAGS::UNKNOWN: return "UNKNOWN";
  }
  return "UNKNOWN";
}

FunctionVariants::FunctionVariants(const details::linkedit_data_command& cmd) :
  LoadCommand::LoadCommand{LoadCommand::TYPE(cmd.cmd), cmd.cmdsize},
  data_offset_{cmd.dataoff},
  data_size_{cmd.datasize} {}

std::ostream& FunctionVariants::print(std::ostream& os) const {
  LoadCommand::print(os) << '\n';
  auto runtime_tbl = runtime_table();

  for (size_t i = 0; i < runtime_tbl.size(); ++i) {
    const RuntimeTable& entry = runtime_tbl[i];
    os << fmt::format("Table #{}\n", i) << indent(entry.to_string(), 2) << '\n';
  }

  return os;
}

FunctionVariants::RuntimeTableEntry::RuntimeTableEntry(
    const details::runtime_table_entry_t& entry
) :
  another_table_(entry.another_table),
  impl_(entry.impl) {
  flag_bit_nums_.fill(0);
  std::copy(std::begin(entry.flag_bit_nums), std::end(entry.flag_bit_nums),
            std::begin(flag_bit_nums_));
}

span<const uint8_t> get_relevant_flags(span<const uint8_t> raw) {
  auto it = std::find_if(raw.begin(), raw.end(), [](uint8_t v) { return v == 0; });
  if (it == raw.end()) {
    return raw;
  }
  size_t distance = std::distance(raw.begin(), it);

  return raw.subspan(0, distance);
}

std::vector<FLAGS> get_high_level_flags(span<const uint8_t> flags, KIND kind) {
  using RuntimeTableEntry = FunctionVariants::RuntimeTableEntry;

  std::vector<FLAGS> out;
  out.reserve(flags.size());

  for (uint8_t flag : flags) {
    switch (kind) {
      case KIND::UNKNOWN: out.push_back((FLAGS)flag); break;

      case KIND::PER_PROCESS:
        out.push_back((FLAGS)(flag | RuntimeTableEntry::F_PER_PROCESS));
        break;

      case KIND::SYSTEM_WIDE:
        out.push_back((FLAGS)(flag | RuntimeTableEntry::F_SYSTEM_WIDE));
        break;

      case KIND::ARM64:
        out.push_back((FLAGS)(flag | RuntimeTableEntry::F_ARM64));
        break;

      case KIND::X86_64:
        out.push_back((FLAGS)(flag | RuntimeTableEntry::F_X86_64));
        break;
    }
  }
  return out;
}

std::string FunctionVariants::RuntimeTableEntry::to_string() const {
  std::ostringstream oss;
  if (another_table()) {
    oss << fmt::format("Table: #{}", impl());
  } else {
    oss << fmt::format("Function: {:#010x}", impl());
  }
  std::vector<FLAGS> flags = this->flags();
  if (flags.empty()) {
    oss << " 00: default";
  } else {
    std::vector<uint8_t> raw;
    std::vector<const char*> formated;
    for (FLAGS f : flags) {
      raw.push_back(get_raw(f));
      formated.push_back(pretty_name(f));
    }
    oss << fmt::format(" {:02x}: {}", fmt::join(raw, " "),
                       fmt::join(formated, ", "));
  }

  return oss.str();
}

std::string FunctionVariants::RuntimeTable::to_string() const {
  std::ostringstream oss;
  oss << fmt::format("Namespace: {} ({})", MachO::to_string(kind()),
                     (uint32_t)kind())
      << '\n';
  for (const RuntimeTableEntry& entry : entries()) {
    oss << "  " << entry << '\n';
  }
  return oss.str();
}

std::vector<FunctionVariants::RuntimeTable>
    FunctionVariants::parse_payload(SpanStream& stream) {
  std::vector<FunctionVariants::RuntimeTable> result;

  auto tableCount = stream.read<uint32_t>();
  if (!tableCount) {
    LIEF_DEBUG("Failed to read FunctionVariants.OnDiskFormat.tableCount");
    return result;
  }

  int64_t max_entries = stream.size() / sizeof(details::runtime_table_entry_t);

  for (size_t i = 0; i < *tableCount; ++i) {
    if (max_entries <= 0) {
      break;
    }

    auto tableOffset = stream.read<uint32_t>();
    if (!tableOffset) {
      LIEF_DEBUG("Failed to read FunctionVariants.OnDiskFormat.tableOffsets[{}]",
                 i);
      return result;
    }

    {
      ScopedStream runtime_table_strm(stream, *tableOffset);
      if (auto entry = parse_entry(*runtime_table_strm, max_entries)) {
        max_entries -= entry->entries().size();
        result.push_back(std::move(*entry));
      } else {
        LIEF_DEBUG("Failed to parse entry #{} at offset: {:#08x}", i,
                   *tableOffset);
      }
    }
  }
  return result;
}

result<FunctionVariants::RuntimeTable>
    FunctionVariants::parse_entry(BinaryStream& stream, uint64_t max_entries) {
  auto kind = stream.read<uint32_t>();

  if (!kind) {
    LIEF_DEBUG("Failed to read FunctionVariantsRuntimeTable.kind");
    return make_error_code(kind.error());
  }

  RuntimeTable runtime_table((RuntimeTable::KIND)*kind, stream.pos());

  auto count = stream.read<uint32_t>();

  if (!count) {
    LIEF_DEBUG("Failed to read FunctionVariantsRuntimeTable.count");
    return make_error_code(count.error());
  }

  for (size_t i = 0; i < *count; ++i) {
    if (i > max_entries) {
      LIEF_WARN("Too many FunctionVariants. Stopping");
      return runtime_table;
    }
    auto raw_entry = stream.read<details::runtime_table_entry_t>();
    if (!raw_entry) {
      LIEF_DEBUG("Failed to read FunctionVariantsRuntimeTable.entries[{}]", i);
      return runtime_table;
    }

    RuntimeTableEntry entry(*raw_entry);

    span<const uint8_t> flags = get_relevant_flags(entry.flag_bit_nums());
    std::vector<FLAGS> hl_flags =
        get_high_level_flags(flags, runtime_table.kind());
    entry.set_flags(std::move(hl_flags));

    runtime_table.add(std::move(entry));
  }
  return runtime_table;
}

const char* to_string(FunctionVariants::RuntimeTable::KIND kind) {
  using KIND = FunctionVariants::RuntimeTable::KIND;
  switch (kind) {
    default:
    case KIND::UNKNOWN: return "UNKNOWN";
    case KIND::PER_PROCESS: return "PER_PROCESS";
    case KIND::SYSTEM_WIDE: return "SYSTEM_WIDE";
    case KIND::ARM64: return "ARM64";
    case KIND::X86_64: return "X86_64";
  }
}

const char* to_string(FLAGS f) {
  switch (f) {
#define FUNCTION_VARIANT_FLAG(X, __, _)                                           \
  case FLAGS::X: return #X;
#include "LIEF/MachO/FunctionVariants/Arm64.def"
#include "LIEF/MachO/FunctionVariants/PerProcess.def"
#include "LIEF/MachO/FunctionVariants/SystemWide.def"
#include "LIEF/MachO/FunctionVariants/X86_64.def"
#undef FUNCTION_VARIANT_FLAG
    case FLAGS::UNKNOWN: return "UNKNOWN";
  }
  return "UNKNOWN";
}

}