object-introspection/oi/OICodeGen.cpp
Jake Hillion a6d74a20a6 Update to clang/llvm 15 (#342)
Summary:

Update to clang-15 compiler and libraries as clang-12 is ancient.

The changes to oilgen are necessary because the new internal toolchain is being more picky about linking PIC to PIC. In certain modes we build with PIC, but try to link a non-PIC oilgen artifact. Add the ability to build the oilgen artifacts with PIC which sorts this.

Reviewed By: ttreyer

Differential Revision: D46220858
2023-09-14 06:02:32 -07:00

3777 lines
113 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 "oi/OICodeGen.h"
#include <folly/SharedMutex.h>
#include <glog/logging.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/regex.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/format.hpp>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <map>
#include <set>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include "oi/DrgnUtils.h"
#include "oi/FuncGen.h"
#include "oi/OIParser.h"
#include "oi/PaddingHunter.h"
#include "oi/SymbolService.h"
namespace fs = std::filesystem;
namespace oi::detail {
static size_t g_level = 0;
#undef VLOG
#define VLOG(verboselevel) \
LOG_IF(INFO, VLOG_IS_ON(verboselevel)) << std::string(2 * g_level, ' ')
std::unique_ptr<OICodeGen> OICodeGen::buildFromConfig(const Config& c,
SymbolService& s) {
auto cg = std::unique_ptr<OICodeGen>(new OICodeGen(c, s));
for (const auto& path : c.containerConfigPaths) {
if (!cg->registerContainer(path)) {
LOG(ERROR) << "failed to register container: " << path;
return nullptr;
}
}
return cg;
}
OICodeGen::OICodeGen(const Config& c, SymbolService& s)
: config{c}, symbols{s} {
membersToStub = config.membersToStub;
for (const auto& type : typesToStub) {
membersToStub.emplace_back(type, "*");
}
// `knownTypes` has been made obsolete by the introduction of using the
// `OIInternal` namespace. Instead of deleting all of the related code
// however, for now we just define the `knownTypes` list to be empty, as this
// will make it easier to selectively revert our changes if it turns out that
// there are issues with the new approach.
knownTypes = {"IPAddress", "uintptr_t"};
sizeMap["SharedMutex"] = sizeof(folly::SharedMutex);
}
bool OICodeGen::registerContainer(const fs::path& path) {
VLOG(1) << "registering container, path: " << path;
auto info = ContainerInfo::loadFromFile(path);
if (!info) {
return false;
}
VLOG(1) << "registered container, type: " << info->typeName;
containerInfoList.emplace_back(std::move(info));
return true;
}
bool OICodeGen::isKnownType(const std::string& type) {
std::string matched;
return isKnownType(type, matched);
}
bool OICodeGen::isKnownType(const std::string& type, std::string& matched) {
if (auto it = std::ranges::find(knownTypes, type); it != knownTypes.end()) {
matched = *it;
return true;
}
if (type.rfind("allocator<", 0) == 0 ||
type.rfind("new_allocator<", 0) == 0) {
matched = type;
return true;
}
if (auto opt = isTypeToStub(type)) {
matched = opt.value();
return true;
}
return false;
}
std::optional<const std::string_view> OICodeGen::fullyQualifiedName(
drgn_type* type) {
if (auto entry = fullyQualifiedNames.find(type);
entry != fullyQualifiedNames.end()) {
return entry->second.contents;
}
char* name = nullptr;
size_t length = 0;
auto* err = drgn_type_fully_qualified_name(type, &name, &length);
if (err != nullptr || name == nullptr) {
return std::nullopt;
}
auto typeNamePair =
fullyQualifiedNames.emplace(type, DrgnString{name, length}).first;
return typeNamePair->second.contents;
}
std::optional<std::reference_wrapper<const ContainerInfo>>
OICodeGen::getContainerInfo(drgn_type* type) {
auto name = fullyQualifiedName(type);
if (!name.has_value()) {
return std::nullopt;
}
std::string nameStr = std::string(*name);
for (auto it = containerInfoList.rbegin(); it != containerInfoList.rend();
++it) {
const ContainerInfo& info = **it;
if (std::regex_search(nameStr, info.matcher)) {
return info;
}
}
return std::nullopt;
}
bool OICodeGen::isContainer(drgn_type* type) {
return getContainerInfo(type).has_value();
}
std::string OICodeGen::preProcessUniquePtr(drgn_type* type, std::string name) {
std::string typeName;
std::string deleterName;
size_t end = name.rfind('>') - 1;
size_t pos = end;
size_t begin = 0;
bool found = false;
int unmatchedTemplate = 0;
while (pos > 0) {
if (name[pos] == '>') {
unmatchedTemplate++;
}
if (name[pos] == '<') {
unmatchedTemplate--;
}
if (unmatchedTemplate == 0 && name[pos] == ',') {
begin = pos + 2;
deleterName = name.substr(begin, end - begin + 1);
size_t offset = std::string("unique_ptr<").size();
typeName = name.substr(offset, pos - offset);
found = true;
break;
}
pos--;
}
VLOG(2) << "Deleter name: " << deleterName << " typeName: " << typeName;
if (deleterName.find("default_delete") == std::string::npos && found) {
size_t typeSize = drgn_type_size(type);
constexpr size_t defaultDeleterSize = sizeof(std::unique_ptr<double>);
constexpr size_t cFunctionDeleterSize =
sizeof(std::unique_ptr<double, void (*)(double*)>);
constexpr size_t stdFunctionDeleterSize =
sizeof(std::unique_ptr<double, std::function<void(double*)>>);
if (typeSize == defaultDeleterSize) {
name.replace(begin, end - begin + 1, "default_delete<" + typeName + ">");
} else if (typeSize == cFunctionDeleterSize) {
name.replace(begin, end - begin + 1, "void(*)(" + typeName + "*)");
} else if (typeSize == stdFunctionDeleterSize) {
name.replace(begin, end - begin + 1,
"std::function<void (" + typeName + "*)>");
} else {
LOG(ERROR) << "Unhandled case, unique_ptr size: " << typeSize;
}
}
return name;
}
void OICodeGen::prependQualifiers(enum drgn_qualifiers qualifiers,
std::string& sb) {
// const qualifier is the only one we're interested in
if ((qualifiers & DRGN_QUALIFIER_CONST) != 0) {
sb += "const ";
}
}
void OICodeGen::removeTemplateParamAtIndex(std::vector<std::string>& params,
const size_t index) {
if (index < params.size()) {
params.erase(params.begin() + index);
}
}
std::string OICodeGen::stripFullyQualifiedName(
const std::string& fullyQualifiedName) {
std::vector<std::string> lines;
boost::iter_split(lines, fullyQualifiedName, boost::first_finder("::"));
return lines[lines.size() - 1];
}
std::string OICodeGen::stripFullyQualifiedNameWithSeparators(
const std::string& fullyQualifiedName) {
std::vector<std::string> stack;
std::string sep = " ,<>()";
std::string tmp;
constexpr std::string_view cond = "conditional_t";
constexpr std::string_view stdCond = "std::conditional_t";
static int cond_t_val = 0;
if ((fullyQualifiedName.starts_with(cond)) ||
(fullyQualifiedName.starts_with(stdCond))) {
return "conditional_t_" + std::to_string(cond_t_val++);
}
for (auto& c : fullyQualifiedName) {
if (sep.find(c) == std::string::npos) {
stack.push_back(std::string(1, c));
} else {
tmp = "";
while (!stack.empty() &&
sep.find(stack[stack.size() - 1]) == std::string::npos) {
tmp = stack[stack.size() - 1] + tmp;
stack.pop_back();
}
tmp = stripFullyQualifiedName(tmp);
stack.push_back(tmp);
if (c == '>' || c == ')') {
std::string findStr = (c == '>') ? "<" : "(";
tmp = "";
while (!stack.empty() && stack[stack.size() - 1] != findStr) {
tmp = stack[stack.size() - 1] + tmp;
stack.pop_back();
}
if (stack.empty())
return "";
// Pop the '<' or '('
stack.pop_back();
if (c == '>') {
stack.push_back("<" + tmp + ">");
} else {
stack.push_back("(" + tmp + ")");
}
} else {
stack.push_back(std::string(1, c));
}
}
}
tmp = "";
for (auto& e : stack) {
tmp += e;
}
return tmp;
}
// Replace a specific template parameter with a generic DummySizedOperator
void OICodeGen::replaceTemplateOperator(
TemplateParamList& template_params,
std::vector<std::string>& template_params_strings,
size_t index) {
if (index >= template_params.size()) {
// Should this happen?
return;
}
size_t comparatorSz = 1;
drgn_type* cmpType = template_params[index].first.type;
if (isDrgnSizeComplete(cmpType)) {
comparatorSz = drgn_type_size(cmpType);
} else {
// Don't think there is anyway to accurately get data from the container
}
// Alignment is input to alignas. alignas(0) is supposed to be ignored.
// So we can safely specify that if we can't query the alignment requirement.
size_t alignment = 0;
if (drgn_type_has_members(cmpType) && drgn_type_num_members(cmpType) == 0) {
comparatorSz = 0;
} else {
auto alignmentOpt = getAlignmentRequirements(cmpType);
if (!alignmentOpt.has_value()) {
// Not sure when/if this would happen. Just log for now.
LOG(ERROR) << "Failed to get alignment for " << cmpType;
} else {
alignment = *alignmentOpt / CHAR_BIT;
}
}
template_params_strings[index] = "DummySizedOperator<" +
std::to_string(comparatorSz) + "," +
std::to_string(alignment) + ">";
}
void OICodeGen::replaceTemplateParameters(
drgn_type* type,
TemplateParamList& template_params,
std::vector<std::string>& template_params_strings,
const std::string& nameWithoutTemplate) {
auto optContainerInfo = getContainerInfo(type);
if (!optContainerInfo.has_value()) {
LOG(ERROR) << "Unknown container type: " << nameWithoutTemplate;
return;
}
const ContainerInfo& containerInfo = *optContainerInfo;
// Some containers will need special handling
if (nameWithoutTemplate == "bimap<") {
// TODO: The end parameters seem to be wild cards to pass in things like
// allocator or passing complex relationships between the keys.
removeTemplateParamAtIndex(template_params_strings, 4);
removeTemplateParamAtIndex(template_params_strings, 3);
removeTemplateParamAtIndex(template_params_strings, 2);
} else {
for (auto const& index : containerInfo.replaceTemplateParamIndex) {
replaceTemplateOperator(template_params, template_params_strings, index);
}
if (containerInfo.allocatorIndex) {
removeTemplateParamAtIndex(template_params_strings,
*containerInfo.allocatorIndex);
}
}
}
bool OICodeGen::buildName(drgn_type* type,
std::string& text,
std::string& outName) {
int ptrDepth = 0;
drgn_type* ut = type;
while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) {
ut = drgn_type_type(ut).type;
ptrDepth++;
}
size_t pos = text.find('<');
if (pos == std::string::npos) {
outName = text;
return false;
}
std::string nameWithoutTemplate = text.substr(0, pos + 1);
std::string tmpName;
if (!buildNameInt(ut, nameWithoutTemplate, tmpName)) {
return false;
}
outName = tmpName + std::string(ptrDepth, '*');
return true;
}
bool OICodeGen::buildNameInt(drgn_type* type,
std::string& nameWithoutTemplate,
std::string& outName) {
// Calling buildName only makes sense if a type is a container and has
// template parameters. For a generic template class, we just flatten the
// name into anything reasonable (e.g. Foo<int> becomes Foo_int_).
if (!isContainer(type) || !drgn_type_has_template_parameters(type)) {
return false;
}
size_t numTemplateParams = drgn_type_num_template_parameters(type);
if (numTemplateParams == 0) {
return false;
}
TemplateParamList templateParams;
if (!getTemplateParams(type, numTemplateParams, templateParams)) {
LOG(ERROR) << "Failed to get template params";
return false;
}
if (templateParams.size() == 0) {
LOG(ERROR) << "Template parameters missing";
}
std::vector<std::string> templateParamsStrings;
for (size_t i = 0; i < templateParams.size(); ++i) {
auto& [p, value] = templateParams[i];
enum drgn_qualifiers qualifiers = p.qualifiers;
prependQualifiers(qualifiers, outName);
std::string templateParamName;
if (!value.empty()) {
if (drgn_type_kind(p.type) == DRGN_TYPE_ENUM) {
/*
* We must reference scoped enums by fully-qualified name to keep the
* C++ compiler happy.
*
* i.e. we want to end up with:
* isset_bitset<1,
* apache::thrift::detail::IssetBitsetOption::Unpacked>
*
* If we instead just used the enum's value, we'd instead have:
* isset_bitset<1, 0>
* However, this implicit conversion from an integer is not valid for
* C++ scoped enums.
*
* Unscoped enums (C-style enums) do not need this special treatment as
* they are implicitly convertible to their integral values.
* Conveniently for us, drgn returns them to us as DRGN_TYPE_INT so they
* are not processed here.
*/
// TODO Better handling of this enumVal?
// We converted from a numeric value into a string earlier and now back
// into a numeric value here.
// Does this indexing work correctly for signed-integer-valued enums?
//
// This code only applies to containers with an enum template parameter,
// of which we only currently have one, so I think these problems don't
// matter for now.
auto enumVal = std::stoull(value);
drgn_type_enumerator* enumerators = drgn_type_enumerators(p.type);
templateParamName = *fullyQualifiedName(p.type);
templateParamName += "::";
templateParamName += enumerators[enumVal].name;
} else {
// We can just directly use the value for other literals
templateParamName = value;
}
} else if (auto it = typeToNameMap.find(p.type);
it != typeToNameMap.end()) {
// Use the type's allocated name if it exists
templateParamName = it->second;
} else if (drgn_type_has_tag(p.type)) {
const char* templateParamNameChr = drgn_type_tag(p.type);
if (templateParamNameChr != nullptr) {
templateParamName = std::string(templateParamNameChr);
} else {
return false;
}
} else if (drgn_type_has_name(p.type)) {
const char* templateParamNameChr = drgn_type_name(p.type);
if (templateParamNameChr != nullptr) {
templateParamName = std::string(templateParamNameChr);
} else {
return false;
}
} else if (drgn_type_kind(p.type) == DRGN_TYPE_FUNCTION) {
char* defStr = nullptr;
if (drgn_format_type_name(p, &defStr)) {
LOG(ERROR) << "Failed to get formatted string for " << p.type;
templateParamName = std::string("");
} else {
templateParamName = defStr;
free(defStr);
}
} else if (drgn_type_kind(p.type) == DRGN_TYPE_POINTER) {
char* defStr = nullptr;
if (drgn_format_type_name(p, &defStr)) {
LOG(ERROR) << "Failed to get formatted string for " << p.type;
templateParamName = std::string("");
} else {
drgn_qualified_type underlyingType = drgn_type_type(p.type);
templateParamName = defStr;
free(defStr);
if (drgn_type_kind(underlyingType.type) == DRGN_TYPE_FUNCTION) {
size_t index = templateParamName.find(" ");
if (index != std::string::npos) {
templateParamName = stripFullyQualifiedNameWithSeparators(
templateParamName.substr(0, index + 1)) +
"(*)" +
stripFullyQualifiedNameWithSeparators(
templateParamName.substr(index + 1));
}
}
}
} else if (drgn_type_kind(p.type) == DRGN_TYPE_VOID) {
templateParamName = std::string("void");
} else if (drgn_type_kind(p.type) == DRGN_TYPE_ARRAY) {
size_t elems = 1;
drgn_type* arrayElementType = nullptr;
drgn_utils::getDrgnArrayElementType(p.type, &arrayElementType, elems);
if (drgn_type_has_name(arrayElementType)) {
templateParamName = drgn_type_name(arrayElementType);
} else {
LOG(ERROR) << "Failed4 to get typename ";
return false;
}
} else {
LOG(ERROR) << "Failed3 to get typename ";
return false;
}
// templateParamName = templateTransformType(templateParamName);
templateParamName =
stripFullyQualifiedNameWithSeparators(templateParamName);
std::string recursiveParam;
if (p.type != nullptr &&
buildName(p.type, templateParamName, recursiveParam)) {
templateParamName = recursiveParam;
}
templateParamsStrings.push_back(templateParamName);
}
replaceTemplateParameters(type, templateParams, templateParamsStrings,
nameWithoutTemplate);
outName = nameWithoutTemplate;
for (size_t i = 0; i < templateParamsStrings.size(); ++i) {
auto& [p, value] = templateParams[i];
enum drgn_qualifiers qualifiers = p.qualifiers;
prependQualifiers(qualifiers, outName);
outName += templateParamsStrings[i];
if (i != templateParamsStrings.size() - 1) {
outName += ", ";
} else {
outName += " >";
}
}
return true;
}
bool OICodeGen::getTemplateParams(
drgn_type* type,
size_t numTemplateParams,
std::vector<std::pair<drgn_qualified_type, std::string>>& v) {
drgn_type_template_parameter* tParams = drgn_type_template_parameters(type);
for (size_t i = 0; i < numTemplateParams; ++i) {
const drgn_object* obj = nullptr;
if (auto* err = drgn_template_parameter_object(&tParams[i], &obj);
err != nullptr) {
LOG(ERROR) << "Error when looking up template parameter " << err->code
<< " " << err->message;
drgn_error_destroy(err);
return false;
}
std::string value;
drgn_qualified_type t{};
if (obj == nullptr) {
if (auto* err = drgn_template_parameter_type(&tParams[i], &t);
err != nullptr) {
LOG(ERROR) << "Error when looking up template parameter " << err->code
<< " " << err->message;
drgn_error_destroy(err);
return false;
}
} else {
t.type = obj->type;
t.qualifiers = obj->qualifiers;
if (obj->encoding == DRGN_OBJECT_ENCODING_BUFFER) {
uint64_t size = drgn_object_size(obj);
char* buf = nullptr;
if (size <= sizeof(obj->value.ibuf)) {
buf = (char*)&(obj->value.ibuf);
} else {
buf = obj->value.bufp;
}
if (buf != nullptr) {
value = std::string(buf);
}
} else if (obj->encoding == DRGN_OBJECT_ENCODING_SIGNED) {
value = std::to_string(obj->value.svalue);
} else if (obj->encoding == DRGN_OBJECT_ENCODING_UNSIGNED) {
value = std::to_string(obj->value.uvalue);
} else if (obj->encoding == DRGN_OBJECT_ENCODING_FLOAT) {
value = std::to_string(obj->value.fvalue);
} else {
LOG(ERROR) << "Unsupported OBJ encoding for getting template parameter";
return false;
}
}
v.push_back({t, value});
}
return true;
}
std::string OICodeGen::transformTypeName(drgn_type* type, std::string& text) {
std::string tmp = stripFullyQualifiedNameWithSeparators(text);
boost::regex re{"<typename\\s(\\w+)>"};
std::string fmt{"<typename ::\\1>"};
std::string output = boost::regex_replace(tmp, re, fmt);
std::string buildNameOutput;
if (!buildName(type, text, buildNameOutput)) {
buildNameOutput = output;
}
if (buildNameOutput.starts_with("unique_ptr")) {
buildNameOutput = preProcessUniquePtr(type, buildNameOutput);
}
return buildNameOutput;
}
bool OICodeGen::getContainerTemplateParams(drgn_type* type, bool& ifStub) {
if (containerTypeMapDrgn.find(type) != containerTypeMapDrgn.end()) {
return true;
}
std::string typeName;
if (drgn_type_has_tag(type)) {
typeName = drgn_type_tag(type);
} else if (drgn_type_has_name(type)) {
typeName = drgn_type_name(type);
} else {
LOG(ERROR) << "Failed1 to get typename: kind " << drgnKindStr(type);
return false;
}
typeName = transformTypeName(type, typeName);
auto optContainerInfo = getContainerInfo(type);
if (!optContainerInfo.has_value()) {
LOG(ERROR) << "Unknown container type: " << typeName;
return false;
}
const ContainerInfo& containerInfo = *optContainerInfo;
std::vector<size_t> paramIdxs;
if (containerInfo.underlyingContainerIndex.has_value()) {
if (containerInfo.numTemplateParams.has_value()) {
LOG(ERROR) << "Container adapters should not enumerate their template "
"parameters";
return false;
}
paramIdxs.push_back(*containerInfo.underlyingContainerIndex);
} else {
auto numTemplateParams = containerInfo.numTemplateParams;
if (!numTemplateParams.has_value()) {
if (!drgn_type_has_template_parameters(type)) {
LOG(ERROR) << "Failed to find template params";
return false;
}
numTemplateParams = drgn_type_num_template_parameters(type);
VLOG(1) << "Num template params for " << typeName << " "
<< *numTemplateParams;
}
for (size_t i = 0; i < *numTemplateParams; ++i) {
paramIdxs.push_back(i);
}
}
return enumerateTemplateParamIdxs(type, containerInfo, paramIdxs, ifStub);
}
bool OICodeGen::enumerateTemplateParamIdxs(drgn_type* type,
const ContainerInfo& containerInfo,
const std::vector<size_t>& paramIdxs,
bool& ifStub) {
if (paramIdxs.empty()) {
// Containers such as IOBuf and IOBufQueue don't have template params, but
// still should be added to containerTypeMap
containerTypeMapDrgn.emplace(
type,
std::pair(std::ref(containerInfo), std::vector<drgn_qualified_type>()));
return true;
}
auto maxParamIdx = *std::max_element(paramIdxs.begin(), paramIdxs.end());
if (!drgn_type_has_template_parameters(type) ||
drgn_type_num_template_parameters(type) <= maxParamIdx) {
LOG(ERROR) << "Failed to find template params";
return false;
}
drgn_type_template_parameter* tParams = drgn_type_template_parameters(type);
for (auto i : paramIdxs) {
drgn_qualified_type t{};
if (auto* err = drgn_template_parameter_type(&tParams[i], &t);
err != nullptr) {
LOG(ERROR) << "Error when looking up template parameter " << err->code
<< " " << err->message;
return false;
}
if (drgn_type_kind(t.type) != DRGN_TYPE_VOID &&
!isDrgnSizeComplete(t.type)) {
ifStub = true;
return true;
}
}
for (auto i : paramIdxs) {
drgn_qualified_type t{};
if (auto* err = drgn_template_parameter_type(&tParams[i], &t);
err != nullptr) {
LOG(ERROR) << "Error when looking up template parameter " << err->code
<< " " << err->message;
drgn_error_destroy(err);
return false;
}
// TODO: This is painful, there seems to be a bug in drgn (or maybe it is
// intentional). Consider a case :-
// typedef struct {int a;} A;
// unique_ptr<A> ptr;
//
// If you query template parameter type of unique_ptr<A>, it skips typdefs
// and directly seems to return underlying type i.e. an unnamed struct.
// Unfortunately, we might need special handling for this.
if (!OICodeGen::enumerateTypesRecurse(t.type)) {
return false;
}
}
// Do not add `shared_ptr<void>`, `unique_ptr<void>`, or `weak_ptr<void>`
// to `containerTypeMapDrgn`.
if (containerInfo.ctype == SHRD_PTR_TYPE ||
containerInfo.ctype == UNIQ_PTR_TYPE ||
containerInfo.ctype == WEAK_PTR_TYPE) {
drgn_qualified_type t{};
// We checked that this succeeded in the previous loop
drgn_template_parameter_type(&tParams[0], &t);
if (drgn_type_kind(t.type) == DRGN_TYPE_VOID) {
return true;
}
}
auto& templateTypes =
containerTypeMapDrgn
.emplace(type, std::pair(std::ref(containerInfo),
std::vector<drgn_qualified_type>()))
.first->second.second;
for (auto i : paramIdxs) {
drgn_qualified_type t{};
drgn_error* err = drgn_template_parameter_type(&tParams[i], &t);
if (err) {
LOG(ERROR) << "Error when looking up template parameter " << err->code
<< " " << err->message;
return false;
}
// TODO: Any reason this can't be done in the prior loop. Then
// this loop can be deleted
templateTypes.push_back(t);
}
return true;
}
void OICodeGen::addPaddingForBaseClass(drgn_type* type,
std::vector<std::string>& def) {
if (drgn_type_num_members(type) < 1) {
return;
}
drgn_type_member* members = drgn_type_members(type);
VLOG(2) << "Base member offset is " << members[0].bit_offset / CHAR_BIT;
uint64_t byteOffset = members[0].bit_offset / CHAR_BIT;
if (byteOffset > 0) {
std::string memName =
std::string("__") + "parentClass[" + std::to_string(byteOffset) + "]";
std::string tname = "uint8_t";
def.push_back(tname);
def.push_back(memName);
def.push_back(";");
}
}
std::string_view OICodeGen::drgnKindStr(drgn_type* type) {
switch (drgn_type_kind(type)) {
case DRGN_TYPE_VOID:
return "DRGN_TYPE_VOID";
case DRGN_TYPE_INT:
return "DRGN_TYPE_INT";
case DRGN_TYPE_BOOL:
return "DRGN_TYPE_BOOL";
case DRGN_TYPE_FLOAT:
return "DRGN_TYPE_FLOAT";
case DRGN_TYPE_STRUCT:
return "DRGN_TYPE_STRUCT";
case DRGN_TYPE_UNION:
return "DRGN_TYPE_UNION";
case DRGN_TYPE_CLASS:
return "DRGN_TYPE_CLASS";
case DRGN_TYPE_ENUM:
return "DRGN_TYPE_ENUM";
case DRGN_TYPE_TYPEDEF:
return "DRGN_TYPE_TYPEDEF";
case DRGN_TYPE_POINTER:
return "DRGN_TYPE_POINTER";
case DRGN_TYPE_ARRAY:
return "DRGN_TYPE_ARRAY";
case DRGN_TYPE_FUNCTION:
return "DRGN_TYPE_FUNCTION";
}
return "";
}
std::string OICodeGen::getAnonName(drgn_type* type, const char* template_) {
std::string typeName;
if (drgn_type_tag(type) != nullptr) {
typeName = drgn_type_tag(type);
} else {
// Unnamed struct/union
if (auto it = unnamedUnion.find(type); it != std::end(unnamedUnion)) {
typeName = it->second;
} else {
typeName = template_ + std::to_string(unnamedUnion.size());
unnamedUnion.emplace(type, typeName);
// Leak a copy of the typeName to ensure its lifetime is greater than the
// drgn_type
char* typeNameCstr = new char[typeName.size() + 1];
std::copy(std::begin(typeName), std::end(typeName), typeNameCstr);
typeNameCstr[typeName.size()] = '\0';
type->_private.tag = typeNameCstr;
// We might need a second copy of the string to avoid double-free
type->_private.oi_name = typeNameCstr;
}
}
return transformTypeName(type, typeName);
}
bool OICodeGen::getMemberDefinition(drgn_type* type) {
// Do a [] lookup to ensure `type` has a entry in classMembersMap
// If it has no entry, the lookup will default construct on for us
classMembersMap[type];
structDefType.push_back(type);
std::string outName;
getDrgnTypeNameInt(type, outName);
VLOG(1) << "Adding members to class " << outName << " " << type;
// If the type is a union, don't expand the members
if (drgn_type_kind(type) == DRGN_TYPE_UNION) {
return true;
}
drgn_type_member* members = drgn_type_members(type);
for (size_t i = 0; i < drgn_type_num_members(type); ++i) {
auto& member = members[i];
auto memberName = member.name ? std::string(member.name)
: "__anon_member_" + std::to_string(i);
drgn_qualified_type t{};
uint64_t bitFieldSize = 0;
if (auto* err = drgn_member_type(&member, &t, &bitFieldSize);
err != nullptr) {
LOG(ERROR) << "Error when looking up member type '" << memberName
<< "': (" << err->code << ") " << err->message;
drgn_error_destroy(err);
continue;
}
if (drgn_type_kind(t.type) == DRGN_TYPE_FUNCTION) {
continue;
}
std::string tname;
getDrgnTypeNameInt(t.type, tname);
isKnownType(tname, tname);
bool isStubbed = isMemberToStub(outName, memberName).has_value();
VLOG(2) << "Adding member; type: " << tname << " " << type
<< " name: " << memberName << " offset(bits): " << member.bit_offset
<< " offset(bytes): " << (float)member.bit_offset / (float)CHAR_BIT
<< " isStubbed: " << (isStubbed ? "true" : "false");
classMembersMap[type].push_back(DrgnClassMemberInfo{
t.type, memberName, member.bit_offset, bitFieldSize, isStubbed});
}
return true;
}
std::string OICodeGen::typeToTransformedName(drgn_type* type) {
auto typeName = drgn_utils::typeToName(type);
typeName = transformTypeName(type, typeName);
return typeName;
}
bool OICodeGen::recordChildren(drgn_type* type) {
drgn_type_template_parameter* parents = drgn_type_parents(type);
for (size_t i = 0; i < drgn_type_num_parents(type); i++) {
drgn_qualified_type t{};
if (auto* err = drgn_template_parameter_type(&parents[i], &t);
err != nullptr) {
LOG(ERROR) << "Error when looking up parent class for type " << type
<< " err " << err->code << " " << err->message;
drgn_error_destroy(err);
continue;
}
drgn_type* parent = drgn_utils::underlyingType(t.type);
if (!isDrgnSizeComplete(parent)) {
VLOG(1) << "Incomplete size for parent class (" << drgn_type_tag(parent)
<< ") of " << drgn_type_tag(type);
continue;
}
const char* parentName = drgn_type_tag(parent);
if (parentName == nullptr) {
VLOG(1) << "No name for parent class (" << parent << ") of "
<< drgn_type_tag(type);
continue;
}
/*
* drgn pointers are not stable, so use string representation for reverse
* mapping for now. We need to find a better way of creating this
* childClasses map - ideally drgn would do this for us.
*/
childClasses[parentName].push_back(type);
VLOG(1) << drgn_type_tag(type) << "(" << type << ") is a child of "
<< drgn_type_tag(parent) << "(" << parent << ")";
}
return true;
}
/*
* Build a mapping of Class -> Children
*
* drgn only gives us the mapping Class -> Parents, so we must iterate over all
* types in the program to build the reverse mapping.
*/
bool OICodeGen::enumerateChildClasses() {
if (!feature(Feature::PolymorphicInheritance)) {
return true;
}
if ((setenv("DRGN_ENABLE_TYPE_ITERATOR", "1", 1)) < 0) {
LOG(ERROR)
<< "Could not set DRGN_ENABLE_TYPE_ITERATOR environment variable";
return false;
}
drgn_type_iterator* typesIterator;
auto* prog = symbols.getDrgnProgram();
drgn_error* err = drgn_type_iterator_create(prog, &typesIterator);
if (err) {
LOG(ERROR) << "Error initialising drgn_type_iterator: " << err->code << ", "
<< err->message;
drgn_error_destroy(err);
return false;
}
while (true) {
drgn_qualified_type* t;
err = drgn_type_iterator_next(typesIterator, &t);
if (err) {
LOG(ERROR) << "Error from drgn_type_iterator_next: " << err->code << ", "
<< err->message;
drgn_error_destroy(err);
continue;
}
if (!t) {
break;
}
auto kind = drgn_type_kind(t->type);
if (kind != DRGN_TYPE_CLASS && kind != DRGN_TYPE_STRUCT) {
continue;
}
if (!recordChildren(t->type)) {
return false;
}
}
drgn_type_iterator_destroy(typesIterator);
return true;
}
// The top level function which enumerates the rootType object. This function
// fills out : -
// 1. struct/class definitions
// 2. function forward declarations
// 3. function definitions
//
// They are appended to the jit code in that order (1), (2), (3)
bool OICodeGen::populateDefsAndDecls() {
if (!enumerateChildClasses()) {
return false;
}
if (drgn_type_has_type(rootType.type) &&
drgn_type_kind(rootType.type) == DRGN_TYPE_FUNCTION) {
rootType = drgn_type_type(rootType.type);
}
auto* type = rootType.type;
rootTypeToIntrospect = rootType;
drgn_qualified_type qtype{};
if (drgn_type_kind(type) == DRGN_TYPE_POINTER) {
qtype = drgn_type_type(type);
type = qtype.type;
rootTypeToIntrospect = qtype;
}
auto typeName = typeToTransformedName(type);
if (typeName.empty()) {
return false;
}
if (typeName == "void") {
LOG(ERROR) << "Argument to be introspected cannot be of type void";
return false;
}
rootTypeStr = typeName;
VLOG(1) << "Root type to introspect : " << rootTypeStr;
return enumerateTypesRecurse(rootType.type);
}
std::optional<uint64_t> OICodeGen::getDrgnTypeSize(drgn_type* type) {
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(type, &sz); err != nullptr) {
LOG(ERROR) << "dgn_type_sizeof(" << type << "): " << err->code << " "
<< err->message;
drgn_error_destroy(err);
auto typeName = getNameForType(type);
if (!typeName.has_value()) {
return std::nullopt;
}
for (auto& e : sizeMap) {
if (typeName->starts_with(e.first)) {
VLOG(1) << "Looked up " << *typeName << " in sizeMap";
return e.second;
}
}
LOG(ERROR) << "Failed to get size for " << *typeName << " " << type;
return std::nullopt;
}
return sz;
}
bool OICodeGen::isDrgnSizeComplete(drgn_type* type) {
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(type, &sz); err != nullptr) {
drgn_error_destroy(err);
} else {
return true;
}
if (drgn_type_kind(type) != DRGN_TYPE_ENUM) {
std::string name;
getDrgnTypeNameInt(type, name);
for (auto& kv : sizeMap) {
if (name.starts_with(kv.first)) {
return true;
}
}
VLOG(1) << "Failed to lookup size " << type << " " << name;
}
return false;
}
bool OICodeGen::enumerateClassParents(drgn_type* type,
const std::string& typeName) {
if (drgn_type_num_parents(type) == 0) {
// Be careful to early exit here to avoid accidentally initialising
// parentClasses when there are no parents.
return true;
}
drgn_type_template_parameter* parents = drgn_type_parents(type);
for (size_t i = 0; i < drgn_type_num_parents(type); ++i) {
drgn_qualified_type t{};
if (auto* err = drgn_template_parameter_type(&parents[i], &t);
err != nullptr) {
LOG(ERROR) << "Error when looking up parent class for type " << type
<< " err " << err->code << " " << err->message;
drgn_error_destroy(err);
return false;
}
VLOG(2) << "Lookup parent for " << typeName << " " << type;
if (!isDrgnSizeComplete(t.type)) {
VLOG(2) << "Parent of " << typeName << " " << type << " is " << t.type
<< " which is incomplete";
return false;
}
if (!OICodeGen::enumerateTypesRecurse(t.type)) {
return false;
}
parentClasses[type].push_back({t.type, parents[i].bit_offset});
}
std::sort(parentClasses[type].begin(), parentClasses[type].end());
return true;
}
bool OICodeGen::enumerateClassMembers(drgn_type* type,
const std::string& typeName,
bool& isStubbed) {
drgn_type_member* members = drgn_type_members(type);
for (size_t i = 0; i < drgn_type_num_members(type); ++i) {
drgn_qualified_type t{};
auto* err = drgn_member_type(&members[i], &t, nullptr);
if (err != nullptr || !isDrgnSizeComplete(t.type)) {
if (err != nullptr) {
LOG(ERROR) << "Error when looking up member type " << err->code << " "
<< err->message << " " << typeName << " " << members[i].name;
}
VLOG(1) << "Type " << typeName
<< " has an incomplete member; stubbing...";
knownDummyTypeList.insert(type);
isStubbed = true;
return true;
}
std::string memberName;
if (members[i].name != nullptr) {
memberName.assign(members[i].name);
}
if (VLOG_IS_ON(2)) {
std::string outName;
getDrgnTypeNameInt(t.type, outName);
VLOG(2) << "Processing member; type: " << outName << " " << t.type
<< " name: " << memberName;
}
if (!OICodeGen::enumerateTypesRecurse(t.type)) {
return false;
}
}
return true;
}
bool OICodeGen::enumerateClassTemplateParams(drgn_type* type,
const std::string& typeName,
bool& isStubbed) {
bool ifStub = false;
if (!getContainerTemplateParams(type, ifStub)) {
LOG(ERROR) << "Failed to get container template params";
return false;
}
if (ifStub) {
VLOG(1) << "Type " << typeName
<< " has an incomplete size member in template params; stubbing...";
knownDummyTypeList.insert(type);
isStubbed = true;
return true;
}
auto containerInfo = getContainerInfo(type);
assert(containerInfo.has_value());
containerTypesFuncDef.insert(*containerInfo);
return true;
}
bool OICodeGen::ifGenerateMemberDefinition(const std::string& typeName) {
return !isKnownType(typeName);
}
bool OICodeGen::generateMemberDefinition(drgn_type* type,
std::string& typeName) {
if (!getMemberDefinition(type)) {
return false;
}
/* Unnamed type */
if (typeName.empty()) {
if (auto it = unnamedUnion.find(type); it != unnamedUnion.end()) {
typeName = it->second;
} else {
LOG(ERROR) << "Failed to find type in unnamedUnion";
return false;
}
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(type, &sz); err != nullptr) {
LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message;
drgn_error_destroy(err);
return false;
}
}
return true;
}
std::optional<std::pair<std::string_view, std::string_view>>
OICodeGen::isMemberToStub(const std::string& typeName,
const std::string& member) {
auto it = std::ranges::find_if(membersToStub, [&](auto& s) {
return typeName.starts_with(s.first) && s.second == member;
});
if (it == std::end(membersToStub)) {
return std::nullopt;
}
return *it;
}
std::optional<std::string_view> OICodeGen::isTypeToStub(
const std::string& typeName) {
if (auto opt = isMemberToStub(typeName, "*")) {
return std::ref(opt.value().first);
}
return std::nullopt;
}
bool OICodeGen::isTypeToStub(drgn_type* type, const std::string& typeName) {
if (isTypeToStub(typeName)) {
VLOG(1) << "Found type to stub ";
knownDummyTypeList.insert(type);
return true;
}
return false;
}
bool OICodeGen::isEmptyClassOrFunctionType(drgn_type* type,
const std::string& typeName) {
return (!isKnownType(typeName) && drgn_type_has_members(type) &&
drgn_type_num_members(type) == 0);
}
/*
* Returns true if the provided type represents a dynamic class.
*
* From the Itanium C++ ABI, a dynamic class is defined as:
* A class requiring a virtual table pointer (because it or its bases have
* one or more virtual member functions or virtual base classes).
*/
bool OICodeGen::isDynamic(drgn_type* type) const {
if (!feature(Feature::PolymorphicInheritance) ||
!drgn_type_has_virtuality(type)) {
return false;
}
if (drgn_type_virtuality(type) != 0 /*DW_VIRTUALITY_none*/) {
// Virtual class - not fully supported by OI yet
return true;
}
drgn_type_member_function* functions = drgn_type_functions(type);
for (size_t i = 0; i < drgn_type_num_functions(type); i++) {
drgn_qualified_type t{};
if (auto* err = drgn_member_function_type(&functions[i], &t)) {
LOG(ERROR) << "Error when looking up member function for type " << type
<< " err " << err->code << " " << err->message;
drgn_error_destroy(err);
continue;
}
if (drgn_type_virtuality(t.type) != 0 /*DW_VIRTUALITY_none*/) {
// Virtual function
return true;
}
}
return false;
}
bool OICodeGen::enumerateClassType(drgn_type* type) {
std::string typeName = getStructName(type);
VLOG(2) << "Class name : " << typeName << " " << type;
if (isTypeToStub(type, typeName)) {
return true;
}
if (isKnownType(typeName)) {
funcDefTypeList.insert(type);
return true;
}
if (!isContainer(type)) {
if (!enumerateClassParents(type, typeName)) {
knownDummyTypeList.insert(type);
return true;
}
bool isStubbed = false;
VLOG(2) << "Processing members for class/union : " << typeName << " "
<< type;
if (!enumerateClassMembers(type, typeName, isStubbed)) {
return false;
}
if (isStubbed) {
return true;
}
}
if (isContainer(type)) {
bool isStubbed = false;
VLOG(1) << "Processing template params container: " << typeName << " "
<< type;
if (!enumerateClassTemplateParams(type, typeName, isStubbed)) {
return false;
}
if (isStubbed) {
return true;
}
} else if (ifGenerateMemberDefinition(typeName)) {
if (isDynamic(type)) {
const auto& children = childClasses[drgn_type_tag(type)];
for (const auto& child : children) {
enumerateTypesRecurse(child);
}
}
if (!generateMemberDefinition(type, typeName)) {
return false;
}
} else if (isEmptyClassOrFunctionType(type, typeName)) {
knownDummyTypeList.insert(type);
VLOG(2) << "Empty class/function type " << type << " name " << typeName;
return true;
}
funcDefTypeList.insert(type);
return true;
}
bool OICodeGen::enumerateTypeDefType(drgn_type* type) {
std::string typeName;
if (drgn_type_has_name(type)) {
typeName = drgn_type_name(type);
}
typeName = transformTypeName(type, typeName);
VLOG(2) << "Transformed typename: " << typeName << " " << type;
if (isTypeToStub(type, typeName)) {
return true;
}
if (isKnownType(typeName)) {
funcDefTypeList.insert(type);
return true;
}
auto qtype = drgn_type_type(type);
bool ret = enumerateTypesRecurse(qtype.type);
std::string tname;
if (drgn_type_has_tag(qtype.type)) {
if (drgn_type_tag(qtype.type) != nullptr) {
tname = drgn_type_tag(qtype.type);
} else {
if (drgn_type_kind(qtype.type) == DRGN_TYPE_UNION) {
tname = getUnionName(qtype.type);
} else {
tname = getStructName(qtype.type);
}
typeName = tname;
funcDefTypeList.insert(type);
}
} else if (drgn_type_has_name(qtype.type)) {
tname = drgn_type_name(qtype.type);
} else {
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(type, &sz); err != nullptr) {
LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message
<< " " << typeName;
drgn_error_destroy(err);
return false;
}
if (sz == sizeof(uintptr_t)) {
// This is a typedef'ed pointer
tname = "uintptr_t";
} else {
LOG(ERROR) << "Failed to get typename: kind " << drgnKindStr(type);
return false;
}
}
typedefMap[typeName] = transformTypeName(qtype.type, tname);
typedefTypes[type] = qtype.type;
return ret;
}
bool OICodeGen::enumerateEnumType(drgn_type* type) {
std::string typeName;
if (drgn_type_tag(type) != nullptr) {
typeName.assign(drgn_type_tag(type));
} else {
typeName = "UNNAMED";
}
if (isKnownType(typeName)) {
return true;
}
std::string enumUnderlyingTypeStr;
if (!getEnumUnderlyingTypeStr(type, enumUnderlyingTypeStr)) {
return false;
}
VLOG(2) << "Converting enum " << enumUnderlyingTypeStr << " " << type
<< " tag: " << typeName;
enumTypes.push_back(type);
funcDefTypeList.insert(type);
return true;
}
static drgn_type* getPtrUnderlyingType(drgn_type* type) {
drgn_type* underlyingType = type;
while (drgn_type_kind(underlyingType) == DRGN_TYPE_POINTER ||
drgn_type_kind(underlyingType) == DRGN_TYPE_TYPEDEF) {
underlyingType = drgn_type_type(underlyingType).type;
}
return underlyingType;
}
bool OICodeGen::enumeratePointerType(drgn_type* type) {
// Not handling pointers right now. Pointer members in classes are going to be
// tricky. If we enumerate objects from pointers there are many questions :-
// 1. How to handle uninitialized pointers
// 2. How to handle nullptr pointers
// 3. How to handle cyclical references with pointers
// Very common for two structs/classes to have pointers to each other
// We will need to save previously encountered pointer values
// 4. Smart pointers might make it easier to detect (1)/(2)
bool ret = true;
drgn_qualified_type qtype = drgn_type_type(type);
funcDefTypeList.insert(type);
// If type is a function pointer, directly store the underlying type in
// pointerToTypeMap, so that TreeBuilder can easily replace function
// pointers with uintptr_t
drgn_type* utype = getPtrUnderlyingType(type);
if (drgn_type_kind(utype) == DRGN_TYPE_FUNCTION) {
VLOG(2) << "Type " << type << " is a function pointer to " << utype;
utype->_private.oi_name = nullptr;
pointerToTypeMap.emplace(type, utype);
return ret;
}
pointerToTypeMap.emplace(type, qtype.type);
drgn_type* underlyingType = drgn_utils::underlyingType(qtype.type);
bool isComplete = isDrgnSizeComplete(underlyingType);
if (drgn_type_kind(underlyingType) == DRGN_TYPE_FUNCTION || isComplete) {
ret = enumerateTypesRecurse(qtype.type);
} else if (!isComplete && drgn_type_kind(underlyingType) != DRGN_TYPE_VOID) {
// If the underlying type is not complete create a stub for the pointer type
knownDummyTypeList.insert(type);
}
return ret;
}
bool OICodeGen::enumeratePrimitiveType(drgn_type* type) {
std::string typeName;
if (!drgn_type_has_name(type)) {
LOG(ERROR) << "Expected type to have a name: " << type;
return false;
}
typeName = drgn_type_name(type);
typeName = transformTypeName(type, typeName);
funcDefTypeList.insert(type);
return true;
}
bool OICodeGen::enumerateArrayType(drgn_type* type) {
uint64_t ret = 0;
if (auto* err = drgn_type_sizeof(type, &ret); err != nullptr) {
LOG(ERROR) << "Error when looking up size from drgn " << err->code << " "
<< err->message << " " << std::endl;
drgn_error_destroy(err);
return false;
}
auto qtype = drgn_type_type(type);
auto rval = enumerateTypesRecurse(qtype.type);
VLOG(1) << "Array type sizeof " << rval << " len " << drgn_type_length(type);
return true;
}
bool OICodeGen::enumerateTypesRecurse(drgn_type* type) {
auto kind = drgn_type_kind(type);
if (kind == DRGN_TYPE_VOID || kind == DRGN_TYPE_FUNCTION) {
VLOG(1) << "Ignore type " << drgnKindStr(type);
return true;
}
// We don't want to generate functions for the same type twice
if (processedTypes.find(type) != processedTypes.end()) {
VLOG(2) << "Already encountered " << type;
return true;
}
std::string outName;
getDrgnTypeNameInt(type, outName);
drgn_qualified_type qtype = {type, {}};
char* defStr = nullptr;
std::string typeDefStr;
if (drgn_format_type(qtype, &defStr) != nullptr) {
LOG(ERROR) << "Failed to get formatted string for " << type;
typeDefStr.assign("unknown");
} else {
typeDefStr.assign(defStr);
free(defStr);
}
VLOG(1) << "START processing type: " << outName << " " << type << " "
<< drgnKindStr(type) << " {";
g_level += 1;
processedTypes.insert(type);
bool ret = true;
switch (kind) {
case DRGN_TYPE_CLASS:
case DRGN_TYPE_STRUCT:
case DRGN_TYPE_UNION:
ret = enumerateClassType(type);
break;
case DRGN_TYPE_ENUM:
ret = enumerateEnumType(type);
break;
case DRGN_TYPE_TYPEDEF:
ret = enumerateTypeDefType(type);
break;
case DRGN_TYPE_POINTER:
ret = enumeratePointerType(type);
break;
case DRGN_TYPE_ARRAY:
enumerateArrayType(type);
break;
case DRGN_TYPE_INT:
case DRGN_TYPE_BOOL:
case DRGN_TYPE_FLOAT:
ret = enumeratePrimitiveType(type);
break;
default:
LOG(ERROR) << "Unknown drgn type " << type;
return false;
}
g_level -= 1;
VLOG(1) << "END processing type: " << outName << " " << type << " "
<< drgnKindStr(type) << " ret:" << ret << " }";
return ret;
}
std::optional<std::string> OICodeGen::getNameForType(drgn_type* type) {
if (auto search = typeToNameMap.find(type); search != typeToNameMap.end()) {
return search->second;
}
LOG(ERROR) << "Failed to find " << type;
return std::nullopt;
}
void OICodeGen::getFuncDefClassMembers(
std::string& code,
drgn_type* type,
std::unordered_map<std::string, int>& memberNames,
bool skipPadding) {
if (drgn_type_kind(type) == DRGN_TYPE_TYPEDEF) {
// Handle case where parent is a typedef
getFuncDefClassMembers(code, drgn_utils::underlyingType(type), memberNames);
return;
}
if (parentClasses.find(type) != parentClasses.end()) {
for (auto& p : parentClasses[type]) {
// paddingIndexMap[type] already cover the parents' paddings,
// so skip the parents' padding generation to avoid double counting
getFuncDefClassMembers(code, p.type, memberNames, true);
}
}
if (classMembersMap.find(type) == classMembersMap.end()) {
return;
}
if (!skipPadding) {
auto paddingIt = paddingIndexMap.find(type);
if (paddingIt != paddingIndexMap.end()) {
const auto& paddingRange = paddingIt->second;
for (auto i = paddingRange.first; i < paddingRange.second; ++i) {
code += "SAVE_SIZE(sizeof(t.__padding_" + std::to_string(i) + "));\n";
}
}
}
const auto& members = classMembersMap[type];
bool captureThriftIsset = thriftIssetStructTypes.contains(type);
if (captureThriftIsset) {
assert(members[members.size() - 1].member_name == "__isset");
auto name = *fullyQualifiedName(type);
code += "using thrift_data = apache::thrift::TStructDataStorage<";
code += name;
code += ">;\n";
}
for (std::size_t i = 0; i < members.size(); ++i) {
if (captureThriftIsset && i < members.size() - 1) {
// Capture Thrift's isset value for each field, except __isset itself,
// which we assume comes last
assert(members[i].member_name != "__isset");
std::string issetIdxStr =
"thrift_data::isset_indexes[" + std::to_string(i) + "]";
code += "{\n";
code += " if (&thrift_data::isset_indexes != nullptr &&\n";
code += " " + issetIdxStr + " != -1) {\n";
code += " SAVE_DATA(t.__isset.get(" + issetIdxStr + "));\n";
code += " } else {\n";
code += " SAVE_DATA(-1);\n";
code += " }\n";
code += "}\n";
}
const auto& member = members[i];
std::string memberName = member.member_name;
std::replace(memberName.begin(), memberName.end(), '.', '_');
deduplicateMemberName(memberNames, memberName);
if (memberName.empty()) {
continue;
}
/*
* Check if the member is a bit field (bitFieldSize > 0).
* If it is a bit field, we can't get its address with operator&.
* If it is *NOT* a bit field, then we can print its address.
*/
if (member.bit_field_size > 0) {
code += "JLOG(\"" + memberName + " (bit_field)\\n\");\n";
} else {
code += "JLOG(\"" + memberName + " @\");\n";
code += "JLOGPTR(&t." + memberName + ");\n";
}
code += "getSizeType(t." + memberName + ", returnArg);\n";
}
}
void OICodeGen::enumerateDescendants(drgn_type* type, drgn_type* baseType) {
auto it = childClasses.find(drgn_type_tag(type));
if (it == childClasses.end()) {
return;
}
// TODO this list may end up containing duplicates
const auto& children = it->second;
descendantClasses[baseType].insert(descendantClasses[baseType].end(),
children.begin(), children.end());
for (const auto& child : children) {
enumerateDescendants(child, baseType);
}
}
void OICodeGen::getFuncDefinitionStr(std::string& code,
drgn_type* type,
const std::string& typeName) {
if (classMembersMap.find(type) == classMembersMap.end()) {
return;
}
std::string funcName = "getSizeType";
if (isDynamic(type)) {
funcName = "getSizeTypeConcrete";
}
code +=
"void " + funcName + "(const " + typeName + "& t, size_t& returnArg) {\n";
std::unordered_map<std::string, int> memberNames;
getFuncDefClassMembers(code, type, memberNames);
code += "}\n";
if (isDynamic(type)) {
enumerateDescendants(type, type);
auto it = descendantClasses.find(type);
if (it == descendantClasses.end()) {
return;
}
const auto& descendants = it->second;
std::vector<std::pair<std::string, SymbolInfo>> concreteClasses;
concreteClasses.reserve(descendants.size());
for (const auto& child : descendants) {
auto fqChildName = *fullyQualifiedName(child);
// We must split this assignment and append because the C++ standard lacks
// an operator for concatenating std::string and std::string_view...
std::string childVtableName = "vtable for ";
childVtableName += fqChildName;
auto optVtableSym = symbols.locateSymbol(childVtableName, true);
if (!optVtableSym) {
LOG(ERROR) << "Failed to find vtable address for '" << childVtableName;
LOG(ERROR) << "Falling back to non dynamic mode";
concreteClasses.clear();
break;
}
auto vtableSym = *optVtableSym;
auto oiChildName = *getNameForType(child);
concreteClasses.push_back({oiChildName, vtableSym});
}
for (const auto& child : concreteClasses) {
const auto& className = child.first;
code += "void getSizeTypeConcrete(const " + className +
"& t, size_t& returnArg);\n";
}
code += std::string("void getSizeType(const ") + typeName +
std::string("& t, size_t& returnArg) {\n");
// The Itanium C++ ABI defines that the vptr must appear at the zero-offset
// position in any class in which they are present.
code += " auto *vptr = *reinterpret_cast<uintptr_t * const *>(&t);\n";
code += " uintptr_t topOffset = *(vptr - 2);\n";
code += " uintptr_t vptrVal = reinterpret_cast<uintptr_t>(vptr);\n";
for (size_t i = 0; i < concreteClasses.size(); i++) {
// The vptr will point to *somewhere* in the vtable of this object's
// concrete class. The exact offset into the vtable can vary based on a
// number of factors, so we compare the vptr against the vtable range for
// each possible class to determine the concrete type.
//
// This works for C++ compilers which follow the GNU v3 ABI, i.e. GCC and
// Clang. Other compilers may differ.
const auto& [className, vtableSym] = concreteClasses[i];
uintptr_t vtableMinAddr = vtableSym.addr;
uintptr_t vtableMaxAddr = vtableSym.addr + vtableSym.size;
code += " if (vptrVal >= 0x" +
(boost::format("%x") % vtableMinAddr).str() + " && vptrVal < 0x" +
(boost::format("%x") % vtableMaxAddr).str() + ") {\n";
code += " SAVE_DATA(" + std::to_string(i) + ");\n";
code +=
" uintptr_t baseAddress = reinterpret_cast<uintptr_t>(&t) + "
"topOffset;\n";
code += " getSizeTypeConcrete(*reinterpret_cast<const " + className +
"*>(baseAddress), returnArg);\n";
code += " return;\n";
code += " }\n";
}
code += " SAVE_DATA(-1);\n";
code += " getSizeTypeConcrete(t, returnArg);\n";
code += "}\n";
}
}
std::string OICodeGen::templateTransformType(const std::string& typeName) {
std::string s;
s.reserve(typeName.size());
for (const auto& c : typeName) {
if (c == '<' || c == '>' || c == ',' || c == ' ' || c == ':' || c == '(' ||
c == ')' || c == '&' || c == '*' || c == '-' || c == '\'' || c == '[' ||
c == ']') {
s += '_';
} else {
s += c;
}
}
return s;
}
std::string OICodeGen::structNameTransformType(const std::string& typeName) {
std::string s;
bool prevColon = false;
s.reserve(typeName.size());
for (const auto& c : typeName) {
if (c == ':') {
if (prevColon) {
prevColon = false;
} else {
prevColon = true;
}
} else {
if (prevColon) {
s[s.size() - 1] = '_';
}
prevColon = false;
}
std::string valid_characters = " &*<>[](){},;:\n";
if (std::isalnum(c) || valid_characters.find(c) != std::string::npos) {
s += c;
} else {
s += '_';
}
}
return s;
}
void OICodeGen::memberTransformName(
std::map<std::string, std::string>& templateTransformMap,
std::string& typeName) {
std::vector<std::string> sortedTypes;
for (auto& e : templateTransformMap) {
sortedTypes.push_back(e.first);
}
std::sort(sortedTypes.begin(), sortedTypes.end(),
[](const std::string& first, const std::string& second) {
return first.size() > second.size();
});
for (auto& e : sortedTypes) {
std::string search = e;
std::string replace = templateTransformMap[e];
boost::replace_all(typeName, search, replace);
}
}
OICodeGen::SortedTypeDefMap OICodeGen::getSortedTypeDefMap(
const std::map<drgn_type*, drgn_type*>& typedefTypeMap) {
auto typeMap = typedefTypeMap;
SortedTypeDefMap typedefVec;
while (!typeMap.empty()) {
for (auto it = typeMap.cbegin(); it != typeMap.cend();) {
if (typeMap.find(it->second) == typeMap.end()) {
typedefVec.push_back(std::make_pair(it->first, it->second));
it = typeMap.erase(it);
} else {
++it;
}
}
}
return typedefVec;
}
bool OICodeGen::getEnumUnderlyingTypeStr(drgn_type* e,
std::string& enumUnderlyingTypeStr) {
std::string name;
if (drgn_type_tag(e) != nullptr) {
name.assign(drgn_type_tag(e));
} else {
VLOG(2) << "Enum tag lookup failed";
}
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(e, &sz); err != nullptr) {
LOG(ERROR) << "Error when looking up size from drgn " << err->code << " "
<< err->message << " ";
drgn_error_destroy(err);
return false;
}
VLOG(2) << "Enum " << name << " size " << sz;
switch (sz) {
case 8:
enumUnderlyingTypeStr = "uint64_t";
break;
case 4:
enumUnderlyingTypeStr = "uint32_t";
break;
case 2:
enumUnderlyingTypeStr = "uint16_t";
break;
case 1:
enumUnderlyingTypeStr = "uint8_t";
break;
default:
LOG(ERROR) << "Error invalid enum size " << name << " " << sz;
return false;
}
return true;
}
bool OICodeGen::getDrgnTypeNameInt(drgn_type* type, std::string& outName) {
std::string name;
if (drgn_type_kind(type) == DRGN_TYPE_ENUM) {
if (!getEnumUnderlyingTypeStr(type, name)) {
return false;
}
} else if (drgn_type_has_tag(type)) {
if (drgn_type_tag(type) != nullptr) {
name.assign(drgn_type_tag(type));
} else {
if (drgn_type_kind(type) == DRGN_TYPE_UNION) {
name = getUnionName(type);
} else {
name = getStructName(type);
}
}
} else if (drgn_type_has_name(type)) {
name.assign(drgn_type_name(type));
} else if (drgn_type_kind(type) == DRGN_TYPE_POINTER) {
drgn_type* underlyingType = getPtrUnderlyingType(type);
if (feature(Feature::ChaseRawPointers) &&
drgn_type_kind(underlyingType) != DRGN_TYPE_FUNCTION) {
// For pointers, figure out name for the underlying type then add
// appropriate number of '*'
{
int ptrDepth = 0;
drgn_type* ut = type;
while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) {
ut = drgn_type_type(ut).type;
ptrDepth++;
}
std::string tmpName;
if (!getDrgnTypeNameInt(ut, tmpName)) {
LOG(ERROR) << "Failed to get name for type " << type;
return false;
}
outName = tmpName + std::string(ptrDepth, '*');
return true;
}
} else {
name.assign("uintptr_t");
}
} else if (drgn_type_kind(type) == DRGN_TYPE_VOID) {
name.assign("void");
} else {
VLOG(2) << "Failed to get tag/name for type " << type;
return false;
}
name = transformTypeName(type, name);
name = structNameTransformType(name);
outName = name;
return true;
}
bool OICodeGen::getDrgnTypeName(drgn_type* type, std::string& outName) {
return getDrgnTypeNameInt(type, outName);
}
void OICodeGen::addTypeToName(drgn_type* type, std::string name) {
VLOG(2) << "Trying to assign name to type: " << name << " " << type;
if (typeToNameMap.find(type) != typeToNameMap.end()) {
VLOG(2) << "Name already assigned to type: " << name << " " << type;
return;
}
name = structNameTransformType(name);
if (nameToTypeMap.find(name) != nameToTypeMap.end()) {
VLOG(1) << "Name conflict : " << type << " " << name << " "
<< drgnKindStr(type) << " conflict with " << nameToTypeMap[name]
<< " " << drgnKindStr(nameToTypeMap[name]);
if (std::ranges::find(structDefType, type) != structDefType.end() ||
std::ranges::find(enumTypes, type) != enumTypes.end() ||
typedefTypes.find(type) != typedefTypes.end() ||
knownDummyTypeList.find(type) != knownDummyTypeList.end()) {
int tIndex = 0;
// Name clashes with another type. Attach an ID at the end and make sure
// that name isn't already present for some other drgn type.
//
while (nameToTypeMap.find(name + "__" + std::to_string(tIndex)) !=
nameToTypeMap.end()) {
tIndex++;
}
if (name != "uint8_t" && name != "uint32_t" && name != "uintptr_t") {
name = name + "__" + std::to_string(tIndex);
} else {
VLOG(1) << "Not renaming " << name;
}
}
}
VLOG(1) << "Assigned name to type: " << name << " " << type;
typeToNameMap[type] = name;
nameToTypeMap[name] = type;
}
void OICodeGen::getClassMembersIncludingParent(
drgn_type* type, std::vector<DrgnClassMemberInfo>& out) {
if (drgn_type_kind(type) == DRGN_TYPE_TYPEDEF) {
// Handle case where parent is a typedef
getClassMembersIncludingParent(drgn_utils::underlyingType(type), out);
return;
}
if (parentClasses.find(type) != parentClasses.end()) {
for (auto& parent : parentClasses[type]) {
getClassMembersIncludingParent(parent.type, out);
}
}
for (auto& mem : classMembersMap[type]) {
out.push_back(mem);
}
}
std::map<drgn_type*, std::vector<DrgnClassMemberInfo>>&
OICodeGen::getClassMembersMap() {
for (auto& e : classMembersMap) {
std::vector<DrgnClassMemberInfo> v;
getClassMembersIncludingParent(e.first, v);
classMembersMapCopy[e.first] = v;
}
for (auto& e : classMembersMapCopy) {
VLOG(1) << "ClassCopy " << e.first << std::endl;
for (auto& m : e.second) {
VLOG(1) << " " << m.type << " " << m.member_name << std::endl;
}
}
return classMembersMapCopy;
}
// 1. First iterate through structs which we have to define (i.e. structDefType
// and knownDummyTypeList) and apply templateTransformType.
// 2. After that, go through typedefList and while assigning name to type, make
// sure it is transformed (i.e. memberTransformName)
// 3. Do the same for funcDefTypeList
void OICodeGen::printAllTypes() {
VLOG(2) << "Printing all types";
VLOG(2) << "Classes";
for (auto& e : classMembersMap) {
VLOG(2) << "Class " << e.first;
auto& members = e.second;
for (auto& m : members) {
VLOG(2) << " " << m.member_name << " " << m.type;
}
}
VLOG(2) << "Struct defs ";
for (auto& e : structDefType) {
VLOG(2) << "Defined struct " << e;
}
VLOG(2) << "FuncDef structs ";
for (auto& e : funcDefTypeList) {
VLOG(2) << "FuncDef struct " << e;
}
VLOG(2) << "Dummy structs ";
for (const auto& e : knownDummyTypeList) {
VLOG(2) << "Dummy struct " << e;
}
}
void OICodeGen::printAllTypeNames() {
VLOG(2) << "Printing all type names ";
VLOG(2) << "Classes";
for (auto& e : classMembersMap) {
auto typeName = getNameForType(e.first);
if (!typeName.has_value()) {
continue;
}
VLOG(2) << "Class " << *typeName << " " << e.first;
auto& members = e.second;
for (auto& m : members) {
VLOG(2) << " " << m.member_name << " " << m.type;
}
}
for (auto& kv : unnamedUnion) {
VLOG(2) << "Unnamed union/struct " << kv.first << " " << kv.second;
}
VLOG(2) << "Structs defs ";
for (auto& e : structDefType) {
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
continue;
}
VLOG(2) << "Defined struct " << *typeName << " " << e;
}
VLOG(2) << "\nFuncDef structs ";
for (auto& e : funcDefTypeList) {
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
continue;
}
VLOG(2) << "FuncDef struct " << *typeName << " " << e;
}
VLOG(2) << "\nDummy structs ";
for (auto& e : knownDummyTypeList) {
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
continue;
}
VLOG(2) << "Dummy struct " << *typeName << " " << e;
}
}
bool OICodeGen::generateStructDef(drgn_type* e, std::string& code) {
if (classMembersMap.find(e) == classMembersMap.end()) {
LOG(ERROR) << "Failed to find in classMembersMap " << e;
return false;
}
auto kind = drgn_type_kind(e);
if (kind != DRGN_TYPE_STRUCT && kind != DRGN_TYPE_CLASS &&
kind != DRGN_TYPE_UNION) {
LOG(ERROR) << "Failed to read type";
return false;
}
std::string generatedMembers;
PaddingInfo paddingInfo{};
bool violatesAlignmentRequirement = false;
auto tmpStr = getNameForType(e);
if (!tmpStr.has_value()) {
return false;
}
auto sz = getDrgnTypeSize(e);
if (!sz.has_value()) {
return false;
}
VLOG(1) << "Generate members for " << *tmpStr << " " << e << " {";
// paddingIndexMap saves the range of padding indexes used for the current
// struct We save the current pad_index as the start of the range...
auto startingPadIndex = pad_index;
uint64_t offsetBits = 0;
std::unordered_map<std::string, int> memberNames;
if (!generateStructMembers(e, memberNames, generatedMembers, offsetBits,
paddingInfo, violatesAlignmentRequirement, 0)) {
return false;
}
// After the members have been generated, we save the updated pad_index as the
// end of the range
if (startingPadIndex != pad_index) {
[[maybe_unused]] auto [_, paddingInserted] =
paddingIndexMap.emplace(e, std::make_pair(startingPadIndex, pad_index));
assert(paddingInserted);
}
uint64_t offsetBytes = offsetBits / CHAR_BIT;
if (drgn_type_kind(e) != DRGN_TYPE_UNION && *sz != offsetBytes) {
VLOG(1) << "size mismatch " << e << " when generating drgn: " << *sz << " "
<< offsetBytes << " " << *tmpStr;
// Special case when class is empty
if (!(offsetBytes == 0 && *sz == 1)) {
}
} else {
VLOG(2) << "Size matches " << *tmpStr << " " << e << " sz " << *sz
<< " offsetBytes " << offsetBytes;
}
std::string structDefinition;
if (paddingInfo.paddingSize != 0 && feature(Feature::GenPaddingStats)) {
structDefinition.append("/* offset | size */ ");
}
if (kind == DRGN_TYPE_STRUCT || kind == DRGN_TYPE_CLASS) {
structDefinition.append("struct");
} else if (kind == DRGN_TYPE_UNION) {
structDefinition.append("union");
auto alignment = getAlignmentRequirements(e);
if (!alignment.has_value()) {
return false;
}
structDefinition.append(" alignas(" +
std::to_string(*alignment / CHAR_BIT) + ")");
}
if (feature(Feature::PackStructs) &&
(kind == DRGN_TYPE_STRUCT || kind == DRGN_TYPE_CLASS) &&
violatesAlignmentRequirement && paddingInfo.paddingSize == 0) {
structDefinition.append(" __attribute__((__packed__))");
}
structDefinition.append(" ");
structDefinition.append(*tmpStr);
structDefinition.append(" {\n");
if (kind == DRGN_TYPE_UNION) {
// Pad out unions
structDefinition.append("char union_padding[" + std::to_string(*sz) +
"];\n");
} else {
structDefinition.append(generatedMembers);
}
VLOG(1) << "}";
structDefinition.append("};\n");
if (feature(Feature::GenPaddingStats)) {
auto paddedStructFound = paddedStructs.find(*tmpStr);
if (paddedStructFound == paddedStructs.end()) {
if (paddingInfo.paddingSize || paddingInfo.isSetSize) {
paddingInfo.structSize = offsetBytes;
paddingInfo.paddingSize /= CHAR_BIT;
paddingInfo.definition = structDefinition;
paddingInfo.computeSaving();
if (paddingInfo.savingSize > 0) {
paddedStructs.insert({*tmpStr, paddingInfo});
}
}
}
}
code.append(structDefinition);
// In FuncGen add static_assert for sizes from this array
if (drgn_type_kind(e) != DRGN_TYPE_UNION) {
topoSortedStructTypes.push_back(e);
}
return true;
}
bool OICodeGen::isNumMemberGreaterThanZero(drgn_type* type) {
if (drgn_type_num_members(type) > 0) {
return true;
}
if (parentClasses.find(type) != parentClasses.end()) {
for (auto& p : parentClasses[type]) {
auto* underlyingType = drgn_utils::underlyingType(p.type);
if (isNumMemberGreaterThanZero(underlyingType)) {
return true;
}
}
}
return false;
}
bool OICodeGen::addPadding(uint64_t padding_bits, std::string& code) {
if (padding_bits == 0) {
return false;
}
VLOG(1) << "Add padding bits " << padding_bits;
if (padding_bits % CHAR_BIT != 0) {
VLOG(1) << "WARNING: Padding not aligned to a byte";
}
if ((padding_bits / CHAR_BIT) > 0) {
std::ostringstream info;
bool isByteMultiple = padding_bits % CHAR_BIT == 0;
if (isByteMultiple) {
info << "/* XXX" << std::setw(3) << std::right << padding_bits / CHAR_BIT
<< "-byte hole */ ";
} else {
info << "/* XXX" << std::setw(3) << std::right << padding_bits
<< "-bit hole */ ";
}
code.append(info.str());
code.append("char __padding_" + std::to_string(pad_index++) + "[" +
std::to_string(padding_bits / CHAR_BIT) + "];\n");
}
return true;
}
static inline void addSizeComment(bool genPaddingStats,
std::string& code,
size_t offset,
size_t sizeInBits) {
if (!genPaddingStats) {
return;
}
bool isByteMultiple = sizeInBits % CHAR_BIT == 0;
size_t sizeInBytes = (sizeInBits + CHAR_BIT - 1) / CHAR_BIT;
std::ostringstream info;
if (isByteMultiple) {
info << "/* " << std::setw(10) << std::left << offset / CHAR_BIT << "| "
<< std::setw(5) << std::right << sizeInBytes << " */ ";
} else {
info << "/* " << std::setw(4) << std::left << offset / CHAR_BIT
<< ": 0 | " << std::setw(5) << std::right << sizeInBytes << " */ ";
}
code.append(info.str());
}
void OICodeGen::deduplicateMemberName(
std::unordered_map<std::string, int>& memberNames,
std::string& memberName) {
if (!memberName.empty()) {
auto srchRes = memberNames.find(memberName);
if (srchRes == memberNames.end()) {
memberNames[memberName] = 1;
} else {
auto newCurrentIndex = srchRes->second + 1;
srchRes->second = newCurrentIndex;
memberName += std::to_string(newCurrentIndex);
}
}
}
std::optional<uint64_t> OICodeGen::generateMember(
const DrgnClassMemberInfo& m,
std::unordered_map<std::string, int>& memberNames,
uint64_t currOffsetBits,
std::string& code,
bool isInUnion) {
// Generate unique name for member
std::string memberName = m.member_name;
deduplicateMemberName(memberNames, memberName);
std::replace(memberName.begin(), memberName.end(), '.', '_');
auto* memberType = m.type;
auto szBytes = getDrgnTypeSize(memberType);
if (!szBytes.has_value()) {
return std::nullopt;
}
if (m.isStubbed) {
code.append("char ");
code.append(memberName);
code.append("[");
code.append(std::to_string(szBytes.value()));
code.append("]; // Member stubbed per config's membersToStub\n");
return currOffsetBits + 8 * szBytes.value();
}
if (drgn_type_kind(memberType) == DRGN_TYPE_ARRAY) {
// TODO: No idea how to handle flexible array member or zero length array
size_t elems = 1;
drgn_type* arrayElementType = nullptr;
drgn_utils::getDrgnArrayElementType(memberType, &arrayElementType, elems);
auto tmpStr = getNameForType(arrayElementType);
if (!tmpStr.has_value()) {
return std::nullopt;
}
if (elems == 0 || isInUnion) {
std::string memName = memberName + "[" + std::to_string(elems) + "]";
code.append(*tmpStr);
code.append(" ");
code.append(memName);
code.append(";");
code.append("\n");
if (isInUnion) {
currOffsetBits = 0;
VLOG(1) << "Member size: " << *szBytes * CHAR_BIT;
}
} else {
code.append("std::array<");
code.append(*tmpStr);
code.append(", ");
code.append(std::to_string(elems));
code.append("> ");
code.append(memberName);
code.append(";\n");
currOffsetBits = currOffsetBits + (*szBytes * CHAR_BIT);
}
} else {
auto tmpStr = getNameForType(memberType);
uint64_t memberSize = 0;
if (!tmpStr.has_value()) {
return std::nullopt;
}
if (m.bit_field_size > 0) {
memberSize = m.bit_field_size;
} else {
auto drgnTypeSize = getDrgnTypeSize(memberType);
if (!drgnTypeSize.has_value()) {
return false;
}
memberSize = *drgnTypeSize * CHAR_BIT;
}
if (isInUnion) {
currOffsetBits = 0;
VLOG(1) << "Member size: " << memberSize;
} else {
addSizeComment(feature(Feature::GenPaddingStats), code, currOffsetBits,
memberSize);
currOffsetBits = currOffsetBits + memberSize;
}
code.append(*tmpStr);
code.append(" ");
code.append(memberName);
if (m.bit_field_size > 0) {
code.append(":" + std::to_string(m.bit_field_size));
}
code.append(";\n");
}
return currOffsetBits;
}
bool OICodeGen::generateParent(
drgn_type* p,
std::unordered_map<std::string, int>& memberNames,
uint64_t& currOffsetBits,
std::string& code,
size_t offsetToNextMember) {
// Parent class could be a typedef
PaddingInfo paddingInfo{};
bool violatesAlignmentRequirement = false;
auto* underlyingType = drgn_utils::underlyingType(p);
uint64_t offsetBits = 0;
if (!generateStructMembers(underlyingType, memberNames, code, offsetBits,
paddingInfo, violatesAlignmentRequirement,
offsetToNextMember)) {
return false;
}
// *Sigh* account for base class optimization
auto typeSize = getDrgnTypeSize(underlyingType);
if (!typeSize.has_value()) {
return false;
}
if (*typeSize > 1 || isNumMemberGreaterThanZero(underlyingType)) {
currOffsetBits += offsetBits;
} else {
VLOG(1) << "Zero members " << p << " skipping parent class "
<< underlyingType;
}
return true;
}
/*Helper function that returns the alignment constraints in bits*/
std::optional<uint64_t> OICodeGen::getAlignmentRequirements(drgn_type* e) {
const uint64_t minimumAlignmentBits = CHAR_BIT;
uint64_t alignmentRequirement = CHAR_BIT;
std::string outName;
std::optional<uint64_t> typeSize;
switch (drgn_type_kind(e)) {
case DRGN_TYPE_VOID:
case DRGN_TYPE_FUNCTION:
LOG(ERROR) << "Alignment req unhandled " << getDrgnTypeName(e, outName);
return std::nullopt;
case DRGN_TYPE_INT:
case DRGN_TYPE_BOOL:
case DRGN_TYPE_FLOAT:
case DRGN_TYPE_ENUM:
typeSize = getDrgnTypeSize(e);
if (!typeSize.has_value()) {
return std::nullopt;
}
return *typeSize * CHAR_BIT;
case DRGN_TYPE_STRUCT:
case DRGN_TYPE_CLASS:
case DRGN_TYPE_UNION:
if (isContainer(e)) {
alignmentRequirement = 64;
} else {
auto numMembers = drgn_type_num_members(e);
auto* members = drgn_type_members(e);
for (size_t i = 0; i < numMembers; ++i) {
drgn_qualified_type memberType{};
if (drgn_member_type(&members[i], &memberType, nullptr) != nullptr) {
continue;
}
size_t currentMemberAlignmentRequirement =
getAlignmentRequirements(memberType.type)
.value_or(minimumAlignmentBits);
alignmentRequirement =
std::max(alignmentRequirement, currentMemberAlignmentRequirement);
}
for (size_t parentIndex = 0; parentIndex < parentClasses[e].size();
++parentIndex) {
size_t parentAlignment =
getAlignmentRequirements(parentClasses[e][parentIndex].type)
.value_or(minimumAlignmentBits);
alignmentRequirement =
std::max(alignmentRequirement, parentAlignment);
}
}
return alignmentRequirement;
case DRGN_TYPE_POINTER:
return 64;
case DRGN_TYPE_TYPEDEF:
case DRGN_TYPE_ARRAY:
auto* underlyingType = drgn_type_type(e).type;
return getAlignmentRequirements(underlyingType);
}
return minimumAlignmentBits;
}
bool OICodeGen::generateStructMembers(
drgn_type* e,
std::unordered_map<std::string, int>& memberNames,
std::string& code,
uint64_t& out_offset_bits,
PaddingInfo& paddingInfo,
bool& violatesAlignmentRequirement,
size_t offsetToNextMemberInSubclass) {
if (classMembersMap.find(e) == classMembersMap.end()) {
VLOG(1) << "Failed to find type in classMembersMap: " << e;
}
size_t parentIndex = 0;
size_t memberIndex = 0;
auto& members = classMembersMap[e];
uint64_t currOffsetBits = 0;
uint64_t alignmentRequirement = 8;
bool hasParents = (parentClasses.find(e) != parentClasses.end());
do {
uint64_t memberOffsetBits = ULONG_MAX;
if (memberIndex < members.size()) {
memberOffsetBits = members[memberIndex].bit_offset;
}
uint64_t parentOffsetBits = ULONG_MAX;
if (hasParents && parentIndex < parentClasses[e].size()) {
parentOffsetBits = parentClasses[e][parentIndex].bit_offset;
}
if (memberOffsetBits == ULONG_MAX && parentOffsetBits == ULONG_MAX) {
break;
}
// Truncate the typenames while printing. They can be huge.
if (memberOffsetBits < parentOffsetBits) {
std::string typeName;
if (typeToNameMap.find(members[memberIndex].type) !=
typeToNameMap.end()) {
std::optional<std::string> tmpStr =
getNameForType(members[memberIndex].type);
if (!tmpStr.has_value())
return false;
typeName = std::move(*tmpStr);
}
VLOG(1) << "Add class member type: " << typeName.substr(0, 15) << "... "
<< members[memberIndex].type << " currOffset: " << currOffsetBits
<< " expectedOffset: " << memberOffsetBits;
if (drgn_type_kind(e) != DRGN_TYPE_UNION) {
if (currOffsetBits > memberOffsetBits) {
LOG(ERROR) << "Failed to match " << e << " currOffsetBits "
<< currOffsetBits << " memberOffsetBits "
<< memberOffsetBits;
return false;
}
if (addPadding(memberOffsetBits - currOffsetBits, code)) {
paddingInfo.paddingSize += memberOffsetBits - currOffsetBits;
paddingInfo.paddings.push_back(memberOffsetBits - currOffsetBits);
}
currOffsetBits = memberOffsetBits;
}
size_t prevOffsetBits = currOffsetBits;
auto newCurrOffsetBits =
generateMember(members[memberIndex], memberNames, currOffsetBits,
code, drgn_type_kind(e) == DRGN_TYPE_UNION);
if (!newCurrOffsetBits.has_value()) {
return false;
}
currOffsetBits = *newCurrOffsetBits;
// Determine whether this type is a Thrift struct. Only try and read the
// isset values if the struct layout is as expected.
// i.e. __isset must be the last member in the struct
const auto& memberName = members[memberIndex].member_name;
bool isThriftIssetStruct =
typeName.starts_with("isset_bitset<") && memberName == "__isset";
if (feature(Feature::CaptureThriftIsset) && isThriftIssetStruct &&
memberIndex == members.size() - 1) {
thriftIssetStructTypes.insert(e);
}
if (feature(Feature::GenPaddingStats)) {
paddingInfo.isThriftStruct = isThriftIssetStruct;
/*
* Thrift has a __isset member for each field to represent that field
* is "set" or not. One byte is normally used for each field(has
* Unpacked in the name) by using @cpp.PackIsset annotation, we use
* one bit for each field instead (has Packed in the name). We're
* only looking for Unpacked __isset as those are the only ones we can
* optimise
*/
if (paddingInfo.isThriftStruct &&
typeName.find("Unpacked") != std::string::npos) {
size_t unpackedSize = (currOffsetBits - prevOffsetBits) / CHAR_BIT;
size_t packedSize = (unpackedSize + CHAR_BIT - 1) / CHAR_BIT;
size_t savingFromPacking = unpackedSize - packedSize;
if (savingFromPacking > 0) {
paddingInfo.isSetSize = unpackedSize;
paddingInfo.isSetOffset = currOffsetBits / CHAR_BIT;
}
}
}
auto currentMemberAlignmentRequirement =
getAlignmentRequirements(members[memberIndex].type);
if (!currentMemberAlignmentRequirement.has_value()) {
return false;
}
alignmentRequirement =
std::max(alignmentRequirement, *currentMemberAlignmentRequirement);
paddingInfo.alignmentRequirement = alignmentRequirement / CHAR_BIT;
memberIndex++;
} else if (parentOffsetBits < memberOffsetBits) {
std::string typeName;
if (typeToNameMap.find(parentClasses[e][parentIndex].type) !=
typeToNameMap.end()) {
std::optional<std::string> tmpStr =
getNameForType(parentClasses[e][parentIndex].type);
if (!tmpStr.has_value()) {
return false;
}
typeName = std::move(*tmpStr);
typeName.resize(15);
}
VLOG(1) << "Add parent type: " << typeName << "(...) "
<< parentClasses[e][parentIndex].type
<< " currOffset: " << currOffsetBits
<< " expectedOffset: " << parentOffsetBits;
if (currOffsetBits > parentOffsetBits && parentOffsetBits != 0) {
// If a parent is empty, drgn can return parentOffsetBits as 0. So,
// unfortunately, this check needs to be loosened.
LOG(ERROR) << "Failed to match " << e << " currOffsetBits "
<< currOffsetBits << " parentOffsetBits "
<< parentOffsetBits;
return false;
}
if (parentOffsetBits > currOffsetBits) {
addPadding(parentOffsetBits - currOffsetBits, code);
paddingInfo.paddingSize += parentOffsetBits - currOffsetBits;
paddingInfo.paddings.push_back(parentOffsetBits - currOffsetBits);
currOffsetBits = parentOffsetBits;
}
auto parentTypeName = getNameForType(parentClasses[e][parentIndex].type);
if (!parentTypeName.has_value()) {
return false;
}
VLOG(1) << "Generating parent " << *parentTypeName << " "
<< parentClasses[e][parentIndex].type << " {";
g_level += 1;
size_t offsetToNextMember = 0;
if (parentIndex + 1 < parentClasses[e].size()) {
offsetToNextMember = parentClasses[e][parentIndex + 1].bit_offset;
} else {
if (memberOffsetBits != ULONG_MAX) {
offsetToNextMember = memberOffsetBits - currOffsetBits;
} else { // when members.size() == 0
offsetToNextMember = 0;
}
}
if (!generateParent(parentClasses[e][parentIndex].type, memberNames,
currOffsetBits, code, offsetToNextMember)) {
return false;
}
auto currentMemberAlignmentRequirement =
getAlignmentRequirements(parentClasses[e][parentIndex].type);
if (!currentMemberAlignmentRequirement.has_value()) {
return false;
}
alignmentRequirement =
std::max(alignmentRequirement, *currentMemberAlignmentRequirement);
paddingInfo.alignmentRequirement = alignmentRequirement / CHAR_BIT;
g_level -= 1;
VLOG(1) << "}";
parentIndex++;
} else {
// if memberOffsetBits and parentOffsetBits are same, parent class must be
// empty
parentIndex++;
}
} while (true);
auto szBytes = getDrgnTypeSize(e);
if (!szBytes.has_value()) {
return false;
}
if (drgn_type_kind(e) != DRGN_TYPE_UNION &&
// If class is empty, class size is at least one. But don't add the
// explicit padding. It can mess up with inheritance
(*szBytes > 1 || isNumMemberGreaterThanZero(e))) {
uint64_t szBits = (*szBytes * CHAR_BIT);
if (offsetToNextMemberInSubclass && offsetToNextMemberInSubclass < szBits) {
/* We are laying out the members of a parent class*/
szBits = offsetToNextMemberInSubclass;
}
if (currOffsetBits < szBits) {
uint64_t paddingBits = szBits - currOffsetBits;
VLOG(1) << "Add padding to end of struct; curr " << currOffsetBits
<< " sz " << szBits;
addPadding(paddingBits, code);
paddingInfo.paddingSize += paddingBits;
if (paddingInfo.isSetSize == 0) {
/*
* We already account for the unnaligned remainder at the end of a
* struct if the struct has an Unpacked __is_set field. Example from
* PaddingHunter.h:
* if (isThriftStruct) {
* if (isSet_size) {
* saving_size = saving_from_packing();
* odd_sum += isSet_offset - saving_from_packing();
* }
* }
*/
paddingInfo.paddings.push_back(paddingBits);
}
}
currOffsetBits = szBits;
} else if (drgn_type_kind(e) == DRGN_TYPE_UNION) {
currOffsetBits = (*szBytes * CHAR_BIT);
}
if (currOffsetBits % alignmentRequirement != 0) {
violatesAlignmentRequirement = true;
}
VLOG(1) << "Returning " << e << " " << currOffsetBits;
out_offset_bits = currOffsetBits;
return true;
}
bool OICodeGen::generateStructDefs(std::string& code) {
std::vector<drgn_type*> structDefTypeCopy = structDefType;
std::map<drgn_type*, std::vector<ParentMember>> parentClassesCopy =
parentClasses;
while (!structDefTypeCopy.empty()) {
for (auto it = structDefTypeCopy.cbegin();
it != structDefTypeCopy.cend();) {
drgn_type* e = *it;
if (classMembersMap.find(e) == classMembersMap.end()) {
LOG(ERROR) << "Failed to find in classMembersMap " << e;
return false;
}
bool skip = false;
// Make sure that all parent class types are defined
if (parentClassesCopy.find(e) != parentClassesCopy.end()) {
auto& parents = parentClassesCopy[e];
for (auto& p : parents) {
auto it2 = std::find(structDefTypeCopy.begin(),
structDefTypeCopy.end(), p.type);
if (it2 != structDefTypeCopy.cend()) {
skip = true;
break;
}
}
}
// Make sure that all member types are defined
if (!skip) {
auto& members = classMembersMap[e];
for (auto& m : members) {
auto* underlyingType = drgn_utils::underlyingType(m.type);
if (underlyingType != e) {
auto it2 = std::find(structDefTypeCopy.begin(),
structDefTypeCopy.end(), underlyingType);
if (it2 != structDefTypeCopy.cend()) {
skip = true;
break;
}
}
}
}
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
return false;
}
if (skip) {
++it;
VLOG(1) << "Skipping " << *typeName << " " << e;
continue;
} else {
VLOG(1) << "Generating struct " << *typeName << " " << e << " {";
g_level += 1;
if (!generateStructDef(e, code)) {
return false;
}
g_level -= 1;
VLOG(1) << "}";
it = structDefTypeCopy.erase(it);
}
}
}
return true;
}
bool OICodeGen::addStaticAssertsForType(drgn_type* type,
bool generateAssertsForOffsets,
std::string& code) {
auto struct_name = getNameForType(type);
if (!struct_name.has_value()) {
return false;
}
auto sz = getDrgnTypeSize(type);
if (!sz.has_value()) {
return false;
}
std::string assertStr =
"static_assert(sizeof(" + *struct_name + ") == " + std::to_string(*sz);
assertStr += ", \"Unexpected size of struct " + *struct_name + "\");\n";
std::unordered_map<std::string, int> memberNames;
if (generateAssertsForOffsets &&
!staticAssertMemberOffsets(*struct_name, type, assertStr, memberNames)) {
return false;
}
code.append(assertStr);
return true;
}
/*
* Generates a declaration for a given fully-qualified type.
*
* e.g. Given "nsA::nsB::Foo"
*
* The folowing is generated:
* namespace nsA {
* namespace nsB {
* struct Foo;
* } // nsB
* } // nsA
*/
void OICodeGen::declareThriftStruct(std::string& code, std::string_view name) {
if (auto pos = name.find("::"); pos != name.npos) {
auto ns = name.substr(0, pos);
code += "namespace ";
code += ns;
code += " {\n";
// Recurse, continuing from after the "::"
declareThriftStruct(code, name.substr(pos + 2));
code += "} // namespace ";
code += ns;
code += "\n";
} else {
code += "struct ";
code += name;
code += ";\n";
}
}
bool OICodeGen::generateJitCode(std::string& code) {
FuncGen::DeclareExterns(code);
// Include relevant headers
code.append("// relevant header includes -----\n");
// We always generate functions for `std::array` since we transform
// raw C arrays into equivalent instances of `std::array`
// This is kind of hacky and introduces a dependency on `std_array.toml` even
// when people may not expect it.
{
bool found = false;
for (const auto& el : containerInfoList) {
if (el->typeName == "std::array<") {
containerTypesFuncDef.insert(*el);
found = true;
break;
}
}
if (!found) {
LOG(ERROR) << "an implementation for `std::array` is required but none "
"was found";
return false;
}
}
std::set<std::string> includedHeaders = config.defaultHeaders;
for (const ContainerInfo& e : containerTypesFuncDef) {
includedHeaders.insert(e.header);
}
// these headers are included in OITraceCode.cpp
includedHeaders.erase("xmmintrin.h");
includedHeaders.erase("utility");
// Required for the offsetof() macro
includedHeaders.insert("cstddef");
if (config.features[Feature::JitTiming]) {
includedHeaders.emplace("chrono");
}
for (const auto& e : includedHeaders) {
code.append("#include <");
code.append(e);
code.append(">\n");
}
code.append("// storage macro definitions -----\n");
if (config.useDataSegment) {
code.append(R"(
#define SAVE_SIZE(val)
#define SAVE_DATA(val) StoreData(val, returnArg)
)");
if (config.features[Feature::JitLogging]) {
code.append(R"(
#define JLOG(str) \
do { \
if (__builtin_expect(logFile, 0)) { \
write(logFile, str, sizeof(str) - 1); \
} \
} while (false)
#define JLOGPTR(ptr) \
do { \
if (__builtin_expect(logFile, 0)) { \
__jlogptr((uintptr_t)ptr); \
} \
} while (false)
)");
} else {
code.append(R"(
#define JLOG(str)
#define JLOGPTR(ptr)
)");
}
} else {
code.append(R"(
#define SAVE_SIZE(val) AddData(val, returnArg)
#define SAVE_DATA(val)
#define JLOG(str)
#define JLOGPTR(ptr)
)");
}
// The purpose of the anonymous namespace within `OIInternal` is that
// anything defined within an anonymous namespace has internal-linkage,
// and therefore won't appear in the symbol table of the resulting object
// file. Both OIL and OID do a linear search through the symbol table for
// the top-level `getSize` function to locate the probe entry point, so
// by keeping the contents of the symbol table to a minimum, we make that
// process faster.
std::string definitionsCode;
definitionsCode.append("namespace OIInternal {\nnamespace {\n");
// Use relevant namespaces
definitionsCode.append("// namespace uses\n");
std::set<std::string> usedNamespaces = config.defaultNamespaces;
for (const ContainerInfo& v : containerTypesFuncDef) {
for (auto& e : v.ns) {
usedNamespaces.insert(std::string(e));
}
}
for (auto& e : usedNamespaces) {
definitionsCode.append("using ");
definitionsCode.append(e);
definitionsCode.append(";\n");
}
printAllTypeNames();
definitionsCode.append("// forward declarations -----\n");
for (auto& e : structDefType) {
if (drgn_type_kind(e) == DRGN_TYPE_STRUCT ||
drgn_type_kind(e) == DRGN_TYPE_CLASS) {
definitionsCode.append("struct");
} else if (drgn_type_kind(e) == DRGN_TYPE_UNION) {
definitionsCode.append("union");
} else {
LOG(ERROR) << "Failed to read type";
return false;
}
definitionsCode.append(" ");
if (typeToNameMap.find(e) == typeToNameMap.end()) {
LOG(ERROR) << "Failed to find " << e << " in typeToNameMap ";
return false;
}
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
return false;
}
definitionsCode.append(typeName.value());
definitionsCode.append(";\n");
}
definitionsCode.append("// stubbed classes -----\n");
for (const auto& e : knownDummyTypeList) {
std::string name;
if (!getDrgnTypeName(e, name)) {
return false;
}
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
return false;
}
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(e, &sz); err != nullptr) {
bool shouldReturn = false;
std::string knownTypeName;
if (auto opt = isTypeToStub(*typeName)) {
knownTypeName = opt.value();
}
LOG(ERROR) << "Failed to query size from drgn; look in sizeMap "
<< knownTypeName << std::endl;
if (auto it = sizeMap.find(knownTypeName); it != sizeMap.end()) {
sz = it->second;
} else {
LOG(ERROR) << "Failed to get size: " << err->code << " " << err->message
<< " " << e;
shouldReturn = true;
}
drgn_error_destroy(err);
if (shouldReturn) {
return false;
}
}
auto alignment = getAlignmentRequirements(e).value_or(CHAR_BIT) / CHAR_BIT;
if (!isKnownType(name)) {
definitionsCode.append("struct alignas(" + std::to_string(alignment) +
") " + (*typeName) + " { char dummy[" +
std::to_string(sz) + "];};\n");
} else {
if (isTypeToStub(name)) {
definitionsCode.append("struct alignas(" + std::to_string(alignment) +
") " + (*typeName) + " { char dummy[" +
std::to_string(sz) + "];};\n");
}
}
}
definitionsCode.append("// enums -----\n");
for (auto& e : enumTypes) {
auto name = getNameForType(e);
if (!name.has_value()) {
return false;
}
uint64_t sz = 0;
if (auto* err = drgn_type_sizeof(e, &sz); err != nullptr) {
LOG(ERROR) << "Error when looking up size from drgn " << err->code << " "
<< err->message << " ";
drgn_error_destroy(err);
return false;
}
switch (sz) {
case 8:
definitionsCode.append("typedef uint64_t " + *name + ";\n");
break;
case 4:
definitionsCode.append("typedef uint32_t " + *name + ";\n");
break;
case 2:
definitionsCode.append("typedef uint16_t " + *name + ";\n");
break;
case 1:
definitionsCode.append("typedef uint8_t " + *name + ";\n");
break;
default:
LOG(ERROR) << "Error invalid enum size " << *name << " " << sz;
return false;
}
}
// This is basically a topological sort. We need to make sure typedefs
// are defined in the correct order.
auto sortedTypeDefMap = getSortedTypeDefMap(typedefTypes);
definitionsCode.append("// typedefs -----\n");
// Typedefs
for (auto const& e : sortedTypeDefMap) {
auto tmpStr1 = getNameForType(e.first);
auto tmpStr2 = getNameForType(e.second);
if (!tmpStr1.has_value() || !tmpStr2.has_value()) {
return false;
}
definitionsCode.append("typedef " + *tmpStr2 + " " + *tmpStr1 + ";\n");
}
definitionsCode.append("// struct definitions -----\n");
if (!generateStructDefs(definitionsCode)) {
return false;
}
funcGen.DeclareGetContainer(definitionsCode);
definitionsCode.append("}\n\n} // namespace OIInternal\n");
std::string functionsCode;
functionsCode.append("namespace OIInternal {\nnamespace {\n");
functionsCode.append("// functions -----\n");
if (!funcGen.DeclareGetSizeFuncs(functionsCode, containerTypesFuncDef,
config.features)) {
LOG(ERROR) << "declaring get size for containers failed";
return false;
}
if (feature(Feature::ChaseRawPointers)) {
functionsCode.append(R"(
template<typename T>
void getSizeType(const T* t, size_t& returnArg);
void getSizeType(const void *s_ptr, size_t& returnArg);
)");
}
for (auto& e : structDefType) {
if (drgn_type_kind(e) != DRGN_TYPE_UNION) {
auto typeName = getNameForType(e);
if (!typeName.has_value()) {
return false;
}
funcGen.DeclareGetSize(functionsCode, *typeName);
}
}
if (config.useDataSegment || feature(Feature::ChaseRawPointers)) {
funcGen.DeclareStoreData(functionsCode);
}
if (config.useDataSegment) {
funcGen.DeclareEncodeData(functionsCode);
funcGen.DeclareEncodeDataSize(functionsCode);
} else {
funcGen.DeclareAddData(functionsCode);
}
if (!funcGen.DefineGetSizeFuncs(functionsCode, containerTypesFuncDef,
config.features)) {
LOG(ERROR) << "defining get size for containers failed";
return false;
}
if (feature(Feature::ChaseRawPointers)) {
functionsCode.append(R"(
template<typename T>
void getSizeType(const T* s_ptr, size_t& returnArg)
{
JLOG("ptr val @");
JLOGPTR(s_ptr);
StoreData((uintptr_t)(s_ptr), returnArg);
if (s_ptr && pointers.add((uintptr_t)s_ptr)) {
StoreData(1, returnArg);
getSizeType(*(s_ptr), returnArg);
} else {
StoreData(0, returnArg);
}
}
void getSizeType(const void *s_ptr, size_t& returnArg)
{
JLOG("void ptr @");
JLOGPTR(s_ptr);
StoreData((uintptr_t)(s_ptr), returnArg);
}
)");
}
for (auto& e : structDefType) {
auto name = getNameForType(e);
if (!name.has_value()) {
return false;
}
if (name->find("TypedValue") == 0) {
funcGen.DefineGetSizeTypedValueFunc(functionsCode, *name);
} else if (drgn_type_kind(e) != DRGN_TYPE_UNION) {
getFuncDefinitionStr(functionsCode, e, *name);
}
}
if (config.useDataSegment) {
funcGen.DefineStoreData(functionsCode);
funcGen.DefineEncodeData(functionsCode);
funcGen.DefineEncodeDataSize(functionsCode);
} else {
funcGen.DefineAddData(functionsCode);
}
for (auto& structType : structDefType) {
// Don't generate member offset asserts for unions since we pad them out
bool generateOffsetAsserts =
(drgn_type_kind(structType) != DRGN_TYPE_UNION);
if (!addStaticAssertsForType(structType, generateOffsetAsserts,
functionsCode)) {
return false;
}
}
for (auto& container : containerTypeMapDrgn) {
// Don't generate member offset asserts since we don't unwind containers
if (!addStaticAssertsForType(container.first, false, functionsCode)) {
return false;
}
}
{
auto* type = rootTypeToIntrospect.type;
auto tmpStr = getNameForType(type);
if (!tmpStr.has_value()) {
return false;
}
std::string typeName = *tmpStr;
// The top-level function needs to appear outside the `OIInternal`
// namespace, or otherwise strange relocation issues may occur. We create a
// `typedef` with a known name (`__ROOT_TYPE__`) *inside* the `OIInternal`
// namespace so that we can refer to it from the top-level function without
// having to worry about appropriately referring to types inside the
// `OIInternal` namespace from outside of it, which would involve dealing
// with all the complexities of prepending `OIInternal::` to not just the
// root type but all of its (possible) template parameters.
functionsCode.append("\ntypedef " + typeName + " __ROOT_TYPE__;\n");
functionsCode.append("}\n\n} // namespace OIInternal\n");
/* Start function definitions. First define top level func for root object
*/
auto rawTypeName = drgn_utils::typeToName(type);
if (config.useDataSegment) {
if (rootTypeStr.starts_with("unique_ptr") ||
rootTypeStr.starts_with("LowPtr") ||
rootTypeStr.starts_with("shared_ptr")) {
funcGen.DefineTopLevelGetSizeSmartPtr(functionsCode, rawTypeName,
config.features);
} else {
funcGen.DefineTopLevelGetSizeRef(functionsCode, rawTypeName,
config.features);
}
} else {
if (linkageName.empty()) {
funcGen.DefineTopLevelGetSizeRefRet(functionsCode, rawTypeName);
} else {
funcGen.DefineTopLevelGetObjectSize(functionsCode, rawTypeName,
linkageName);
}
}
}
// Add definitions for Thrift data storage types.
// This is very brittle, but changes in the Thrift compiler should be caught
// by our integration tests. It might be better to build the definition of
// this struct from the debug info like we do for the types we're probing.
// That would require significant refactoring of CodeGen, however.
std::string thriftDefinitions;
for (const auto& t : thriftIssetStructTypes) {
auto fullyQualified = *fullyQualifiedName(t);
declareThriftStruct(thriftDefinitions, fullyQualified);
thriftDefinitions.append(R"(
namespace apache { namespace thrift {
template <> struct TStructDataStorage<)");
thriftDefinitions.append(fullyQualified);
thriftDefinitions.append(R"(> {
static constexpr const std::size_t fields_size = 1; // Invalid, do not use
static const std::array<folly::StringPiece, fields_size> fields_names;
static const std::array<int16_t, fields_size> fields_ids;
static const std::array<protocol::TType, fields_size> fields_types;
static const std::array<folly::StringPiece, fields_size> storage_names;
static const std::array<int, fields_size> __attribute__((weak)) isset_indexes;
};
}} // namespace thrift, namespace apache
)");
}
code.append(definitionsCode);
code.append(thriftDefinitions);
code.append(functionsCode);
std::ofstream ifs("/tmp/tmp_oid_output_2.cpp");
ifs << code;
ifs.close();
return true;
}
bool OICodeGen::isUnnamedStruct(drgn_type* type) {
return unnamedUnion.find(type) != unnamedUnion.end();
}
bool OICodeGen::generateNamesForTypes() {
std::map<std::string, std::string> templateTransformMap;
printAllTypes();
for (auto& e : structDefType) {
std::string name;
if (!getDrgnTypeName(e, name)) {
return false;
}
std::string tname = templateTransformType(name);
tname = structNameTransformType(tname);
templateTransformMap[name] = tname;
addTypeToName(e, tname);
}
// This sucks, make sure enumTypes are named before typedefTypes
// Otherwise there is a really confusing issue to untangle e.g.
// Assume groupa::groupb::OffendingType is an enum
// using OffendingType = groupa::groupb::OffendingType;
//
// This would result in 2 types a typedef and an enum. Make sure the
// underlying enum is named first.
for (auto& e : enumTypes) {
if (drgn_type_tag(e)) {
std::string name = drgn_type_tag(e);
addTypeToName(e, name);
} else {
static int index = 0;
std::string name = "__unnamed_enum_" + std::to_string(index);
addTypeToName(e, name);
}
}
for (auto& e : typedefTypes) {
std::string name;
if (!getDrgnTypeName(e.first, name)) {
return false;
}
std::string tname = templateTransformType(name);
tname = structNameTransformType(tname);
addTypeToName(e.first, tname);
}
VLOG(1) << "Printing size map";
for (auto& e : sizeMap) {
VLOG(1) << "sizeMap[" << e.first << "] : " << e.second;
}
for (auto& e : knownDummyTypeList) {
std::string name;
if (!getDrgnTypeName(e, name)) {
return false;
}
std::string tname = templateTransformType(name);
if (tname.size() == 0) {
static int index = 0;
tname = "__unnamed_dummy_struct_" + std::to_string(index);
index++;
}
tname = structNameTransformType(tname);
templateTransformMap[name] = tname;
addTypeToName(e, tname);
}
// funcDefTypeList should already include every type in classMembersMap?
// This is rather unfortunate. Assume there is an type which is a pointer to
// another concrete type, e.g. struct Foo and Foo *. If Foo is renamed
// to Foo__0 because of a conflict, the pointer also needs to be renamed to
// Foo__0 *. In the first pass of funcDefTypeList and typedefTypes, assign
// names to all types except pointer types. Then in a second pass assign names
// to pointers (and also handle multiple levels of pointer).
// TODO: Just create a separate list for pointer types. That would avoid
// having to iterate 2 times.
// First pass, ignore pointer types
for (const auto& e : funcDefTypeList) {
if (isUnnamedStruct(e)) {
bool found = false;
for (auto& t : typedefTypes) {
if (t.second == e && drgn_type_kind(t.second) != DRGN_TYPE_ENUM) {
found = true;
break;
}
}
if (found) {
continue;
}
} else if (drgn_type_kind(e) == DRGN_TYPE_POINTER) {
continue;
}
std::string name;
if (!getDrgnTypeName(e, name)) {
return false;
}
memberTransformName(templateTransformMap, name);
addTypeToName(e, name);
}
// First pass, ignore pointer types
for (auto& e : typedefTypes) {
if (isUnnamedStruct(e.second) &&
drgn_type_kind(e.second) != DRGN_TYPE_ENUM) {
} else {
// Apply template transform to only to the type which is already known
// i.e. for "typedef A B", only apply template transform to A
if (drgn_type_kind(e.second) != DRGN_TYPE_POINTER) {
std::string name;
if (!getDrgnTypeName(e.second, name)) {
return false;
}
memberTransformName(templateTransformMap, name);
addTypeToName(e.second, name);
}
}
}
// Second pass, name the pointer types
for (auto& e : funcDefTypeList) {
if (drgn_type_kind(e) == DRGN_TYPE_POINTER) {
int ptrDepth = 0;
drgn_type* ut = e;
while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) {
ut = drgn_type_type(ut).type;
ptrDepth++;
}
auto ut_name = getNameForType(ut);
if (ut_name.has_value()) {
addTypeToName(e, (*ut_name) + std::string(ptrDepth, '*'));
} else {
std::string name;
if (!getDrgnTypeName(e, name)) {
return false;
}
memberTransformName(templateTransformMap, name);
addTypeToName(e, name);
}
}
}
// Second pass, name the pointer types
for (auto& e : typedefTypes) {
if (drgn_type_kind(e.second) == DRGN_TYPE_POINTER) {
int ptrDepth = 0;
drgn_type* ut = e.second;
while (drgn_type_kind(ut) == DRGN_TYPE_POINTER) {
ut = drgn_type_type(ut).type;
ptrDepth++;
}
auto utName = getNameForType(ut);
if (utName.has_value()) {
addTypeToName(e.second, (*utName) + std::string(ptrDepth, '*'));
} else {
std::string name;
if (!getDrgnTypeName(e.second, name)) {
return false;
}
memberTransformName(templateTransformMap, name);
addTypeToName(e.second, name);
}
}
}
{
// TODO: Do we need to apply a template transform to rootType
std::string name;
if (!getDrgnTypeName(rootType.type, name)) {
return false;
}
addTypeToName(rootType.type, name);
}
return true;
}
bool OICodeGen::generate(std::string& code) {
if (!populateDefsAndDecls()) {
return false;
}
if (!generateNamesForTypes()) {
return false;
}
if (!generateJitCode(code)) {
return false;
}
return true;
}
/**
* Generate static_asserts for the offsets of each member of the given type
*/
bool OICodeGen::staticAssertMemberOffsets(
const std::string& struct_name,
drgn_type* struct_type,
std::string& assert_str,
std::unordered_map<std::string, int>& memberNames,
uint64_t base_offset) {
if (knownDummyTypeList.find(struct_type) != knownDummyTypeList.end()) {
return true;
}
if (drgn_type_kind(struct_type) == DRGN_TYPE_TYPEDEF) {
// Operate on the underlying type for typedefs
return staticAssertMemberOffsets(struct_name,
drgn_utils::underlyingType(struct_type),
assert_str, memberNames, base_offset);
}
const auto* tag = drgn_type_tag(struct_type);
if (tag && isContainer(struct_type)) {
// We don't generate members for container types
return true;
}
if (parentClasses.find(struct_type) != parentClasses.end()) {
// Recurse into parents to find inherited members
for (const auto& parent : parentClasses[struct_type]) {
auto parentOffset = base_offset + parent.bit_offset / CHAR_BIT;
if (!staticAssertMemberOffsets(struct_name, parent.type, assert_str,
memberNames, parentOffset)) {
return false;
}
}
}
if (!drgn_type_has_members(struct_type)) {
return true;
}
auto* members = drgn_type_members(struct_type);
for (size_t i = 0; i < drgn_type_num_members(struct_type); ++i) {
if (members[i].name == nullptr) {
// Types can be defined in a class without assigning a member name e.g.
// struct A { struct {int i;} ;}; is a valid struct with size 4.
continue;
}
std::string memberName = members[i].name;
deduplicateMemberName(memberNames, memberName);
std::replace(memberName.begin(), memberName.end(), '.', '_');
drgn_qualified_type memberQualType{};
uint64_t bitFieldSize = 0;
if (auto* err =
drgn_member_type(&members[i], &memberQualType, &bitFieldSize);
err != nullptr) {
LOG(ERROR) << "Error when looking up member type " << err->code << " "
<< err->message << " " << memberName;
drgn_error_destroy(err);
return false;
}
// Can't use "offsetof" on bitfield members
if (bitFieldSize > 0) {
continue;
}
auto expectedOffset = base_offset + members[i].bit_offset / CHAR_BIT;
assert_str += "static_assert(offsetof(" + struct_name + ", " + memberName +
") == " + std::to_string(expectedOffset) +
", \"Unexpected offset of " + struct_name +
"::" + memberName + "\");\n";
}
return true;
}
std::map<std::string, PaddingInfo> OICodeGen::getPaddingInfo() {
return paddedStructs;
}
drgn_qualified_type OICodeGen::getRootType() {
return rootTypeToIntrospect;
};
void OICodeGen::setRootType(drgn_qualified_type rt) {
rootType = rt;
}
TypeHierarchy OICodeGen::getTypeHierarchy() {
std::map<
struct drgn_type*,
std::pair<ContainerTypeEnum, std::vector<struct drgn_qualified_type>>>
containerTypeMap;
for (auto const& [k, v] : containerTypeMapDrgn) {
const ContainerInfo& cinfo = v.first;
containerTypeMap[k] = {cinfo.ctype, v.second};
}
return {
.classMembersMap = getClassMembersMap(),
.containerTypeMap = std::move(containerTypeMap),
.typedefMap = typedefTypes,
.sizeMap = sizeMap,
.knownDummyTypeList = knownDummyTypeList,
.pointerToTypeMap = pointerToTypeMap,
.thriftIssetStructTypes = thriftIssetStructTypes,
.descendantClasses = descendantClasses,
};
}
std::string OICodeGen::Config::toString() const {
using namespace std::string_literals;
// The list of ignored members must also be part of the remote hash
std::string ignoreMembers = "IgnoreMembers=";
for (const auto& ignore : membersToStub) {
ignoreMembers += ignore.first;
ignoreMembers += "::";
ignoreMembers += ignore.second;
ignoreMembers += ';';
}
return boost::algorithm::join(toOptions(), ",") + "," + ignoreMembers;
}
std::vector<std::string> OICodeGen::Config::toOptions() const {
std::vector<std::string> options;
options.reserve(allFeatures.size());
for (const auto f : allFeatures) {
if (features[f]) {
options.emplace_back(std::string("-f") + featureToStr(f));
} else {
options.emplace_back(std::string("-F") + featureToStr(f));
}
}
return options;
}
void OICodeGen::initializeCodeGen() {
LOG(WARNING) << "OICodeGen::initializeCodeGen is no longer necessary";
};
} // namespace oi::detail