150 lines
4.9 KiB
C++
150 lines
4.9 KiB
C++
|
|
// Copyright 2018 The Chromium Authors
|
||
|
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
|
// found in the LICENSE file.
|
||
|
|
|
||
|
|
#include "base/profiler/module_cache.h"
|
||
|
|
|
||
|
|
#include <dlfcn.h>
|
||
|
|
#include <mach-o/getsect.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <uuid/uuid.h>
|
||
|
|
|
||
|
|
#include "base/strings/string_number_conversions.h"
|
||
|
|
#include "build/build_config.h"
|
||
|
|
|
||
|
|
namespace base {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
#if defined(ARCH_CPU_64_BITS)
|
||
|
|
using MachHeaderType = mach_header_64;
|
||
|
|
using SegmentCommandType = segment_command_64;
|
||
|
|
constexpr uint32_t kMachHeaderMagic = MH_MAGIC_64;
|
||
|
|
constexpr uint32_t kSegmentCommand = LC_SEGMENT_64;
|
||
|
|
#else
|
||
|
|
using MachHeaderType = mach_header;
|
||
|
|
using SegmentCommandType = segment_command;
|
||
|
|
constexpr uint32_t kMachHeaderMagic = MH_MAGIC;
|
||
|
|
constexpr uint32_t kSegmentCommand = LC_SEGMENT;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// Returns the unique build ID and text segment size for a module loaded at
|
||
|
|
// |module_addr|. Returns the empty string and 0 if the function fails to get
|
||
|
|
// the build ID or size.
|
||
|
|
//
|
||
|
|
// Build IDs are created by the concatenation of the module's GUID (Windows) /
|
||
|
|
// UUID (Mac) and an "age" field that indicates how many times that GUID/UUID
|
||
|
|
// has been reused. In Windows binaries, the "age" field is present in the
|
||
|
|
// module header, but on the Mac, UUIDs are never reused and so the "age" value
|
||
|
|
// appended to the UUID is always 0.
|
||
|
|
void GetUniqueIdAndTextSize(const void* module_addr,
|
||
|
|
std::string* unique_id,
|
||
|
|
size_t* text_size) {
|
||
|
|
const MachHeaderType* mach_header =
|
||
|
|
reinterpret_cast<const MachHeaderType*>(module_addr);
|
||
|
|
DCHECK_EQ(mach_header->magic, kMachHeaderMagic);
|
||
|
|
|
||
|
|
size_t offset = sizeof(MachHeaderType);
|
||
|
|
size_t offset_limit = sizeof(MachHeaderType) + mach_header->sizeofcmds;
|
||
|
|
bool found_uuid = false;
|
||
|
|
bool found_text_size = false;
|
||
|
|
|
||
|
|
for (uint32_t i = 0; i < mach_header->ncmds; ++i) {
|
||
|
|
if (offset + sizeof(load_command) >= offset_limit) {
|
||
|
|
unique_id->clear();
|
||
|
|
*text_size = 0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const load_command* load_cmd = reinterpret_cast<const load_command*>(
|
||
|
|
reinterpret_cast<const uint8_t*>(mach_header) + offset);
|
||
|
|
|
||
|
|
if (offset + load_cmd->cmdsize > offset_limit) {
|
||
|
|
// This command runs off the end of the command list. This is malformed.
|
||
|
|
unique_id->clear();
|
||
|
|
*text_size = 0;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (load_cmd->cmd == LC_UUID) {
|
||
|
|
if (load_cmd->cmdsize < sizeof(uuid_command)) {
|
||
|
|
// This "UUID command" is too small. This is malformed.
|
||
|
|
unique_id->clear();
|
||
|
|
} else {
|
||
|
|
const uuid_command* uuid_cmd =
|
||
|
|
reinterpret_cast<const uuid_command*>(load_cmd);
|
||
|
|
static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t),
|
||
|
|
"UUID field of UUID command should be 16 bytes.");
|
||
|
|
// The ID comprises the UUID concatenated with the Mac's "age" value
|
||
|
|
// which is always 0.
|
||
|
|
unique_id->assign(HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) +
|
||
|
|
"0");
|
||
|
|
}
|
||
|
|
if (found_text_size)
|
||
|
|
return;
|
||
|
|
found_uuid = true;
|
||
|
|
} else if (load_cmd->cmd == kSegmentCommand) {
|
||
|
|
const SegmentCommandType* segment_cmd =
|
||
|
|
reinterpret_cast<const SegmentCommandType*>(load_cmd);
|
||
|
|
if (strncmp(segment_cmd->segname, SEG_TEXT,
|
||
|
|
sizeof(segment_cmd->segname)) == 0) {
|
||
|
|
*text_size = segment_cmd->vmsize;
|
||
|
|
// Compare result with library function call, which is slower than this
|
||
|
|
// code.
|
||
|
|
unsigned long text_size_from_libmacho;
|
||
|
|
DCHECK(getsegmentdata(mach_header, SEG_TEXT, &text_size_from_libmacho));
|
||
|
|
DCHECK_EQ(*text_size, text_size_from_libmacho);
|
||
|
|
}
|
||
|
|
if (found_uuid)
|
||
|
|
return;
|
||
|
|
found_text_size = true;
|
||
|
|
}
|
||
|
|
offset += load_cmd->cmdsize;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!found_uuid) {
|
||
|
|
unique_id->clear();
|
||
|
|
}
|
||
|
|
if (!found_text_size) {
|
||
|
|
*text_size = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
class MacModule : public ModuleCache::Module {
|
||
|
|
public:
|
||
|
|
MacModule(const Dl_info& dl_info)
|
||
|
|
: base_address_(reinterpret_cast<uintptr_t>(dl_info.dli_fbase)),
|
||
|
|
debug_basename_(FilePath(dl_info.dli_fname).BaseName()) {
|
||
|
|
GetUniqueIdAndTextSize(dl_info.dli_fbase, &id_, &size_);
|
||
|
|
}
|
||
|
|
|
||
|
|
MacModule(const MacModule&) = delete;
|
||
|
|
MacModule& operator=(const MacModule&) = delete;
|
||
|
|
|
||
|
|
// ModuleCache::Module
|
||
|
|
uintptr_t GetBaseAddress() const override { return base_address_; }
|
||
|
|
std::string GetId() const override { return id_; }
|
||
|
|
FilePath GetDebugBasename() const override { return debug_basename_; }
|
||
|
|
size_t GetSize() const override { return size_; }
|
||
|
|
bool IsNative() const override { return true; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
uintptr_t base_address_;
|
||
|
|
std::string id_;
|
||
|
|
FilePath debug_basename_;
|
||
|
|
size_t size_;
|
||
|
|
};
|
||
|
|
|
||
|
|
// static
|
||
|
|
std::unique_ptr<const ModuleCache::Module> ModuleCache::CreateModuleForAddress(
|
||
|
|
uintptr_t address) {
|
||
|
|
Dl_info info;
|
||
|
|
if (!dladdr(reinterpret_cast<const void*>(address), &info))
|
||
|
|
return nullptr;
|
||
|
|
return std::make_unique<MacModule>(info);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace base
|