Skip to content

Commit

Permalink
SERVER-62049 Add pretty-printing for some of the new types in SBE
Browse files Browse the repository at this point in the history
  • Loading branch information
IrinaYatsenko authored and Evergreen Agent committed Dec 16, 2021
1 parent 4cfcc10 commit 207e7d1
Show file tree
Hide file tree
Showing 5 changed files with 430 additions and 0 deletions.
135 changes: 135 additions & 0 deletions buildscripts/gdb/mongo_printers.py
Expand Up @@ -681,6 +681,140 @@ def children(self):
yield 'value', bson.json_util.dumps(value)


def make_inverse_enum_dict(enum_type_name):
"""
Create a dictionary that maps enum values to the unqualified names of the enum elements.
For example, if the enum type is 'mongo::sbe::vm::Builtin' with an element 'regexMatch', the
dictionary will contain 'regexMatch' value and not 'mongo::sbe::vm::Builtin::regexMatch'.
"""
enum_dict = gdb.types.make_enum_dict(gdb.lookup_type(enum_type_name))
enum_inverse_dic = dict()
for key, value in enum_dict.items():
enum_inverse_dic[int(value)] = key.split('::')[-1] # take last element
return enum_inverse_dic


def read_as_integer(pmem, size):
"""Read 'size' bytes at 'pmem' as an integer."""
# We assume the same platform for the debugger and the debuggee (thus, 'sys.byteorder'). If
# this becomes a problem look into whether it's possible to determine the byteorder of the
# inferior.
return int.from_bytes( \
gdb.selected_inferior().read_memory(pmem, size).tobytes(), \
sys.byteorder)


class SbeCodeFragmentPrinter(object):
"""
Pretty-printer for mongo::sbe::vm::CodeFragment.
Objects of 'mongo::sbe::vm::CodeFragment' type contain a stream of op-codes to be executed by
the 'sbe::vm::ByteCode' class. The pretty printer decodes the stream and outputs it as a list of
named instructions.
"""

def __init__(self, val):
"""Initialize SbeCodeFragmentPrinter."""
self.val = val

# The instructions stream is stored using 'absl::InlinedVector<uint8_t, 16>' type, which can
# either use an inline buffer or an allocated one. The choice of storage is decoded in the
# last bit of the 'metadata_' field.
storage = self.val['_instrs']['storage_']
meta = storage['metadata_'].cast(gdb.lookup_type('size_t'))
self.is_inlined = (meta % 2 == 0)
self.size = (meta >> 1)
self.pdata = \
storage['data_']['inlined']['inlined_data'].cast(gdb.lookup_type('uint8_t').pointer()) \
if self.is_inlined \
else storage['data_']['allocated']['allocated_data']

# Precompute lookup tables for Instructions and Builtins.
self.optags_lookup = make_inverse_enum_dict('mongo::sbe::vm::Instruction::Tags')
self.builtins_lookup = make_inverse_enum_dict('mongo::sbe::vm::Builtin')
self.valuetags_lookup = make_inverse_enum_dict('mongo::sbe::value::TypeTags')

def to_string(self):
"""Return sbe::vm::CodeFragment for printing."""
return "%s" % (self.val.type)

# pylint: disable=R0915
def children(self):
"""children."""
yield '_instrs', '{... (to see raw output, run "disable pretty-printer")}'
yield '_fixUps', self.val['_fixUps']
yield '_stackSize', self.val['_stackSize']

yield 'inlined', self.is_inlined
yield 'instrs data at', '[{} - {}]'.format(hex(self.pdata), hex(self.pdata + self.size))
yield 'instrs total size', self.size

# Sizes for types we'll use when parsing the insructions stream.
int_size = gdb.lookup_type('int').sizeof
ptr_size = gdb.lookup_type('void').pointer().sizeof
tag_size = gdb.lookup_type('mongo::sbe::value::TypeTags').sizeof
value_size = gdb.lookup_type('mongo::sbe::value::Value').sizeof
uint32_size = gdb.lookup_type('uint32_t').sizeof
builtin_size = gdb.lookup_type('mongo::sbe::vm::Builtin').sizeof

cur_op = self.pdata
end_op = self.pdata + self.size
instr_count = 0
error = False
while cur_op < end_op:
op_addr = cur_op
op_tag = read_as_integer(op_addr, 1)

