object-introspection/oi/OICodeGen.cpp
2023-12-13 11:59:21 +00:00

3765 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;
}
if (info->requiredFeatures != (config.features & info->requiredFeatures)) {
VLOG(1) << "Skipping container (feature conflict): " << info->typeName;
return true;
}
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('>');
if (end == std::string::npos) {
LOG(ERROR) << "No template parameter for the unique_ptr " << type;
return name;
}
size_t pos = end = end - 1; // Skip the '>' found
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(R"(
// storage macro definitions -----
#define SAVE_SIZE(val)
#define SAVE_DATA(val) StoreData(val, returnArg)
)");
code.append("namespace {\n");
code.append("static struct Context {\n");
code.append(" PointerHashSet<> pointers;\n");
code.append("} ctx;\n");
code.append("} // namespace\n");
FuncGen::DefineJitLog(code, config.features);
// 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);
}
}
funcGen.DeclareStoreData(functionsCode);
funcGen.DeclareEncodeData(functionsCode);
funcGen.DeclareEncodeDataSize(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 && ctx.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);
}
}
funcGen.DefineStoreData(functionsCode);
funcGen.DefineEncodeData(functionsCode);
funcGen.DefineEncodeDataSize(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 (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);
}
}
// 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