Last active
February 21, 2025 16:16
-
-
Save flga/90b9fb63a1f4b670b102f9e1bddb156a to your computer and use it in GitHub Desktop.
WIP Odin lldb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import lldb | |
import math | |
import traceback | |
def __lldb_init_module(debugger, dict): | |
debugger.HandleCommand('type summary add -F odin.string_summary -w odin string') | |
debugger.HandleCommand('type summary add -F odin.typeid_summary -w odin typeid') | |
debugger.HandleCommand('type synth add -l odin.TypeidSynth -w odin typeid') | |
debugger.HandleCommand('type summary add -F odin.any_summary -w odin any') | |
debugger.HandleCommand('type synth add -l odin.AnySynth -w odin any') | |
debugger.HandleCommand('type summary add -F odin.union_summary --recognizer-function odin.is_union -w odin') | |
debugger.HandleCommand('type synth add -l odin.UnionSynth --recognizer-function odin.is_union -w odin') | |
debugger.HandleCommand('type summary add -F odin.slice_dynarray_summary --recognizer-function odin.is_slice_or_dynarray -w odin') | |
debugger.HandleCommand('type synth add -l odin.SliceDynArraySynth --recognizer-function odin.is_slice_or_dynarray -w odin') | |
debugger.HandleCommand('type summary add -F odin.soa_slice_dynarray_summary --recognizer-function odin.is_soa_slice_or_dynarray -w odin') | |
debugger.HandleCommand('type synth add -l odin.SoaSliceDynArraySynth --recognizer-function odin.is_soa_slice_or_dynarray -w odin') | |
debugger.HandleCommand('type summary add -F odin.map_summary --recognizer-function odin.is_map -w odin') | |
debugger.HandleCommand('type synth add -l odin.MapSynth --recognizer-function odin.is_map -w odin') | |
debugger.HandleCommand('type synth add -l odin.EnumArraySynth --recognizer-function odin.is_enum_array -w odin') | |
debugger.HandleCommand('type category enable odin') | |
# enum arrays ================================================================== | |
def is_enum_array(type, internal_dict): | |
if type.type != lldb.eTypeClassTypedef: | |
return False | |
if not type.name.startswith("["): | |
return False | |
target = lldb.debugger.GetSelectedTarget() | |
count_type = target.FindFirstType(type.name[1:type.name.find("]")]) | |
if not count_type.IsValid(): | |
return False | |
return count_type.type == lldb.eTypeClassEnumeration | |
class EnumArraySynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
count_type_name = value.GetDisplayTypeName() | |
count_type = value.GetTarget().FindFirstType(count_type_name[1:count_type_name.find("]")]) | |
self.count_members = count_type.GetEnumMembers() | |
self.elem_type = value.GetType().GetArrayElementType() | |
self.elem_type_size = self.elem_type.GetByteSize() | |
self | |
def num_children(self): | |
return len(self.count_members) | |
def has_children(self): | |
return len(self.count_members) > 0 | |
def get_child_at_index(self, index): | |
offset = index * self.elem_type_size | |
return self.value.CreateChildAtOffset(f"[{index}, {self.count_members[index].name}]", offset, self.elem_type) | |
# slices and dynarrays ========================================================= | |
def is_slice_or_dynarray(type, internal_dict): | |
if type.type != lldb.eTypeClassStruct: | |
return False | |
return type.name.startswith("[]") or type.name.startswith("[dynamic]") | |
def slice_dynarray_summary(value, internal_dict): | |
raw = value.GetNonSyntheticValue() | |
len = raw.GetChildMemberWithName("len") | |
cap = raw.GetChildMemberWithName("cap") | |
if cap.IsValid(): | |
return f"len = {len.GetValueAsSigned(0)}, cap = {cap.GetValueAsSigned(0)}" | |
return f"len = {len.GetValueAsSigned(0)}" | |
class SliceDynArraySynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.update() | |
def update(self): | |
raw = self.value.GetNonSyntheticValue() | |
self.len = raw.GetChildMemberWithName("len").GetValueAsSigned() | |
self.data = raw.GetChildMemberWithName("data") | |
self.elem_type = self.data.GetType().GetPointeeType() | |
self.elem_type_size = self.elem_type.GetByteSize() | |
def num_children(self): | |
return self.len | |
def has_children(self): | |
return self.len > 0 | |
def get_child_at_index(self, index): | |
offset = index * self.elem_type_size | |
return self.data.CreateChildAtOffset(f"[{index}]", offset, self.elem_type) | |
# soa slices and dynarrays ===================================================== | |
def is_soa_slice_or_dynarray(type, internal_dict): | |
if type.type != lldb.eTypeClassStruct: | |
return False | |
return type.name.startswith("#soa[]") or type.name.startswith("#soa[dynamic]") | |
def soa_slice_dynarray_summary(value, internal_dict): | |
raw = value.GetNonSyntheticValue() | |
len = raw.GetChildMemberWithName("__$len") | |
cap = raw.GetChildMemberWithName("__$cap") | |
if cap.IsValid(): | |
return f"len = {len.GetValueAsSigned(0)}, cap = {cap.GetValueAsSigned(0)}" | |
return f"len = {len.GetValueAsSigned(0)}" | |
class SoaSliceDynArraySynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.update() | |
def update(self): | |
raw = self.value.GetNonSyntheticValue() | |
total_fields = raw.GetType().GetNumberOfFields() | |
# double #soa (as in #soa[]#soa...) will have multiple __$len fields, and this will pick the wrong one | |
# I don't know why you'd do this in the first place, so leaving it as is | |
# as it allows us to use the exact same code for slices and dynarrays | |
self.len = raw.GetChildMemberWithName("__$len").GetValueAsSigned() | |
self.num_members = total_fields - 1 | |
def num_children(self): | |
if self.len == 0: | |
return 0 | |
return self.num_members | |
def has_children(self): | |
if self.len == 0: | |
return False | |
return self.num_members > 0 | |
def get_child_at_index(self, index): | |
raw = self.value.GetNonSyntheticValue() | |
member = raw.GetChildAtIndex(index) | |
addr = member.AddressOf().GetValueAsUnsigned(0) | |
name = member.GetName() | |
type = member.GetType().GetPointeeType().GetArrayType(self.len).GetPointerType() | |
# Dereferencing makes lldb-dap chunk the results | |
# It should never try to dereference a null because we pretend we have no kids | |
# if len == 0 | |
return member.CreateValueFromAddress(name, addr, type).Dereference() | |
# strings ====================================================================== | |
def string_summary(value, internal_dict): | |
return f"\"{read_string(value)}\"" | |
# any ========================================================================== | |
def any_summary(value, internal_dict): | |
try: | |
data = value.GetChildAtIndex(0) | |
summary = data.GetSummary() | |
if summary is None: | |
summary = data.GetValue() | |
return f'({data.GetDisplayTypeName()}) {summary}' | |
except Exception as e: | |
print(traceback.format_exc()) | |
class AnySynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.update() | |
def num_children(self): | |
return 2 | |
def get_child_index(self, name): | |
if name == "id": | |
return 0 | |
if name == "data": | |
return 1 | |
def get_child_at_index(self, index): | |
if index == 0: | |
return self.typeid | |
if index == 1: | |
return self.data | |
def update(self): | |
raw = self.value.GetNonSyntheticValue() | |
self.typeid = raw.GetChildMemberWithName("id") | |
self.data = raw.GetChildMemberWithName("data") | |
try: | |
# print("@typeid", self.typeid) | |
# print("@type_info_of", type_info_of(self.typeid)) | |
# print("@type_of", type_of(type_info_of(self.typeid))) | |
typ = type_of(type_info_of(self.typeid)) | |
if typ is not None: | |
self.data = self.data.Cast(typ.GetPointerType()) | |
except Exception as e: | |
print(traceback.format_exc()) | |
def has_children(self): | |
return True | |
# typeid ======================================================================= | |
def typeid_summary(value, internal_dict): | |
try: | |
return odin_name(type_info_of(value)) | |
except: | |
return value.GetValueAsUnsigned(0) | |
class TypeidSynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.type_info = None | |
self.update() | |
def num_children(self): | |
if self.type_info is None: | |
return 0 | |
return 1 | |
def get_child_index(self, name): | |
if name == "value": | |
return 0 | |
return None | |
def get_child_at_index(self, index): | |
return self.type_info | |
def update(self): | |
try: | |
self.type_info = type_info_of(self.value) | |
self.type_info = self.type_info.CreateValueFromAddress("value", self.type_info.address_of.GetValueAsUnsigned(0), self.type_info.GetType()) | |
except Exception as e: | |
print(traceback.format_exc()) | |
def has_children(self): | |
return self.type_info is not None | |
# unions ======================================================================= | |
def is_union(type, internal_dict): | |
if type.type != lldb.eTypeClassUnion: | |
return False | |
first_field = type.GetFieldAtIndex(0) | |
return first_field.name == "v0" or first_field.name == "tag" | |
def union_summary(value, internal_dict): | |
variant = value.GetChildMemberWithName("value") | |
if not variant.IsValid(): | |
return "nil" | |
# The default lldb format includes the field name (`value`) which is irrelevant, | |
# so we do the formatting ourselves in order to omit it. | |
summary = variant.GetSummary() | |
if summary is None: | |
summary = variant.GetValue() | |
return f"({variant.GetDisplayTypeName()}) {summary}" | |
class UnionSynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.has_v0 = self.value.GetChildMemberWithName("v0") is not None | |
def num_children(self): | |
return 1 | |
def get_child_index(self, name): | |
if name == "value": | |
return 0 | |
return None | |
def get_child_at_index(self, index): | |
if index != 0: | |
raise ValueError(f"there is no field at index {index}, unions only have one field so the index can't be anything other than 0") | |
value = self.value.GetNonSyntheticValue() | |
tag_value = value.GetChildMemberWithName("tag").GetValueAsUnsigned(0) | |
variant_name = f"v{tag_value}" | |
variant = value.GetChildMemberWithName(variant_name) | |
return variant.CreateValueFromAddress("value", variant.address_of.GetValueAsUnsigned(0), variant.GetType()) | |
def has_children(self): | |
return True | |
# maps ========================================================================= | |
def is_map(type, internal_dict): | |
if type.type != lldb.eTypeClassStruct: | |
return False | |
return type.name.startswith("map[") | |
def map_summary(value, internal_dict): | |
raw = value.GetNonSyntheticValue() | |
len = raw.GetChildMemberWithName("len") | |
cap_log2 = raw.GetChildMemberWithName("data").GetValueAsUnsigned(0) & 63 | |
cap = 0 if cap_log2 <= 0 else 1 << cap_log2 | |
return f"len = {len.GetValueAsSigned(0)}, cap = {cap}" | |
class MapSynth: | |
def __init__(self, value, internal_dict): | |
self.value = value | |
self.update() | |
def update(self): | |
raw = self.value.GetNonSyntheticValue() | |
self.len = raw.GetChildMemberWithName("len").GetValueAsSigned() | |
self.data = raw.GetChildMemberWithName("data") | |
data_addr = self.data.GetValueAsUnsigned(0) | |
self.base_addr = data_addr & ~63 | |
cap_log2 = data_addr & 63 | |
self.cap = 0 if cap_log2 <= 0 else 1 << cap_log2 | |
self.map_key = self.data.GetChildMemberWithName("key") | |
self.map_key_type = self.map_key.GetType() | |
self.map_key_cell = self.data.GetChildMemberWithName("key_cell") | |
self.map_key_cell_type = self.map_key_cell.GetType() | |
self.map_key_base_addr = self.base_addr | |
self.map_value = self.data.GetChildMemberWithName("value") | |
self.map_value_type = self.map_value.GetType() | |
self.map_value_cell = self.data.GetChildMemberWithName("value_cell") | |
self.map_value_cell_type = self.map_value_cell.GetType() | |
self.map_value_base_addr = self.map_key_base_addr + self.map_cell_offset(self.map_key_type, self.map_key_cell_type, self.cap) | |
self.map_hash = self.data.GetChildMemberWithName("hash") | |
self.map_hash_type = self.map_hash.GetType() | |
self.tombstone_mask = 1 << (self.map_hash_type.GetByteSize() * 8 - 1) | |
self.map_hash_base_addr = self.map_value_base_addr + self.map_cell_offset(self.map_value_type, self.map_value_cell_type, self.cap) | |
self.map_hashes = self.value.CreateValueFromAddress("hashes", self.map_hash_base_addr, self.map_hash_type.GetArrayType(self.cap)) | |
def map_cell_offset(self, elem_type, elem_cell_type, index): | |
if elem_type.GetByteSize() == 0: | |
return 0 | |
elems_per_cell = elem_cell_type.GetByteSize() // elem_type.GetByteSize() | |
cell_index = index // elems_per_cell | |
data_index = index % elems_per_cell | |
return (cell_index * elem_cell_type.GetByteSize()) + (data_index * elem_type.GetByteSize()) | |
def num_children(self): | |
return self.len | |
def has_children(self): | |
return self.len > 0 | |
def get_child_at_index(self, index): | |
current_index = 0 | |
for i in range(self.cap): | |
hash = self.map_hashes.GetChildAtIndex(i).GetValueAsUnsigned() | |
if hash == 0 or (hash & self.tombstone_mask) != 0: | |
continue | |
current_index += 1 | |
if current_index - 1 != index: | |
continue | |
key_addr = self.map_key_base_addr + self.map_cell_offset(self.map_key_type, self.map_key_cell_type, i) | |
val_addr = self.map_value_base_addr + self.map_cell_offset(self.map_value_type, self.map_value_cell_type, i) | |
key = self.value.CreateValueFromAddress("key", key_addr, self.map_key_type) | |
if self.map_value_type.GetByteSize() == 0: | |
return key | |
name = key.GetSummary() | |
if name is None: | |
name = key.GetValue() | |
return self.value.CreateValueFromAddress(name, val_addr, self.map_value_type) | |
# type introspection =========================================================== | |
def find_type_table(target): | |
for var in target.FindGlobalVariables("type_table", 69): | |
if var.GetDisplayTypeName() == "[]^runtime.Type_Info": | |
return var | |
return None | |
def type_info_of(typeid): | |
id = int(typeid.GetValueAsUnsigned(0)) | |
mask = (1 << (8 * typeid.GetByteSize() - 8)) - 1 | |
index = (id & mask) | |
type_table = find_type_table(typeid.GetTarget()).GetNonSyntheticValue() | |
type_table_data = type_table.GetChildMemberWithName("data") | |
element_type = type_table_data.GetType().GetPointeeType() | |
base = type_table_data.GetValueAsUnsigned(0) | |
offset = index * element_type.GetByteSize() | |
return type_table_data.CreateValueFromAddress("", base + offset, element_type).Dereference() | |
def type_of(type_info): | |
target = type_info.GetTarget() | |
size = type_info.GetChildMemberWithName("size").GetValueAsSigned(0) | |
bit_size = size * 8 | |
variant = type_info.GetChildMemberWithName("variant").GetNonSyntheticValue() | |
# print("@variant", variant) | |
variant_tag = variant.GetChildMemberWithName("tag").GetValueAsUnsigned(0) | |
# print("@variant_tag", variant_tag) | |
variant_data = variant.GetChildMemberWithName(f"v{variant_tag}") | |
# print("@variant_data", variant_data) | |
match variant_tag: | |
case 1: # Type_Info_Named | |
prefix = "" | |
base = variant_data.GetChildMemberWithName("base").Dereference() | |
# print("@base", base) | |
match base.GetChildMemberWithName("variant").GetNonSyntheticValue().GetChildMemberWithName("tag").GetValueAsUnsigned(): | |
case 5: # Type_Info_Complex | |
prefix = "struct" | |
case 6: # Type_Info_Quaternion | |
prefix = "struct" | |
case 7: # Type_Info_String | |
prefix = "struct" | |
case 9: # Type_Info_Any | |
prefix = "struct" | |
case 16: # Type_Info_Dynamic_Array | |
prefix = "struct" | |
case 17: # Type_Info_Slice | |
prefix = "struct" | |
case 19: # Type_Info_Struct | |
prefix = "struct" | |
case 20: # Type_Info_Union | |
prefix = "union" | |
case 21: # Type_Info_Enum | |
prefix = "enum" | |
case 22: # Type_Info_Map | |
prefix = "struct" | |
case 27: # Type_Info_Bit_Field | |
prefix = "struct" # TODO: confirm | |
# print("@prefix", prefix) | |
name = read_string(variant_data.GetChildMemberWithName("name")) | |
# print("@name", name) | |
pkg = read_string(variant_data.GetChildMemberWithName("pkg")) | |
# print("@pkg", pkg) | |
# print("@key", f"{prefix} {pkg}.{name}") | |
# print("@ret", target.FindFirstType(f"{prefix} {pkg}.{name}")) | |
return target.FindFirstType(f"{prefix} {pkg}.{name}") | |
case 2: # Type_Info_Integer | |
signed = variant_data.GetChildMemberWithName("signed").GetValueAsSigned(0) == 1 | |
endianness = variant_data.GetChildMemberWithName("endianness") # TODO: care | |
return target.FindFirstType(f"{"i" if signed else "u"}{bit_size}") | |
case 3: # Type_Info_Rune | |
return target.FindFirstType("rune") | |
case 4: # Type_Info_Float | |
endianness = variant_data.GetChildMemberWithName("endianness") # TODO: care | |
return target.FindFirstType(f"f{bit_size}") | |
case 5: # Type_Info_Complex | |
return target.FindFirstType(f"struct complex{bit_size}") | |
case 6: # Type_Info_Quaternion | |
return target.FindFirstType(f"struct quaternion{bit_size}") | |
case 7: # Type_Info_String | |
return target.FindFirstType(f"struct string") | |
case 8: # Type_Info_Boolean | |
if bit_size == 8: | |
return target.FindFirstType("bool") | |
return target.FindFirstType(f"b{bit_size}") | |
case 9: # Type_Info_Any | |
return target.FindFirstType(f"struct any") | |
case 10: # Type_Info_Type_Id | |
return target.FindFirstType(f"typeid") | |
case 11: # Type_Info_Pointer | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return type_of(elem).GetPointerType() | |
case 12: # Type_Info_Multi_Pointer | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return type_of(elem).GetPointerType() | |
case 13: # Type_Info_Procedure | |
return None | |
case 14: # Type_Info_Array | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return type_of(elem).GetArrayType(variant_data.GetValueAsSigned("count")) | |
case 15: # Type_Info_Enumerated_Array | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return type_of(elem).GetArrayType(variant_data.GetValueAsSigned("count")) | |
case 16: # Type_Info_Dynamic_Array | |
return target.FindFirstType(f"struct {odin_name(type_info)}") | |
case 17: # Type_Info_Slice | |
return target.FindFirstType(f"struct {odin_name(type_info)}") | |
case 18: # Type_Info_Parameters | |
return None | |
case 19: # Type_Info_Struct | |
return None | |
case 20: # Type_Info_Union | |
return None | |
case 21: # Type_Info_Enum | |
return None | |
case 22: # Type_Info_Map | |
return target.FindFirstType(f"struct {odin_name(type_info)}") | |
case 23: # Type_Info_Bit_Set | |
return None | |
case 24: # Type_Info_Simd_Vector | |
return None | |
case 25: # Type_Info_Matrix | |
return None | |
case 26: # Type_Info_Soa_Pointer | |
return None | |
case 27: # Type_Info_Bit_Field | |
return None | |
def odin_name(type_info): | |
target = type_info.GetTarget() | |
size = type_info.GetChildMemberWithName("size").GetValueAsSigned(0) | |
bit_size = size * 8 | |
variant = type_info.GetChildMemberWithName("variant").GetNonSyntheticValue() | |
variant_tag = variant.GetChildMemberWithName("tag").GetValueAsUnsigned(0) | |
variant_data = variant.GetChildMemberWithName(f"v{variant_tag}") | |
match variant_tag: | |
case 1: # Type_Info_Named | |
name = read_string(variant_data.GetChildMemberWithName("name")) | |
pkg = read_string(variant_data.GetChildMemberWithName("pkg")) | |
return f"{pkg}.{name}" | |
case 2: # Type_Info_Integer | |
signed = variant_data.GetChildMemberWithName("signed").GetValueAsSigned(0) == 1 | |
endianness = variant_data.GetChildMemberWithName("endianness") # TODO: care | |
return f"{"i" if signed else "u"}{bit_size}" | |
case 3: # Type_Info_Rune | |
return "rune" | |
case 4: # Type_Info_Float | |
endianness = variant_data.GetChildMemberWithName("endianness") # TODO: care | |
return f"f{bit_size}" | |
case 5: # Type_Info_Complex | |
return f"complex{bit_size}" | |
case 6: # Type_Info_Quaternion | |
return f"quaternion{bit_size}" | |
case 7: # Type_Info_String | |
return "string" | |
case 8: # Type_Info_Boolean | |
return f"b{bit_size}" | |
case 9: # Type_Info_Any | |
return "any" | |
case 10: # Type_Info_Type_Id | |
return "typeid" | |
case 11: # Type_Info_Pointer | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return f"^{odin_name(elem)}" | |
case 12: # Type_Info_Multi_Pointer | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return f"^{odin_name(elem)}" | |
case 13: # Type_Info_Procedure | |
return None | |
case 14: # Type_Info_Array | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
count = variant_data.GetChildMemberWithName("count").GetValueAsSigned(0) | |
return f"[{count}]{odin_name(elem)}" | |
case 15: # Type_Info_Enumerated_Array | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
count = variant_data.GetChildMemberWithName("count").GetValueAsSigned(0) | |
return f"[{count}]{odin_name(elem)}" # TODO: confirm that there is indeed no way to retrieve the enum name | |
case 16: # Type_Info_Dynamic_Array | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return f"[dynamic]{odin_name(elem)}" | |
case 17: # Type_Info_Slice | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return f"[]{odin_name(elem)}" | |
case 18: # Type_Info_Parameters | |
return None | |
case 19: # Type_Info_Struct | |
return None | |
case 20: # Type_Info_Union | |
return None | |
case 21: # Type_Info_Enum | |
return None | |
case 22: # Type_Info_Map | |
key = variant_data.GetChildMemberWithName("key").Dereference() | |
value = variant_data.GetChildMemberWithName("value").Dereference() | |
return f"[{odin_name(key)}]{odin_name(value)}" | |
case 23: # Type_Info_Bit_Set | |
return None | |
case 24: # Type_Info_Simd_Vector | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
count = variant_data.GetChildMemberWithName("count").GetValueAsSigned(0) | |
return f"#simd[{count}]{odin_name(elem)}" | |
case 25: # Type_Info_Matrix | |
#column_major matrix[2,3]f32 | |
# TODO: row/col major tag | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
row_count = variant_data.GetChildMemberWithName("row_count").GetValueAsSigned(0) | |
col_count = variant_data.GetChildMemberWithName("column_count").GetValueAsSigned(0) | |
return f"matrix[{row_count}, {col_count}]{odin_name(elem)}" | |
case 26: # Type_Info_Soa_Pointer | |
elem = variant_data.GetChildMemberWithName("elem").Dereference() | |
return f"#soa ^{odin_name(elem)}" | |
return None | |
case 27: # Type_Info_Bit_Field | |
return None | |
# helpers ====================================================================== | |
def read_string(odin_string): | |
pointer = odin_string.GetChildMemberWithName("data").GetValueAsUnsigned(0) | |
length = odin_string.GetChildMemberWithName("len").GetValueAsUnsigned(0) | |
if pointer == 0 or length == 0: | |
return '' | |
error = lldb.SBError() | |
content = odin_string.process.ReadMemory(pointer, length, error) | |
assert error.Success(), error | |
return content.decode("utf-8", "ignore") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some notes that might help people (mainly aimed at using VSCode).
flga (the author of the OP) recommended me to use LLDB DAP instead of the CodeLLDB extension.
Then for mac users:
If you type
lldb
in the terminal followed byscript import sys; print(sys.version)
then it will report the python version:Mine was:
And
script import sys; print(sys.executable)
told me:So looks like lldb in xcode is using a kinda old python version in the sense that things like
match
, which is used in theodin.py
script, won't work.I struggled with
PATH
and so on, but in the end the only thing that worked for me was using thelldb-dap.executable-path
setting:settings.json
In the debug console you should see something like:
I do get a bunch of errors, but I'm 99% sure that that is because
odin.py
does not check againstNone
.