if not op_tag in self.optags_lookup:
yield hex(op_addr), 'unknown op tag: {}'.format(op_tag)
error = True
break
op_name = self.optags_lookup[op_tag]

cur_op += 1
instr_count += 1

# Some instructions have extra arguments, embedded into the ops stream.
args = ''
if op_name in ['pushLocalVal', 'pushMoveLocalVal', 'pushLocalLambda']:
args = 'arg: ' + str(read_as_integer(cur_op, int_size))
cur_op += int_size
if op_name in ['jmp', 'jmpTrue', 'jmpNothing']:
offset = read_as_integer(cur_op, int_size)
cur_op += int_size
args = 'offset: ' + str(offset) + ', target: ' + hex(cur_op + offset)
elif op_name in ['pushConstVal']:
tag = read_as_integer(cur_op, tag_size)
args = 'tag: ' + self.valuetags_lookup.get(tag, "unknown") + \
', value: ' + hex(read_as_integer(cur_op + tag_size, value_size))
cur_op += (tag_size + value_size)
elif op_name in ['pushAccessVal', 'pushMoveVal']:
args = 'accessor: ' + hex(read_as_integer(cur_op, ptr_size))
cur_op += ptr_size
elif op_name in ['numConvert']:
args = 'convert to: ' + \
self.valuetags_lookup.get(read_as_integer(cur_op, tag_size), "unknown")
cur_op += tag_size
elif op_name in ['typeMatch']:
args = 'mask: ' + hex(read_as_integer(cur_op, uint32_size))
cur_op += uint32_size
elif op_name in ['function', 'functionSmall']:
arity_size = \
gdb.lookup_type('mongo::sbe::vm::ArityType').sizeof \
if op_name == 'function' \
else gdb.lookup_type('mongo::sbe::vm::SmallArityType').sizeof
builtin_id = read_as_integer(cur_op, builtin_size)
args = 'builtin: ' + self.builtins_lookup.get(builtin_id, "unknown")
args += ' arity: ' + str(read_as_integer(cur_op + builtin_size, arity_size))
cur_op += (builtin_size + arity_size)

yield hex(op_addr), '{} ({})'.format(op_name, args)

yield 'instructions count', \
instr_count if not error else '? (successfully parsed {})'.format(instr_count)


def build_pretty_printer():
"""Build a pretty printer."""
pp = MongoPrettyPrinterCollection()
Expand All @@ -701,6 +835,7 @@ def build_pretty_printer():
pp.add('__wt_session_impl', '__wt_session_impl', False, WtSessionImplPrinter)
pp.add('__wt_txn', '__wt_txn', False, WtTxnPrinter)
pp.add('__wt_update', '__wt_update', False, WtUpdateToBsonPrinter)
pp.add('CodeFragment', 'mongo::sbe::vm::CodeFragment', False, SbeCodeFragmentPrinter)
return pp


Expand Down
4 changes: 4 additions & 0 deletions src/mongo/db/exec/sbe/expressions/expression.cpp
Expand Up @@ -61,6 +61,10 @@ vm::CodeFragment wrapNothingTest(vm::CodeFragment&& code, F&& generator) {
return std::move(code);
}

std::string EExpression::toString() const {
return DebugPrinter{}.print(debugPrint());
}

std::unique_ptr<EExpression> EConstant::clone() const {
auto [tag, val] = value::copyValue(_tag, _val);
return std::make_unique<EConstant>(tag, val);
Expand Down
4 changes: 4 additions & 0 deletions src/mongo/db/exec/sbe/expressions/expression.h
Expand Up @@ -302,6 +302,10 @@ class EExpression {
invariant(node);
}
}

private:
// For printing from an interactive debugger.
std::string toString() const;
};

template <typename T, typename... Args>
Expand Down
138 changes: 138 additions & 0 deletions src/mongo/db/exec/sbe/vm/vm.cpp
Expand Up @@ -168,6 +168,144 @@ size_t writeToMemory(uint8_t* ptr, const T val) noexcept {
}
} // namespace

