// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2022-2024 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License.  You may obtain a copy of the License at
//
//     https://llvm.org/LICENSE.txt
//
// 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.
//
// Author: Aleksei Vetrov

#include "elf_dwarf_handle.h"

#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <gelf.h>
#include <libelf.h>

#include <cstddef>
#include <functional>
#include <sstream>
#include <string>

#include "error.h"
#include "hex.h"

namespace stg {

namespace {

const Dwfl_Callbacks kDwflCallbacks = {
    .find_elf = nullptr,
    .find_debuginfo = dwfl_standard_find_debuginfo,
    .section_address = dwfl_offline_section_address,
    .debuginfo_path = nullptr};

constexpr int kReturnOk = 0;

std::string GetDwflError(const char* caller) {
  std::ostringstream result;
  const int dwfl_error = dwfl_errno();
  const char* errmsg = dwfl_errmsg(dwfl_error);
  if (errmsg == nullptr) {
    // There are some cases when DWFL fails to produce an error message.
    result << caller << " returned error code " << Hex(dwfl_error);
  } else {
    result << caller << " returned error: " << errmsg;
  }
  return result.str();
}

void CheckOrDwflError(bool condition, const char* caller) {
  if (!condition) {
    Die() << GetDwflError(caller);
  }
}

}  // namespace

ElfDwarfHandle::ElfDwarfHandle(
    const char* module_name, const std::function<Dwfl_Module*()>& add_module) {
  dwfl_ = DwflUniquePtr(dwfl_begin(&kDwflCallbacks));
  CheckOrDwflError(dwfl_ != nullptr, "dwfl_begin");
  // Add data to process to dwfl
  dwfl_module_ = add_module();
  CheckOrDwflError(dwfl_module_ != nullptr, module_name);
  // Finish adding files to dwfl and process them
  CheckOrDwflError(dwfl_report_end(dwfl_.get(), nullptr, nullptr) == kReturnOk,
                   "dwfl_report_end");
}

ElfDwarfHandle::ElfDwarfHandle(const std::string& path)
    : ElfDwarfHandle("dwfl_report_offline", [&] {
        return dwfl_report_offline(dwfl_.get(), path.c_str(), path.c_str(), -1);
      }) {}

ElfDwarfHandle::ElfDwarfHandle(char* data, size_t size)
    : ElfDwarfHandle("dwfl_report_offline_memory", [&] {
        return dwfl_report_offline_memory(dwfl_.get(), "<memory>", "<memory>",
                                          data, size);
      }) {}

Elf& ElfDwarfHandle::GetElf() {
  GElf_Addr loadbase = 0;  // output argument for dwfl, unused by us
  Elf* elf = dwfl_module_getelf(dwfl_module_, &loadbase);
  CheckOrDwflError(elf != nullptr, "dwfl_module_getelf");
  return *elf;
}

Dwarf* ElfDwarfHandle::GetDwarf() {
  GElf_Addr loadbase = 0;  // output argument for dwfl, unused by us
  Dwarf* dwarf = dwfl_module_getdwarf(dwfl_module_, &loadbase);
  if (dwarf == nullptr) {
    Warn() << "No DWARF found: " << GetDwflError("dwfl_module_getdwarf");
  }
  return dwarf;
}

}  // namespace stg
