#include "absl/profiling/internal/profile_builder.h"
#ifdef __linux__
#include <elf.h>
#include <link.h>
#endif
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/casts.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
namespace {
enum class WireType {
kVarint = 0,
kLengthDelimited = 2,
};
#ifdef __linux__
const ElfW(Phdr) * GetFirstSegment(const dl_phdr_info* const info,
const ElfW(Word) segment_type) {
for (int i = 0; i < info->dlpi_phnum; ++i) {
if (info->dlpi_phdr[i].p_type == segment_type) {
return &info->dlpi_phdr[i];
}
}
return nullptr;
}
static const char* GetSoName(const dl_phdr_info* const info) {
const ElfW(Phdr)* const pt_dynamic = GetFirstSegment(info, PT_DYNAMIC);
if (pt_dynamic == nullptr) {
return nullptr;
}
const ElfW(Dyn)* dyn =
reinterpret_cast<ElfW(Dyn)*>(info->dlpi_addr + pt_dynamic->p_vaddr);
const ElfW(Dyn)* dt_strtab = nullptr;
const ElfW(Dyn)* dt_strsz = nullptr;
const ElfW(Dyn)* dt_soname = nullptr;
for (; dyn->d_tag != DT_NULL; ++dyn) {
if (dyn->d_tag == DT_SONAME) {
dt_soname = dyn;
} else if (dyn->d_tag == DT_STRTAB) {
dt_strtab = dyn;
} else if (dyn->d_tag == DT_STRSZ) {
dt_strsz = dyn;
}
}
if (dt_soname == nullptr) {
return nullptr;
}
ABSL_RAW_CHECK(dt_strtab != nullptr, "Unexpected nullptr");
ABSL_RAW_CHECK(dt_strsz != nullptr, "Unexpected nullptr");
const char* const strtab = reinterpret_cast<char*>(
info->dlpi_addr + static_cast<ElfW(Word)>(dt_strtab->d_un.d_val));
ABSL_RAW_CHECK(dt_soname->d_un.d_val < dt_strsz->d_un.d_val,
"Unexpected order");
return strtab + dt_soname->d_un.d_val;
}
std::string GetBuildId(const dl_phdr_info* const info) {
std::string result;
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)* pt_note = &info->dlpi_phdr[i];
if (pt_note->p_type != PT_NOTE) continue;
const char* note =
reinterpret_cast<char*>(info->dlpi_addr + pt_note->p_vaddr);
const char* const last = note + pt_note->p_filesz;
const ElfW(Xword) align = pt_note->p_align;
while (note < last) {
const ElfW(Nhdr)* const nhdr = reinterpret_cast<const ElfW(Nhdr)*>(note);
if (note + sizeof(*nhdr) > last) {
break;
}
const ElfW(Xword) desc_start =
(sizeof(*nhdr) + nhdr->n_namesz + align - 1) & -align;
const ElfW(Xword) size =
desc_start + ((nhdr->n_descsz + align - 1) & -align);
if (nhdr->n_namesz >= static_cast<ElfW(Word)>(-align) ||
nhdr->n_descsz >= static_cast<ElfW(Word)>(-align) ||
desc_start < sizeof(*nhdr) || size < desc_start ||
size > static_cast<ElfW(Xword)>(last - note)) {
break;
}
if (nhdr->n_type == NT_GNU_BUILD_ID) {
const char* const note_name = note + sizeof(*nhdr);
if (nhdr->n_namesz == 4 && memcmp(note_name, "GNU\0", 4) == 0) {
if (!result.empty()) {
return "";
}
result = absl::BytesToHexString(
absl::string_view(note + desc_start, nhdr->n_descsz));
}
}
note += size;
}
}
return result;
}
#endif
struct Varint {
explicit Varint(uint64_t v) : value(v) {}
explicit Varint(StringId v) : value(static_cast<uint64_t>(v)) {}
explicit Varint(LocationId v) : value(static_cast<uint64_t>(v)) {}
explicit Varint(MappingId v) : value(static_cast<uint64_t>(v)) {}
uint64_t value;
template <typename Sink>
friend void AbslStringify(Sink& sink, const Varint& v) {
char buf[10];
char* p = buf;
uint64_t u = v.value;
while (u >= 0x80) {
*p++ = static_cast<char>((u & 0x7f) | 0x80);
u >>= 7;
}
*p++ = static_cast<char>(u);
sink.Append(absl::string_view(buf, static_cast<size_t>(p - buf)));
}
};
struct Tag {
int field_number;
WireType wire_type;
template <typename Sink>
friend void AbslStringify(Sink& sink, const Tag& t) {
absl::Format(&sink, "%v",
Varint((static_cast<uint64_t>(t.field_number) << 3) |
static_cast<uint64_t>(t.wire_type)));
}
};
struct LengthDelimited {
int field_number;
absl::string_view value;
template <typename Sink>
friend void AbslStringify(Sink& sink, const LengthDelimited& ld) {
absl::Format(&sink, "%v%v%v",
Tag{ld.field_number, WireType::kLengthDelimited},
Varint(ld.value.size()), ld.value);
}
};
struct VarintField {
int field_number;
Varint value;
template <typename Sink>
friend void AbslStringify(Sink& sink, const VarintField& vf) {
absl::Format(&sink, "%v%v", Tag{vf.field_number, WireType::kVarint},
vf.value);
}
};
}
StringId ProfileBuilder::InternString(absl::string_view str) {
if (str.empty()) return StringId(0);
return string_table_.emplace(str, StringId(string_table_.size()))
.first->second;
}
LocationId ProfileBuilder::InternLocation(const void* address) {
return location_table_
.emplace(absl::bit_cast<uintptr_t>(address),
LocationId(location_table_.size() + 1))
.first->second;
}
void ProfileBuilder::AddSample(
int64_t value, absl::Span<const void* const> stack,
absl::Span<const std::pair<StringId, int64_t>> labels) {
std::string sample_proto;
absl::StrAppend(
&sample_proto,
VarintField{SampleProto::kValue, Varint(static_cast<uint64_t>(value))});
for (const void* addr : stack) {
absl::StrAppend(
&sample_proto,
VarintField{SampleProto::kLocationId,
Varint(InternLocation(absl::bit_cast<const void*>(
absl::bit_cast<uintptr_t>(addr) - 1)))});
}
for (const auto& label : labels) {
std::string label_proto =
absl::StrCat(VarintField{LabelProto::kKey, Varint(label.first)},
VarintField{LabelProto::kNum,
Varint(static_cast<uint64_t>(label.second))});
absl::StrAppend(&sample_proto,
LengthDelimited{SampleProto::kLabel, label_proto});
}
samples_.push_back(std::move(sample_proto));
}
void ProfileBuilder::AddSampleType(StringId type, StringId unit) {
std::string sample_type_proto =
absl::StrCat(VarintField{ValueTypeProto::kType, Varint(type)},
VarintField{ValueTypeProto::kUnit, Varint(unit)});
sample_types_.push_back(std::move(sample_type_proto));
}
MappingId ProfileBuilder::AddMapping(uintptr_t memory_start,
uintptr_t memory_limit,
uintptr_t file_offset,
absl::string_view filename,
absl::string_view build_id) {
size_t index = mappings_.size() + 1;
auto [it, inserted] = mapping_table_.emplace(memory_start, index);
if (!inserted) {
return static_cast<MappingId>(it->second);
}
Mapping m;
m.start = memory_start;
m.limit = memory_limit;
m.offset = file_offset;
m.filename = std::string(filename);
m.build_id = std::string(build_id);
mappings_.push_back(std::move(m));
return static_cast<MappingId>(index);
}
std::string ProfileBuilder::Emit() && {
std::string profile_proto;
for (const auto& sample_type : sample_types_) {
absl::StrAppend(&profile_proto,
LengthDelimited{ProfileProto::kSampleType, sample_type});
}
for (const auto& sample : samples_) {
absl::StrAppend(&profile_proto,
LengthDelimited{ProfileProto::kSample, sample});
}
for (size_t i = 0, n = mappings_.size(); i < n; ++i) {
const auto& mapping = mappings_[i];
std::string mapping_proto = absl::StrCat(
VarintField{MappingProto::kId, Varint(static_cast<uint64_t>(i + 1))},
VarintField{MappingProto::kMemoryStart, Varint(mapping.start)},
VarintField{MappingProto::kMemoryLimit, Varint(mapping.limit)},
VarintField{MappingProto::kFileOffset, Varint(mapping.offset)},
VarintField{MappingProto::kFilename,
Varint(InternString(mapping.filename))},
VarintField{MappingProto::kBuildId,
Varint(InternString(mapping.build_id))});
absl::StrAppend(&profile_proto,
LengthDelimited{ProfileProto::kMapping, mapping_proto});
}
for (const auto& [address, id] : location_table_) {
std::string location =
absl::StrCat(VarintField{LocationProto::kId, Varint(id)},
VarintField{LocationProto::kAddress, Varint(address)});
if (!mappings_.empty()) {
auto it = mapping_table_.upper_bound(address);
if (it != mapping_table_.begin()) {
--it;
}
const size_t mapping_index = it->second;
const Mapping& mapping = mappings_[mapping_index - 1];
if (it->first <= address && address < mapping.limit) {
absl::StrAppend(
&location,
VarintField{LocationProto::kMappingId,
Varint(static_cast<uint64_t>(mapping_index))});
}
}
absl::StrAppend(&profile_proto,
LengthDelimited{ProfileProto::kLocation, location});
}
std::string string_table_proto;
std::vector<absl::string_view> sorted_strings(string_table_.size());
for (const auto& p : string_table_) {
sorted_strings[static_cast<size_t>(p.second)] = p.first;
}
for (const auto& s : sorted_strings) {
absl::StrAppend(&string_table_proto,
LengthDelimited{ProfileProto::kStringTable, s});
}
absl::StrAppend(&profile_proto, VarintField{ProfileProto::kDropFrames,
Varint(drop_frames_id_)});
absl::StrAppend(&profile_proto,
VarintField{ProfileProto::kComment, Varint(comment_id_)});
absl::StrAppend(&profile_proto, VarintField{ProfileProto::kDefaultSampleType,
Varint(default_sample_type_id_)});
return absl::StrCat(string_table_proto, profile_proto);
}
void ProfileBuilder::set_drop_frames_id(StringId drop_frames_id) {
drop_frames_id_ = drop_frames_id;
}
void ProfileBuilder::set_comment_id(StringId comment_id) {
comment_id_ = comment_id;
}
void ProfileBuilder::set_default_sample_type_id(
StringId default_sample_type_id) {
default_sample_type_id_ = default_sample_type_id;
}
void ProfileBuilder::AddCurrentMappings() {
#ifdef __linux__
dl_iterate_phdr(
+[](dl_phdr_info* info, size_t, void* data) {
auto& builder = *reinterpret_cast<ProfileBuilder*>(data);
if (info->dlpi_phdr == nullptr && info->dlpi_phnum == 0) {
return 0;
}
const bool is_main_executable = builder.mappings_.empty();
char self_filename[PATH_MAX + 1];
const char* filename = info->dlpi_name;
if (filename == nullptr || filename[0] == '\0') {
if (is_main_executable) {
ssize_t ret = readlink("/proc/self/exe", self_filename,
sizeof(self_filename) - 1);
if (ret >= 0 && static_cast<size_t>(ret) < sizeof(self_filename)) {
self_filename[ret] = '\0';
filename = self_filename;
}
} else {
filename = GetSoName(info);
}
}
char resolved_path[PATH_MAX];
absl::string_view resolved_filename;
if (realpath(filename, resolved_path)) {
resolved_filename = resolved_path;
} else {
resolved_filename = filename;
}
const std::string build_id = GetBuildId(info);
for (int i = 0; i < info->dlpi_phnum; ++i) {
if (info->dlpi_phdr[i].p_type != PT_LOAD) {
continue;
}
const ElfW(Phdr)* pt_load = &info->dlpi_phdr[i];
ABSL_RAW_CHECK(pt_load != nullptr, "Unexpected nullptr");
const size_t memory_start = info->dlpi_addr + pt_load->p_vaddr;
const size_t memory_limit = memory_start + pt_load->p_memsz;
const size_t file_offset = pt_load->p_offset;
builder.AddMapping(memory_start, memory_limit, file_offset,
resolved_filename, build_id);
}
return 0;
},
this);
#endif }
} ABSL_NAMESPACE_END
}