# Copyright (C) 2022-2024 Free Software Foundation, Inc.
#
# This file is part of GNU Emacs.
#
# GNU Emacs is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Emacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Emacs. If not, see .
# Load this module in LLDB with
#
# (lldb) command script import emacs_lldb
#
# Available commands start with 'x' and can be seen with
#
# (lldb) help
import lldb
########################################################################
# Utilities
########################################################################
# Return the name of enumerator ENUM as a string.
def enumerator_name(enum):
enumerators = enum.GetType().GetEnumMembers()
for enum_member in enumerators:
if enum.GetValueAsUnsigned() == enum_member.GetValueAsUnsigned():
return enum_member.GetName()
return None
# A class wrapping an SBValue for a Lisp_Object, providing convenience
# functions.
class Lisp_Object:
# Map pvec_type enumerators to corresponding C types.
pvec2type = {
"PVEC_FRAME": "struct frame",
"PVEC_WINDOW": "struct window",
"PVEC_BIGNUM": "struct Lisp_Bignum",
"PVEC_MARKER": "struct Lisp_Marker",
"PVEC_OVERLAY": "struct Lisp_Overlay",
"PVEC_FINALIZER": "struct Lisp_Finalizer",
"PVEC_SYMBOL_WITH_POS": "struct Lisp_Symbol_With_Pos",
"PVEC_MISC_PTR": "",
"PVEC_USER_PTR": "struct Lisp_User_Ptr",
"PVEC_PROCESS": "struct Lisp_Process",
"PVEC_BOOL_VECTOR": "struct Lisp_Bool_Vector",
"PVEC_BUFFER": "struct buffer",
"PVEC_HASH_TABLE": "struct Lisp_Hash_Table",
"PVEC_OBARRAY": "struct Lisp_Obarray",
"PVEC_TERMINAL": "struct terminal",
"PVEC_WINDOW_CONFIGURATION": "struct save_window_data",
"PVEC_SUBR": "struct Lisp_Subr",
"PVEC_OTHER": "void",
"PVEC_XWIDGET": "void",
"PVEC_XWIDGET_VIEW": "void",
"PVEC_THREAD": "struct thread_state",
"PVEC_MUTEX": "Lisp_Mutex",
"PVEC_CONDVAR": "Lisp_CondVar",
"PVEC_MODULE_FUNCTION": "struct Lisp_Module_Function",
"PVEC_NATIVE_COMP_UNIT": "struct Lisp_Native_Comp_Unit",
"PVEC_SQLITE": "struct Lisp_Sqlite",
"PVEC_CLOSURE": "struct Lisp_Vector",
"PVEC_CHAR_TABLE": "struct Lisp_Vector",
"PVEC_SUB_CHAR_TABLE": "struct Lisp_Sub_Char_Table",
"PVEC_RECORD": "struct Lisp_Vector",
"PVEC_FONT": "struct font",
"PVEC_NORMAL_VECTOR": "struct Lisp_Vector",
"PVEC_TS_NODE": "struct Lisp_TS_Node",
"PVEC_TS_PARSER": "struct Lisp_TS_Parser",
"PVEC_TS_COMPILED_QUERY": "struct Lisp_TS_Query",
}
# Object construction/initialization.
def __init__(self, lisp_obj):
self.tagged = lisp_obj
self.unsigned = None
self.lisp_type = None
self.pvec_type = None
self.untagged = None
self.init_unsigned()
self.init_lisp_types()
self.init_values()
def init_unsigned(self):
if self.tagged.GetType().GetTypeClass() == lldb.eTypeClassStruct:
# Lisp_Object is actually a struct.
lisp_word = self.tagged.GetValueForExpressionPath(".i")
self.unsigned = lisp_word.GetValueAsUnsigned()
else:
self.unsigned = self.tagged.GetValueAsUnsigned()
# Initialize self.lisp_type to the C Lisp_Type enumerator of the
# Lisp_Object, as a string. Initialize self.pvec_type likewise to
# the pvec_type enumerator if the object is a vector-like, as a
# string.
def init_lisp_types(self):
GCTYPEBITS = self.unsigned_const('GCTYPEBITS')
self.lisp_type = self.tag_name(self.unsigned
& ((1 << GCTYPEBITS) - 1))
if self.lisp_type == "Lisp_Vectorlike":
self.pvec_type = "PVEC_NORMAL_VECTOR"
vector = self.get_lisp_pointer("struct Lisp_Vector")
size = vector.GetValueForExpressionPath("->header.size")
size = size.GetValueAsUnsigned()
PSEUDOVECTOR_FLAG = self.unsigned_const('PSEUDOVECTOR_FLAG')
if size & PSEUDOVECTOR_FLAG:
PVEC_TYPE_MASK = self.unsigned_const(
'More_Lisp_Bits::PVEC_TYPE_MASK')
PSEUDOVECTOR_AREA_BITS = self.unsigned_const(
'More_Lisp_Bits::PSEUDOVECTOR_AREA_BITS')
pvec = (size & PVEC_TYPE_MASK) >> PSEUDOVECTOR_AREA_BITS
self.pvec_type = self.pvec_name(pvec)
# Initialize self.untagged according to lisp_type and pvec_type.
def init_values(self):
lt = self.lisp_type
if lt == "Lisp_Symbol":
offset = self.get_lisp_pointer("char").GetValueAsUnsigned()
self.untagged = self.eval(
f"(struct Lisp_Symbol *)((char *)&lispsym + {offset})")
elif lt == "Lisp_String":
self.untagged = self.get_lisp_pointer("struct Lisp_String")
elif lt == "Lisp_Vectorlike":
c_type = Lisp_Object.pvec2type[self.pvec_type]
self.untagged = self.get_lisp_pointer(c_type)
elif lt == "Lisp_Cons":
self.untagged = self.get_lisp_pointer("struct Lisp_Cons")
elif lt == "Lisp_Float":
self.untagged = self.get_lisp_pointer("struct Lisp_Float")
elif lt in ("Lisp_Int0", "Lisp_Int1"):
GCTYPEBITS = self.unsigned_const('GCTYPEBITS')
x = self.unsigned >> (GCTYPEBITS - 1)
self.untagged = self.eval(f"(EMACS_INT){x})")
elif lt == "Lisp_Type_Unused0":
self.untagged = self.unsigned
else:
assert False, f"Unknown Lisp type {self.lisp_type}"
# Get a numeric constant (unsigned).
const_cache = {}
def unsigned_const(self, name):
val = self.const_cache.get(name)
if val is None:
frame = self.tagged.GetFrame()
val = frame.EvaluateExpression(name).GetValueAsUnsigned()
self.const_cache[name] = val
return val
# Get the name of a Lisp_Object tag value, like "Lisp_String".
tag_cache = {}
def tag_name(self, tag):
name = self.tag_cache.get(tag)
if name is None:
frame = self.tagged.GetFrame()
val = frame.EvaluateExpression(f"(enum Lisp_Type){tag}")
name = enumerator_name(val)
self.tag_cache[tag] = name
return name
# Get the name of a pseudovector type tag, like "PVEC_HASH_TABLE".
pvec_cache = {}
def pvec_name(self, pvec):
name = self.pvec_cache.get(pvec)
if name is None:
frame = self.tagged.GetFrame()
val = frame.EvaluateExpression(f"(enum pvec_type){pvec}")
name = enumerator_name(val)
self.pvec_cache[pvec] = name
return name
# Evaluate EXPR in the context of the current frame.
eval_cache = {}
def eval(self, expr):
val = self.eval_cache.get(expr)
if val is None:
frame = self.tagged.GetFrame()
val = frame.EvaluateExpression(expr)
self.eval_cache[expr] = val
return val
# Return an SBValue for this object denoting a pointer of type
# TYP*.
lisp_ptr_cache = {}
def get_lisp_pointer(self, typ):
uns = self.unsigned
ptr = self.lisp_ptr_cache.get((uns, typ))
if ptr is None:
VALMASK = self.unsigned_const('VALMASK')
frame = self.tagged.GetFrame()
ptr = frame.EvaluateExpression(
f"({typ}*)(EMACS_INT){uns & VALMASK}")
self.lisp_ptr_cache[(uns, typ)] = ptr
return ptr
# If this is a Lisp_String, return an SBValue for its string data.
# Return None otherwise.
def get_string_data(self):
if self.lisp_type == "Lisp_String":
return self.untagged.GetValueForExpressionPath("->u.s.data")
return None
# if this is a Lisp_Symbol, return an SBBalue for its name.
# Return None otherwise.
def get_symbol_name(self):
if self.lisp_type == "Lisp_Symbol":
name = self.untagged.GetValueForExpressionPath("->u.s.name")
return Lisp_Object(name).get_string_data()
return None
# Return a summary string for this object.
def summary(self):
return str(self.untagged)
########################################################################
# LLDB Commands
########################################################################
def xbacktrace(debugger, command, ctx, result, internal_dict):
"""Print Emacs Lisp backtrace"""
frame = ctx.GetFrame()
n = frame.EvaluateExpression(
"current_thread->m_specpdl_ptr - current_thread->m_specpdl")
for i in reversed(range(0, n.GetValueAsUnsigned())):
s = frame.EvaluateExpression(f"current_thread->m_specpdl[{i}]")
kind = enumerator_name(s.GetChildMemberWithName("kind"))
if kind == "SPECPDL_BACKTRACE":
function = Lisp_Object(s.GetValueForExpressionPath(".bt.function"))
if function.lisp_type == "Lisp_Symbol":
sym_name = function.get_symbol_name()
result.AppendMessage(str(sym_name))
elif function.lisp_type == "Lisp_Vectorlike":
result.AppendMessage(function.pvec_type)
else:
result.AppendMessage(function.lisp_type)
def xdebug_print(debugger, command, result, internal_dict):
"""Print Lisp_Objects using safe_debug_print()"""
debugger.HandleCommand(f"expr safe_debug_print({command})")
########################################################################
# Formatters
########################################################################
def type_summary_Lisp_Object(obj, internal_dict):
return Lisp_Object(obj).summary()
class Lisp_Object_Provider:
"""Synthetic children provider for Lisp_Objects.
Supposedly only used by 'frame variable', where -P can be used
to specify a printing depth. """
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.children = {}
def update(self):
lisp_obj = Lisp_Object(self.valobj)
lisp_type = lisp_obj.lisp_type
try:
if lisp_type == "Lisp_Symbol":
child = lisp_obj.get_symbol_name()
self.children["name"] = child
elif lisp_type == "Lisp_String":
child = lisp_obj.get_string_data()
self.children["data"] = child
elif lisp_type == "Lisp_Cons":
car = lisp_obj.untagged.GetValueForExpressionPath("->u.s.car")
cdr = lisp_obj.untagged.GetValueForExpressionPath("->u.s.u.cdr")
self.children["car"] = car
self.children["cdr"] = cdr
else:
self.children["untagged"] = lisp_obj.untagged
except:
print(f"*** exception in child provider update for {lisp_type}")
pass
def num_children(self):
return len(self.children)
def get_child_index(self, name):
index = 0
for child_name, child in self.children:
if child_name == name:
return index
index = index + 1
return -1
def get_child_at_index(self, index):
key = list(self.children)[index]
return self.children[key]
########################################################################
# Initialization
########################################################################
# Define Python FUNCTION as an LLDB command.
def define_command (debugger, function):
lldb_command = function.__name__
python_function = __name__ + "." + function.__name__
interpreter = debugger.GetCommandInterpreter()
def define(overwrite):
res = lldb.SBCommandReturnObject()
interpreter.HandleCommand(f"command script add "
f"{overwrite} "
f"--function {python_function} "
f"{lldb_command}",
res)
return res.Succeeded()
if not define("--overwrite"):
define("")
# Define Python FUNCTION as an LLDB type summary provider for types
# matching REGEX. Type summaries defined here are defined in the
# category Emacs, and can be seen with 'type summary list -w Emacs',
# and deleted in a similar way.
def define_type_summary(debugger, regex, function):
python_function = __name__ + "." + function.__name__
debugger.HandleCommand(f"type summary add --expand "
f"--cascade true "
f"--category Emacs "
f"--python-function {python_function} "
+ regex)
# Define Python class CLS as a children provider for the types
# matching REFEXP. Providers are defined in the category Emacs, and
# can be seen with 'type synthetic list -w Emacs', and deleted in a
# similar way.
def define_type_synthetic(debugger, regex, cls):
python_class = __name__ + "." + cls.__name__
debugger.HandleCommand(f"type synthetic add "
f"--category Emacs "
f"--python-class {python_class} "
+ regex)
# Enable a given category of type summary providers.
def enable_type_category(debugger, category):
debugger.HandleCommand(f"type category enable {category}")
# This function is called by LLDB to initialize the module.
def __lldb_init_module(debugger, internal_dict):
define_command(debugger, xbacktrace)
define_command(debugger, xdebug_print)
define_type_summary(debugger, "Lisp_Object", type_summary_Lisp_Object)
define_type_synthetic(debugger, "Lisp_Object", Lisp_Object_Provider)
enable_type_category(debugger, "Emacs")
print('Emacs debugging support has been installed.')
# end.