llvm-project/lldb/source/Plugins/ObjectContainer/BSD-Archive/ObjectContainerBSDArchive.cpp
Jason Molenda e4c83b7b11
[lldb][NFC] Change ObjectFile argument type (#171574)
The ObjectFile plugin interface accepts an optional DataBufferSP
argument. If the caller has the contents of the binary, it can provide
this in that DataBufferSP. The ObjectFile subclasses in their
CreateInstance methods will fill in the DataBufferSP with the actual
binary contents if it is not set.
ObjectFile base class creates an ivar DataExtractor from the
DataBufferSP passed in.

My next patch will be a caller that creates a VirtualDataExtractor with
the binary data, and needs to pass that in to the ObjectFile plugin,
instead of the bag-of-bytes DataBufferSP. It builds on the previous
patch changing ObjectFile's ivar from DataExtractor to DataExtractorSP
so I could pass in a subclass in the shared ptr. And it will be using
the VirtualDataExtractor that Jonas added in
https://github.com/llvm/llvm-project/pull/168802

No behavior is changed by the patch; we're simply moving the creation of
the DataExtractor to the caller, instead of a DataBuffer that is
immediately used to set up the ObjectFile DataExtractor. The patch is a
bit complicated because all of the ObjectFile subclasses have to
initialize their DataExtractor to pass in to the base class.

I ran the testsuite on macOS and on AArch64 Ubutnu. (btw David, I ran it
under qemu on my M4 mac with SME-no-SVE again, Ubuntu 25.10, checked
lshw(1) cpu capabilities, and qemu doesn't seem to be virtualizing the
SME, that explains why the testsuite passes)

rdar://148939795

---------

Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
2025-12-11 10:08:56 -08:00

528 lines
20 KiB
C++

//===-- ObjectContainerBSDArchive.cpp -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ObjectContainerBSDArchive.h"
#if defined(_WIN32) || defined(_AIX)
// Defines from ar, missing on Windows
#define SARMAG 8
#define ARFMAG "`\n"
typedef struct ar_hdr {
char ar_name[16];
char ar_date[12];
char ar_uid[6], ar_gid[6];
char ar_mode[8];
char ar_size[10];
char ar_fmag[2];
} ar_hdr;
#else
#include <ar.h>
#endif
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/Timer.h"
#include "llvm/Object/Archive.h"
#include "llvm/Support/MemoryBuffer.h"
using namespace lldb;
using namespace lldb_private;
using namespace llvm::object;
LLDB_PLUGIN_DEFINE(ObjectContainerBSDArchive)
ObjectContainerBSDArchive::Object::Object() : ar_name() {}
void ObjectContainerBSDArchive::Object::Clear() {
ar_name.Clear();
modification_time = 0;
size = 0;
file_offset = 0;
file_size = 0;
}
void ObjectContainerBSDArchive::Object::Dump() const {
printf("name = \"%s\"\n", ar_name.GetCString());
printf("mtime = 0x%8.8" PRIx32 "\n", modification_time);
printf("size = 0x%8.8" PRIx32 " (%" PRIu32 ")\n", size, size);
printf("file_offset = 0x%16.16" PRIx64 " (%" PRIu64 ")\n", file_offset,
file_offset);
printf("file_size = 0x%16.16" PRIx64 " (%" PRIu64 ")\n\n", file_size,
file_size);
}
ObjectContainerBSDArchive::Archive::Archive(const lldb_private::ArchSpec &arch,
const llvm::sys::TimePoint<> &time,
lldb::offset_t file_offset,
lldb::DataExtractorSP extractor_sp,
ArchiveType archive_type)
: m_arch(arch), m_modification_time(time), m_file_offset(file_offset),
m_objects(), m_extractor_sp(extractor_sp), m_archive_type(archive_type) {}
Log *l = GetLog(LLDBLog::Object);
ObjectContainerBSDArchive::Archive::~Archive() = default;
size_t ObjectContainerBSDArchive::Archive::ParseObjects() {
std::unique_ptr<llvm::MemoryBuffer> mem_buffer =
llvm::MemoryBuffer::getMemBuffer(
llvm::StringRef((const char *)m_extractor_sp->GetDataStart(),
m_extractor_sp->GetByteSize()),
llvm::StringRef(),
/*RequiresNullTerminator=*/false);
auto exp_ar = llvm::object::Archive::create(mem_buffer->getMemBufferRef());
if (!exp_ar) {
LLDB_LOG_ERROR(l, exp_ar.takeError(), "failed to create archive: {0}");
return 0;
}
auto llvm_archive = std::move(exp_ar.get());
llvm::Error iter_err = llvm::Error::success();
Object obj;
for (const auto &child : llvm_archive->children(iter_err)) {
obj.Clear();
auto exp_name = child.getName();
if (exp_name) {
obj.ar_name = ConstString(exp_name.get());
} else {
LLDB_LOG_ERROR(l, exp_name.takeError(),
"failed to get archive object name: {0}");
continue;
}
auto exp_mtime = child.getLastModified();
if (exp_mtime) {
obj.modification_time =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::time_point_cast<std::chrono::seconds>(
exp_mtime.get())
.time_since_epoch())
.count();
} else {
LLDB_LOG_ERROR(l, exp_mtime.takeError(),
"failed to get archive object time: {0}");
continue;
}
auto exp_size = child.getRawSize();
if (exp_size) {
obj.size = exp_size.get();
} else {
LLDB_LOG_ERROR(l, exp_size.takeError(),
"failed to get archive object size: {0}");
continue;
}
obj.file_offset = child.getDataOffset();
auto exp_file_size = child.getSize();
if (exp_file_size) {
obj.file_size = exp_file_size.get();
} else {
LLDB_LOG_ERROR(l, exp_file_size.takeError(),
"failed to get archive object file size: {0}");
continue;
}
m_object_name_to_index_map.Append(obj.ar_name, m_objects.size());
m_objects.push_back(obj);
}
if (iter_err) {
LLDB_LOG_ERROR(l, std::move(iter_err),
"failed to iterate over archive objects: {0}");
}
// Now sort all of the object name pointers
m_object_name_to_index_map.Sort();
return m_objects.size();
}
ObjectContainerBSDArchive::Object *
ObjectContainerBSDArchive::Archive::FindObject(
ConstString object_name, const llvm::sys::TimePoint<> &object_mod_time) {
const ObjectNameToIndexMap::Entry *match =
m_object_name_to_index_map.FindFirstValueForName(object_name);
if (!match)
return nullptr;
if (object_mod_time == llvm::sys::TimePoint<>())
return &m_objects[match->value];
const uint64_t object_modification_date = llvm::sys::toTimeT(object_mod_time);
if (m_objects[match->value].modification_time == object_modification_date)
return &m_objects[match->value];
const ObjectNameToIndexMap::Entry *next_match =
m_object_name_to_index_map.FindNextValueForName(match);
while (next_match) {
if (m_objects[next_match->value].modification_time ==
object_modification_date)
return &m_objects[next_match->value];
next_match = m_object_name_to_index_map.FindNextValueForName(next_match);
}
return nullptr;
}
ObjectContainerBSDArchive::Archive::shared_ptr
ObjectContainerBSDArchive::Archive::FindCachedArchive(
const FileSpec &file, const ArchSpec &arch,
const llvm::sys::TimePoint<> &time, lldb::offset_t file_offset) {
std::lock_guard<std::recursive_mutex> guard(Archive::GetArchiveCacheMutex());
shared_ptr archive_sp;
Archive::Map &archive_map = Archive::GetArchiveCache();
Archive::Map::iterator pos = archive_map.find(file);
// Don't cache a value for "archive_map.end()" below since we might delete an
// archive entry...
while (pos != archive_map.end() && pos->first == file) {
bool match = true;
if (arch.IsValid() &&
!pos->second->GetArchitecture().IsCompatibleMatch(arch))
match = false;
else if (file_offset != LLDB_INVALID_OFFSET &&
pos->second->GetFileOffset() != file_offset)
match = false;
if (match) {
if (pos->second->GetModificationTime() == time) {
return pos->second;
} else {
// We have a file at the same path with the same architecture whose
// modification time doesn't match. It doesn't make sense for us to
// continue to use this BSD archive since we cache only the object info
// which consists of file time info and also the file offset and file
// size of any contained objects. Since this information is now out of
// date, we won't get the correct information if we go and extract the
// file data, so we should remove the old and outdated entry.
archive_map.erase(pos);
pos = archive_map.find(file);
continue; // Continue to next iteration so we don't increment pos
// below...
}
}
++pos;
}
return archive_sp;
}
ObjectContainerBSDArchive::Archive::shared_ptr
ObjectContainerBSDArchive::Archive::ParseAndCacheArchiveForFile(
const FileSpec &file, const ArchSpec &arch,
const llvm::sys::TimePoint<> &time, lldb::offset_t file_offset,
DataExtractorSP extractor_sp, ArchiveType archive_type) {
shared_ptr archive_sp(
new Archive(arch, time, file_offset, extractor_sp, archive_type));
if (archive_sp) {
const size_t num_objects = archive_sp->ParseObjects();
if (num_objects > 0) {
std::lock_guard<std::recursive_mutex> guard(
Archive::GetArchiveCacheMutex());
Archive::GetArchiveCache().insert(std::make_pair(file, archive_sp));
} else {
archive_sp.reset();
}
}
return archive_sp;
}
ObjectContainerBSDArchive::Archive::Map &
ObjectContainerBSDArchive::Archive::GetArchiveCache() {
static Archive::Map g_archive_map;
return g_archive_map;
}
std::recursive_mutex &
ObjectContainerBSDArchive::Archive::GetArchiveCacheMutex() {
static std::recursive_mutex g_archive_map_mutex;
return g_archive_map_mutex;
}
void ObjectContainerBSDArchive::Initialize() {
PluginManager::RegisterPlugin(GetPluginNameStatic(),
GetPluginDescriptionStatic(), CreateInstance,
GetModuleSpecifications);
}
void ObjectContainerBSDArchive::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}
ObjectContainer *ObjectContainerBSDArchive::CreateInstance(
const lldb::ModuleSP &module_sp, DataBufferSP &data_sp,
lldb::offset_t data_offset, const FileSpec *file,
lldb::offset_t file_offset, lldb::offset_t length) {
ConstString object_name(module_sp->GetObjectName());
if (!object_name)
return nullptr;
if (data_sp) {
// We have data, which means this is the first 512 bytes of the file Check
// to see if the magic bytes match and if they do, read the entire table of
// contents for the archive and cache it
DataExtractor data;
data.SetData(data_sp, data_offset, length);
ArchiveType archive_type = ObjectContainerBSDArchive::MagicBytesMatch(data);
if (file && data_sp && archive_type != ArchiveType::Invalid) {
LLDB_SCOPED_TIMERF(
"ObjectContainerBSDArchive::CreateInstance (module = %s, file = "
"%p, file_offset = 0x%8.8" PRIx64 ", file_size = 0x%8.8" PRIx64 ")",
module_sp->GetFileSpec().GetPath().c_str(),
static_cast<const void *>(file), static_cast<uint64_t>(file_offset),
static_cast<uint64_t>(length));
// Map the entire .a file to be sure that we don't lose any data if the
// file gets updated by a new build while this .a file is being used for
// debugging
DataBufferSP archive_data_sp =
FileSystem::Instance().CreateDataBuffer(*file, length, file_offset);
if (!archive_data_sp)
return nullptr;
lldb::offset_t archive_data_offset = 0;
Archive::shared_ptr archive_sp(Archive::FindCachedArchive(
*file, module_sp->GetArchitecture(), module_sp->GetModificationTime(),
file_offset));
std::unique_ptr<ObjectContainerBSDArchive> container_up(
new ObjectContainerBSDArchive(module_sp, archive_data_sp,
archive_data_offset, file, file_offset,
length, archive_type));
if (container_up) {
if (archive_sp) {
// We already have this archive in our cache, use it
container_up->SetArchive(archive_sp);
return container_up.release();
} else if (container_up->ParseHeader())
return container_up.release();
}
}
} else {
// No data, just check for a cached archive
Archive::shared_ptr archive_sp(Archive::FindCachedArchive(
*file, module_sp->GetArchitecture(), module_sp->GetModificationTime(),
file_offset));
if (archive_sp) {
std::unique_ptr<ObjectContainerBSDArchive> container_up(
new ObjectContainerBSDArchive(module_sp, data_sp, data_offset, file,
file_offset, length,
archive_sp->GetArchiveType()));
if (container_up) {
// We already have this archive in our cache, use it
container_up->SetArchive(archive_sp);
return container_up.release();
}
}
}
return nullptr;
}
ArchiveType
ObjectContainerBSDArchive::MagicBytesMatch(const DataExtractor &data) {
uint32_t offset = 0;
const char *armag =
(const char *)data.PeekData(offset, sizeof(ar_hdr) + SARMAG);
if (armag == nullptr)
return ArchiveType::Invalid;
ArchiveType result = ArchiveType::Invalid;
if (strncmp(armag, ArchiveMagic, SARMAG) == 0)
result = ArchiveType::Archive;
else if (strncmp(armag, ThinArchiveMagic, SARMAG) == 0)
result = ArchiveType::ThinArchive;
else
return ArchiveType::Invalid;
armag += offsetof(struct ar_hdr, ar_fmag) + SARMAG;
if (strncmp(armag, ARFMAG, 2) == 0)
return result;
return ArchiveType::Invalid;
}
ObjectContainerBSDArchive::ObjectContainerBSDArchive(
const lldb::ModuleSP &module_sp, DataBufferSP &data_sp,
lldb::offset_t data_offset, const lldb_private::FileSpec *file,
lldb::offset_t file_offset, lldb::offset_t size, ArchiveType archive_type)
: ObjectContainer(module_sp, file, file_offset, size, data_sp, data_offset),
m_archive_sp() {
m_archive_type = archive_type;
}
void ObjectContainerBSDArchive::SetArchive(Archive::shared_ptr &archive_sp) {
m_archive_sp = archive_sp;
}
ObjectContainerBSDArchive::~ObjectContainerBSDArchive() = default;
bool ObjectContainerBSDArchive::ParseHeader() {
if (m_archive_sp.get() == nullptr) {
if (m_extractor_sp->GetByteSize() > 0) {
ModuleSP module_sp(GetModule());
if (module_sp) {
m_archive_sp = Archive::ParseAndCacheArchiveForFile(
m_file, module_sp->GetArchitecture(),
module_sp->GetModificationTime(), m_offset, m_extractor_sp,
m_archive_type);
}
// Clear the m_extractor_sp that contains the entire archive data and let
// our m_archive_sp hold onto the data.
m_extractor_sp = std::make_shared<DataExtractor>();
}
}
return m_archive_sp.get() != nullptr;
}
FileSpec GetChildFileSpecificationsFromThin(llvm::StringRef childPath,
const FileSpec &parentFileSpec) {
llvm::SmallString<128> FullPath;
if (llvm::sys::path::is_absolute(childPath)) {
FullPath = childPath;
} else {
FullPath = parentFileSpec.GetDirectory().GetStringRef();
llvm::sys::path::append(FullPath, childPath);
}
FileSpec child = FileSpec(FullPath.str(), llvm::sys::path::Style::posix);
return child;
}
ObjectFileSP ObjectContainerBSDArchive::GetObjectFile(const FileSpec *file) {
ModuleSP module_sp(GetModule());
if (module_sp) {
if (module_sp->GetObjectName() && m_archive_sp) {
Object *object = m_archive_sp->FindObject(
module_sp->GetObjectName(), module_sp->GetObjectModificationTime());
if (object) {
if (m_archive_type == ArchiveType::ThinArchive) {
// Set file to child object file
FileSpec child = GetChildFileSpecificationsFromThin(
object->ar_name.GetStringRef(), m_file);
lldb::offset_t file_offset = 0;
lldb::offset_t file_size = object->size;
std::shared_ptr<DataBuffer> child_data_sp =
FileSystem::Instance().CreateDataBuffer(child, file_size,
file_offset);
if (!child_data_sp ||
child_data_sp->GetByteSize() != object->file_size)
return ObjectFileSP();
lldb::offset_t data_offset = 0;
DataExtractorSP extractor_sp =
std::make_shared<DataExtractor>(child_data_sp);
return ObjectFile::FindPlugin(
module_sp, &child, m_offset + object->file_offset,
object->file_size, extractor_sp, data_offset);
}
lldb::offset_t data_offset = object->file_offset;
DataExtractorSP extractor_sp =
std::make_shared<DataExtractor>(m_archive_sp->GetData());
return ObjectFile::FindPlugin(
module_sp, file, m_offset + object->file_offset, object->file_size,
extractor_sp, data_offset);
}
}
}
return ObjectFileSP();
}
size_t ObjectContainerBSDArchive::GetModuleSpecifications(
const lldb_private::FileSpec &file, lldb::DataBufferSP &data_sp,
lldb::offset_t data_offset, lldb::offset_t file_offset,
lldb::offset_t file_size, lldb_private::ModuleSpecList &specs) {
// We have data, which means this is the first 512 bytes of the file Check to
// see if the magic bytes match and if they do, read the entire table of
// contents for the archive and cache it
DataExtractorSP extractor_sp = std::make_shared<DataExtractor>();
extractor_sp->SetData(data_sp, data_offset, data_sp->GetByteSize());
ArchiveType archive_type =
ObjectContainerBSDArchive::MagicBytesMatch(*extractor_sp.get());
if (!file || !data_sp || archive_type == ArchiveType::Invalid)
return 0;
const size_t initial_count = specs.GetSize();
llvm::sys::TimePoint<> file_mod_time =
FileSystem::Instance().GetModificationTime(file);
Archive::shared_ptr archive_sp(
Archive::FindCachedArchive(file, ArchSpec(), file_mod_time, file_offset));
bool set_archive_arch = false;
if (!archive_sp) {
set_archive_arch = true;
data_sp =
FileSystem::Instance().CreateDataBuffer(file, file_size, file_offset);
if (data_sp) {
extractor_sp->SetData(data_sp);
archive_sp = Archive::ParseAndCacheArchiveForFile(
file, ArchSpec(), file_mod_time, file_offset, extractor_sp,
archive_type);
}
}
if (archive_sp) {
const size_t num_objects = archive_sp->GetNumObjects();
for (size_t idx = 0; idx < num_objects; ++idx) {
const Object *object = archive_sp->GetObjectAtIndex(idx);
if (object) {
if (archive_sp->GetArchiveType() == ArchiveType::ThinArchive) {
if (object->ar_name.IsEmpty())
continue;
FileSpec child = GetChildFileSpecificationsFromThin(
object->ar_name.GetStringRef(), file);
if (ObjectFile::GetModuleSpecifications(child, 0, object->file_size,
specs)) {
ModuleSpec &spec =
specs.GetModuleSpecRefAtIndex(specs.GetSize() - 1);
llvm::sys::TimePoint<> object_mod_time(
std::chrono::seconds(object->modification_time));
spec.GetObjectName() = object->ar_name;
spec.SetObjectOffset(0);
spec.SetObjectSize(object->file_size);
spec.GetObjectModificationTime() = object_mod_time;
}
continue;
}
const lldb::offset_t object_file_offset =
file_offset + object->file_offset;
if (object->file_offset < file_size && file_size > object_file_offset) {
if (ObjectFile::GetModuleSpecifications(
file, object_file_offset, file_size - object_file_offset,
specs)) {
ModuleSpec &spec =
specs.GetModuleSpecRefAtIndex(specs.GetSize() - 1);
llvm::sys::TimePoint<> object_mod_time(
std::chrono::seconds(object->modification_time));
spec.GetObjectName() = object->ar_name;
spec.SetObjectOffset(object_file_offset);
spec.SetObjectSize(object->file_size);
spec.GetObjectModificationTime() = object_mod_time;
}
}
}
}
}
const size_t end_count = specs.GetSize();
size_t num_specs_added = end_count - initial_count;
if (set_archive_arch && num_specs_added > 0) {
// The archive was created but we didn't have an architecture so we need to
// set it
for (size_t i = initial_count; i < end_count; ++i) {
ModuleSpec module_spec;
if (specs.GetModuleSpecAtIndex(i, module_spec)) {
if (module_spec.GetArchitecture().IsValid()) {
archive_sp->SetArchitecture(module_spec.GetArchitecture());
break;
}
}
}
}
return num_specs_added;
}