From 7af425f87bf9866c60ac134cbb6aa9eb0c61f8af Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gerd=20M=C3=B6llmann?= Date: Sun, 10 Jul 2022 13:35:32 +0200 Subject: [PATCH] Support for debugging Emacs with LLDB * (src/.lldbinit): New file. * (etc/emacs_lldb.py): Module loaded from .lldbinit. --- etc/DEBUG | 36 ++++++++++ etc/emacs_lldb.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++ src/.lldbinit | 33 +++++++++ 3 files changed, 242 insertions(+) create mode 100644 etc/emacs_lldb.py create mode 100644 src/.lldbinit diff --git a/etc/DEBUG b/etc/DEBUG index 7d2f810d078..df289310f9f 100644 --- a/etc/DEBUG +++ b/etc/DEBUG @@ -1036,6 +1036,42 @@ recovering the contents of Emacs buffers from a core dump file. You might also find those commands useful for displaying the list of buffers in human-readable format from within the debugger. +*** Debugging Emacs with LLDB + +On systems where GDB is not available, like macOS with M1 chip, you +can also use LLDB for Emacs debugging. + +To start LLDB to debug Emacs, you can simply type "lldb ./emacs RET" +at the shell prompt in directory of the Emacs executable, usually the +'src' sub-directory of the Emacs tree). + +When you debug Emacs with LLDB, you should start LLDB in the directory +where the Emacs executable was built. That directory has an .lldbinit +file that loads a Python module emacs_lldb.py from the 'etc' directory +of the Emacs source tree. The Python module defines "user-defined" +commands for debugging Emacs. + +LLDB by default does not automatically load .lldbinit files in the +current directory. The simplest way to fix this is to add the +following line to your ~/.lldbinit file (creating such a file if it +doesn't already exist): + + settings set target.load-cwd-lldbinit true + +Alternatively, you can type "lldb --local-lldbinit ./emacs RET". + +If everything worked, you should see something like "Emacs debugging +support has been installed" after starting LLDB. You can see which +Emacs-specific commands are defined with + + (lldb) help + +User-defined commands for Emacs debugging start with an "x". + +Please refer to the LLDB reference on the web for more information +about LLDB. If you already know GDB, you will also find a mapping +from GDB commands to corresponding LLDB commands there. + This file is part of GNU Emacs. diff --git a/etc/emacs_lldb.py b/etc/emacs_lldb.py new file mode 100644 index 00000000000..4e0b20c8a55 --- /dev/null +++ b/etc/emacs_lldb.py @@ -0,0 +1,173 @@ +# Copyright (C) 2022 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 + + +######################################################################## +# Utilties +######################################################################## + +# Return the Lisp_Type of Lisp_Object OBJ. +def get_lisp_type(obj): + int_value = obj.GetValueAsUnsigned() + return obj.GetFrame().EvaluateExpression( + f"(enum Lisp_Type) ((EMACS_INT) {int_value} " + "& (1 << GCTYPEBITS) - 1)") + +# Return the Lisp_Type or pseudo-vector type of OBJ. +def get_lisp_type_or_vectorlike(obj): + lisp_type = get_lisp_type(obj) + if enumerator_name(lisp_type) == "Lisp_Vectorlike": + vector = get_lisp_pointer(obj, "struct Lisp_Vector") + header_size = vector.GetValueForExpressionPath( + "->header.size").GetValueAsUnsigned() + frame = obj.GetFrame() + pseudo = frame.EvaluateExpression( + f"{header_size} & PSEUDOVECTOR_FLAG") + if pseudo.GetValueAsUnsigned() != 0: + return frame.EvaluateExpression( + f"(enum pvec_type) (({header_size} " + "& More_Lisp_Bits::PVEC_TYPE_MASK) " + ">> More_Lisp_Bits::PSEUDOVECTOR_AREA_BITS)") + return frame.EvaluateExpression("pvec_type::PVEC_NORMAL_VECTOR") + return lisp_type + +# Return Lisp_Object OBJ as pointer to TYP *. +def get_lisp_pointer(obj, typ): + return obj.GetFrame().EvaluateExpression( + f"({typ}*) (((EMACS_INT) {obj.GetValueAsUnsigned()}) & VALMASK)") + +# Return Lisp_Object OBJ as pointer to Lisp_Symbol. +def get_lisp_symbol(obj): + ptr = get_lisp_pointer(obj, "char") + offset = ptr.GetValueAsUnsigned() + return obj.GetFrame().EvaluateExpression( + f"(struct Lisp_Symbol *) ((char *) &lispsym + {offset})") + +# Return Lisp_Object OBJ as pointer to Lisp_String +def get_lisp_string(obj): + return get_lisp_pointer(obj, "struct Lisp_String") + +# Return the string data of Lisp_Object OBJ which denotes a Lisp_String. +def get_lisp_string_data(obj): + string = get_lisp_string(obj) + return string.GetValueForExpressionPath("->u.s.data") + +# Assuming OBJ denotes a Lisp_Symbol, return the name of the symbol. +def get_lisp_symbol_name(obj): + sym = get_lisp_symbol(obj) + name = sym.GetValueForExpressionPath("->u.s.name") + return get_lisp_string_data(name) + +# Return a string for the enuerator ENUM. +def enumerator_name(enum): + enumerators = enum.GetType().GetEnumMembers() + return enumerators[enum.GetValueAsUnsigned()].GetName() + + +######################################################################## +# 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 = s.GetValueForExpressionPath(".bt.function") + function_type = enumerator_name(get_lisp_type(function)) + if function_type == "Lisp_Symbol": + sym_name = get_lisp_symbol_name(function) + result.AppendMessage(str(sym_name)) + elif function_type == "Lisp_Vectorlike": + subtype = get_lisp_type_or_vectorlike(function) + result.AppendMessage(str(subtype)) + else: + result.AppendMessage(function_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 +######################################################################## + +# Return a type summary for Lisp_Objects. +def format_Lisp_Object(obj, internal_dict): + lisp_type = get_lisp_type_or_vectorlike(obj) + kind = enumerator_name(lisp_type) + summary = "-> " + if kind == "PVEC_FRAME": + ptr = get_lisp_pointer(obj, "struct frame") + summary += str(ptr) + elif kind == "PVEC_WINDOW": + ptr = get_lisp_pointer(obj, "struct window") + summary += str(ptr) + return summary + + +######################################################################## +# 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 formatter. +def define_formatter(debugger, regex, function): + python_function = __name__ + "." + function.__name__ + debugger.HandleCommand(f"type summary add " + f"--cascade true " + f'--regex "{regex}" ' + f"--python-function {python_function}") + +# 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_formatter(debugger, "Lisp_Object", format_Lisp_Object) + print('Emacs debugging support has been installed.') + +# end. diff --git a/src/.lldbinit b/src/.lldbinit new file mode 100644 index 00000000000..617d63958bd --- /dev/null +++ b/src/.lldbinit @@ -0,0 +1,33 @@ +# -*- mode: shell-script -*- +# Copyright (C) 1992-1998, 2000-2022 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 . +# +# Use 'lldb --local-init' or add to your ~/.lldbinit the line +# +# settings set target.load-cwd-lldbinit true +# +# Emacs-specific commands start with 'x'. Type 'help' to see all +# commands. Type 'help ' to see help for a command +# . + +# Make Python find our files +script -- sys.path.append('../etc') + +# Load our Python files +command script import emacs_lldb + +# end. -- 2.39.5