std::string CodeFragment::toString() const {
std::ostringstream ss;
auto pcPointer = _instrs.data();
auto pcEnd = pcPointer + _instrs.size();
ss << "[" << (void*)pcPointer << "-" << (void*)pcEnd << "] ";

while (pcPointer < pcEnd) {
Instruction i = readFromMemory<Instruction>(pcPointer);
ss << (void*)pcPointer << ": " << i.toString() << "(";
pcPointer += sizeof(i);
switch (i.tag) {
// Instructions with no arguments.
case Instruction::pop:
case Instruction::swap:
case Instruction::add:
case Instruction::sub:
case Instruction::mul:
case Instruction::div:
case Instruction::idiv:
case Instruction::mod:
case Instruction::negate:
case Instruction::logicNot:
case Instruction::less:
case Instruction::collLess:
case Instruction::lessEq:
case Instruction::collLessEq:
case Instruction::greater:
case Instruction::collGreater:
case Instruction::greaterEq:
case Instruction::collGreaterEq:
case Instruction::eq:
case Instruction::collEq:
case Instruction::neq:
case Instruction::collNeq:
case Instruction::cmp3w:
case Instruction::collCmp3w:
case Instruction::fillEmpty:
case Instruction::getField:
case Instruction::getElement:
case Instruction::getArraySize:
case Instruction::collComparisonKey:
case Instruction::getFieldOrElement:
case Instruction::traverseP:
case Instruction::traverseF:
case Instruction::setField:
case Instruction::aggSum:
case Instruction::aggMin:
case Instruction::aggCollMin:
case Instruction::aggMax:
case Instruction::aggCollMax:
case Instruction::aggFirst:
case Instruction::aggLast:
case Instruction::exists:
case Instruction::isNull:
case Instruction::isObject:
case Instruction::isArray:
case Instruction::isString:
case Instruction::isNumber:
case Instruction::isBinData:
case Instruction::isDate:
case Instruction::isNaN:
case Instruction::isInfinity:
case Instruction::isRecordId:
case Instruction::isMinKey:
case Instruction::isMaxKey:
case Instruction::isTimestamp:
case Instruction::fail:
case Instruction::ret: {
break;
}
// Instructions with a single integer argument.
case Instruction::pushLocalVal:
case Instruction::pushMoveLocalVal:
case Instruction::pushLocalLambda: {
auto arg = readFromMemory<int>(pcPointer);
pcPointer += sizeof(arg);
ss << "arg: " << arg;
break;
}
case Instruction::jmp:
case Instruction::jmpTrue:
case Instruction::jmpNothing: {
auto offset = readFromMemory<int>(pcPointer);
pcPointer += sizeof(offset);
ss << "offset: " << offset << ", target: " << (void*)(pcPointer + offset);
break;
}
// Instructions with other kinds of arguments.
case Instruction::pushConstVal: {
auto tag = readFromMemory<value::TypeTags>(pcPointer);
pcPointer += sizeof(tag);
auto val = readFromMemory<value::Value>(pcPointer);
pcPointer += sizeof(val);
ss << "value: " << std::make_pair(tag, val);
break;
}
case Instruction::pushAccessVal:
case Instruction::pushMoveVal: {
auto accessor = readFromMemory<value::SlotAccessor*>(pcPointer);
pcPointer += sizeof(accessor);
ss << "accessor: " << static_cast<void*>(accessor);
break;
}
case Instruction::numConvert: {
auto tag = readFromMemory<value::TypeTags>(pcPointer);
pcPointer += sizeof(tag);
ss << "tag: " << tag;
break;
}
case Instruction::typeMatch: {
auto typeMask = readFromMemory<uint32_t>(pcPointer);
pcPointer += sizeof(typeMask);
ss << "typeMask: " << typeMask;
break;
}
case Instruction::function:
case Instruction::functionSmall: {
auto f = readFromMemory<Builtin>(pcPointer);
pcPointer += sizeof(f);
ArityType arity{0};
if (i.tag == Instruction::function) {
arity = readFromMemory<ArityType>(pcPointer);
pcPointer += sizeof(ArityType);
} else {
arity = readFromMemory<SmallArityType>(pcPointer);
pcPointer += sizeof(SmallArityType);
}
ss << "f: " << static_cast<uint8_t>(f) << ", arity: " << arity;
break;
}
default:
ss << "unknown";
}
ss << "); ";
}
return ss.str();
}

void CodeFragment::adjustStackSimple(const Instruction& i) {
_stackSize += Instruction::stackOffset[i.tag];
}
Expand Down

0 comments on commit 207e7d1

Please sign in to comment.