diff options
author | Gunnar Beutner <gunnar@beutner.name> | 2021-04-20 18:37:33 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-04-22 00:45:43 +0200 |
commit | 232e64c10a212fad56c4f90b62de84ffb3e7cdeb (patch) | |
tree | 8147cdcf2ae96b921f925296be205a2d7d975817 | |
parent | ba261aba72373d595149453bae175645b3097d52 (diff) | |
download | serenity-232e64c10a212fad56c4f90b62de84ffb3e7cdeb.zip |
Meta: Add GDB pretty printers
-rwxr-xr-x | Meta/debug-kernel.sh | 6 | ||||
-rwxr-xr-x | Meta/serenity.sh | 3 | ||||
-rw-r--r-- | Meta/serenity_gdb.py | 274 |
3 files changed, 280 insertions, 3 deletions
diff --git a/Meta/debug-kernel.sh b/Meta/debug-kernel.sh index 471808e358..0f78ee6ceb 100755 --- a/Meta/debug-kernel.sh +++ b/Meta/debug-kernel.sh @@ -8,7 +8,9 @@ # remote on localhost:1234. So point our debugger there, and inform # the debugger which binary to load symbols, etc from. # -$SERENITY_KERNEL_DEBUGGER \ +exec $SERENITY_KERNEL_DEBUGGER \ -ex "file $(dirname "$0")/../Build/${SERENITY_ARCH:-i686}/Kernel/Kernel" \ -ex 'set arch i386:intel' \ - -ex 'target remote localhost:1234' + -ex 'target remote localhost:1234' \ + -ex "source $(dirname "$0")/serenity_gdb.py" \ + "$@" diff --git a/Meta/serenity.sh b/Meta/serenity.sh index 77672bc3c4..2cfa165e0f 100755 --- a/Meta/serenity.sh +++ b/Meta/serenity.sh @@ -201,7 +201,8 @@ run_gdb() { if [ -n "$KERNEL_CMD_LINE" ]; then export SERENITY_KERNEL_CMDLINE="$KERNEL_CMD_LINE" fi - gdb "$BUILD_DIR/Kernel/Kernel" -ex 'target remote :1234' "${GDB_ARGS[@]}" -ex cont + sleep 1 + "$(get_top_dir)/Meta/debug-kernel.sh" "${GDB_ARGS[@]}" -ex cont fi } diff --git a/Meta/serenity_gdb.py b/Meta/serenity_gdb.py new file mode 100644 index 0000000000..cfb2c72f79 --- /dev/null +++ b/Meta/serenity_gdb.py @@ -0,0 +1,274 @@ +# Copyright (c) 2021, Gunnar Beutner <gunnar@beutner.name> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import gdb +import gdb.types +import re + + +class AKAtomic: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val["m_value"] + + +class AKDistinctNumeric: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val["m_value"] + + +class AKRefCounted: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val["m_ref_count"] + + +class AKString: + def __init__(self, val): + self.val = val + + def to_string(self): + if int(self.val["m_impl"]["m_bits"]["m_value"]) == 0: + return '""' + else: + impl = AKRefPtr(self.val["m_impl"]).get_pointee().dereference() + return AKStringImpl(impl).to_string() + + +class AKStringView: + def __init__(self, val): + self.val = val + + def to_string(self): + if int(self.val["m_length"]) == 0: + return '""' + else: + characters = self.val["m_characters"] + str_type = characters.type.target().array(self.val["m_length"]).pointer() + return str(characters.cast(str_type).dereference()) + + +def get_field_unalloced(val, member, type): + # Trying to access a variable-length field seems to fail with + # Python Exception <class 'gdb.error'> value requires 4294967296 bytes, which is more than max-value-size + # This works around that issue. + return gdb.parse_and_eval(f"*({type}*)(({val.type.name}*){int(val.address)})->{member}") + + +class AKStringImpl: + def __init__(self, val): + self.val = val + + def to_string(self): + if int(self.val["m_length"]) == 0: + return '""' + else: + str_type = gdb.lookup_type("char").array(self.val["m_length"]) + return get_field_unalloced(self.val, "m_inline_buffer", str_type) + + +class AKOwnPtr: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val.type.name + + def children(self): + return [('*', self.val["m_ptr"])] + + +class AKRefPtr: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val.type.name + + def get_pointee(self): + inner_type = self.val.type.template_argument(0) + inner_type_ptr = inner_type.pointer() + return self.val["m_bits"]["m_value"].cast(inner_type_ptr) + + def children(self): + return [('*', self.get_pointee())] + + +class AKVector: + def __init__(self, val): + self.val = val + + def to_string(self): + return f'{self.val.type.name} of len {int(self.val["m_size"])}' + + def children(self): + vec_len = int(self.val["m_size"]) + + if vec_len == 0: + return [] + + outline_buf = self.val["m_outline_buffer"] + + inner_type_ptr = self.val.type.template_argument(0).pointer() + + if int(outline_buf) != 0: + elements = outline_buf.cast(inner_type_ptr) + else: + elements = get_field_unalloced(self.val, "m_inline_buffer_storage", inner_type_ptr) + + return [(f"[{i}]", elements[i]) for i in range(vec_len)] + + +class AKHashMapPrettyPrinter: + def __init__(self, val): + self.val = val + + @staticmethod + def _iter_hashtable(val, cb): + entry_type_ptr = val.type.template_argument(0).pointer() + buckets = val["m_buckets"] + for i in range(0, val["m_capacity"]): + bucket = buckets[i] + if bucket["used"]: + cb(bucket["storage"].cast(entry_type_ptr)) + + @staticmethod + def _iter_hashmap(val, cb): + table = val["m_table"] + AKHashMapPrettyPrinter._iter_hashtable(table, lambda entry: cb(entry["key"], entry["value"])) + + def to_string(self): + return self.val.type.name + + def children(self): + elements = [] + + def cb(key, value): + nonlocal elements + elements.append((f"[{key}]", value)) + + AKHashMapPrettyPrinter._iter_hashmap(self.val, cb) + return elements + + +class VirtualAddress: + def __init__(self, val): + self.val = val + + def to_string(self): + return self.val["m_address"] + + +class SerenityPrettyPrinterLocator(gdb.printing.PrettyPrinter): + def __init__(self): + super(SerenityPrettyPrinterLocator, self).__init__("serenity_pretty_printers", []) + + self.re = re.compile('^([^<]+)(<.*>)?$') + + def __call__(self, val): + type = gdb.types.get_basic_type(val.type) + typename = str(type.tag) + + match = self.re.match(typename) + if not match: + return None + + klass = match.group(1) + + if klass == 'AK::Atomic': + return AKAtomic(val) + elif klass == 'AK::DistinctNumeric': + return AKDistinctNumeric(val) + elif klass == 'AK::HashMap': + return AKHashMapPrettyPrinter(val) + elif klass == 'AK::RefCounted': + return AKRefCounted(val) + elif klass == 'AK::RefPtr': + return AKRefPtr(val) + elif klass == 'AK::OwnPtr': + return AKOwnPtr(val) + elif klass == 'AK::NonnullRefPtr': + return AKRefPtr(val) + elif klass == 'AK::String': + return AKString(val) + elif klass == 'AK::StringView': + return AKStringView(val) + elif klass == 'AK::StringImpl': + return AKStringImpl(val) + elif klass == 'AK::Vector': + return AKVector(val) + elif klass == 'VirtualAddress': + return VirtualAddress(val) + + +gdb.printing.register_pretty_printer(None, SerenityPrettyPrinterLocator(), replace=True) + + +class FindThreadCmd(gdb.Command): + """ + Find SerenityOS thread for the specified TID. + find_thread TID + """ + + def __init__(self): + super(FindThreadCmd, self).__init__( + "find_thread", gdb.COMMAND_USER + ) + + def _find_thread(self, tid): + threads = gdb.parse_and_eval("Kernel::Thread::g_tid_map") + thread = None + + def cb(key, value): + nonlocal thread + if int(key["m_value"]) == tid: + thread = value + + AKHashMapPrettyPrinter._iter_hashmap(threads, cb) + return thread + + def complete(self, text, word): + return gdb.COMPLETE_SYMBOL + + def invoke(self, args, from_tty): + argv = gdb.string_to_argv(args) + if len(argv) == 0: + gdb.write("Argument required (TID).\n") + return + tid = int(argv[0]) + thread = self._find_thread(tid) + if not thread: + gdb.write(f"No thread with TID {tid} found.\n") + else: + gdb.write(f"{thread}\n") + + +FindThreadCmd() |