summaryrefslogtreecommitdiffstats
path: root/python/lldbutils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /python/lldbutils
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/lldbutils')
-rw-r--r--python/lldbutils/.ruff.toml4
-rw-r--r--python/lldbutils/README.txt221
-rw-r--r--python/lldbutils/lldbutils/__init__.py17
-rw-r--r--python/lldbutils/lldbutils/content.py28
-rw-r--r--python/lldbutils/lldbutils/general.py165
-rw-r--r--python/lldbutils/lldbutils/gfx.py65
-rw-r--r--python/lldbutils/lldbutils/layout.py141
-rw-r--r--python/lldbutils/lldbutils/utils.py86
8 files changed, 727 insertions, 0 deletions
diff --git a/python/lldbutils/.ruff.toml b/python/lldbutils/.ruff.toml
new file mode 100644
index 0000000000..7c7717fa13
--- /dev/null
+++ b/python/lldbutils/.ruff.toml
@@ -0,0 +1,4 @@
+extend = "../../pyproject.toml"
+
+[isort]
+known-first-party = ["lldbutils"]
diff --git a/python/lldbutils/README.txt b/python/lldbutils/README.txt
new file mode 100644
index 0000000000..fd7e90675c
--- /dev/null
+++ b/python/lldbutils/README.txt
@@ -0,0 +1,221 @@
+lldb debugging functionality for Gecko
+======================================
+
+This directory contains a module, lldbutils, which is imported by the
+in-tree .lldbinit file. The lldbutil modules define some lldb commands
+that are handy for debugging Gecko.
+
+If you want to add a new command or Python-implemented type summary, either add
+it to one of the existing broad area Python files (such as lldbutils/layout.py
+for layout-related commands) or create a new file if none of the existing files
+is appropriate. If you add a new file, make sure you add it to __all__ in
+lldbutils/__init__.py.
+
+
+Supported commands
+------------------
+
+Most commands below that can take a pointer to an object also support being
+called with a smart pointer like nsRefPtr or nsCOMPtr.
+
+
+* frametree EXPR, ft EXPR
+ frametreelimited EXPR, ftl EXPR
+
+ Shows information about a frame tree. EXPR is an expression that
+ is evaluated, and must be an nsIFrame*. frametree displays the
+ entire frame tree that contains the given frame. frametreelimited
+ displays a subtree of the frame tree rooted at the given frame.
+
+ (lldb) p this
+ (nsBlockFrame *) $4 = 0x000000011687fcb8
+ (lldb) ftl this
+ Block(div)(-1)@0x11687fcb8 {0,0,7380,690} [state=0002100000d04601] [content=0x11688c0c0] [sc=0x11687f990:-moz-scrolled-content]<
+ line 0x116899130: count=1 state=inline,clean,prevmarginclean,not impacted,not wrapped,before:nobr,after:nobr[0x100] {60,0,0,690} ink-overflow=60,510,0,0 scr-overflow=60,510,0,0 <
+ Text(0)""@0x1168990c0 {60,510,0,0} [state=0001000020404000] [content=0x11687ca10] [sc=0x11687fd88:-moz-non-element,parent=0x11687eb00] [run=0x115115e80][0,0,T]
+ >
+ >
+ (lldb) ft this
+ Viewport(-1)@0x116017430 [view=0x115efe190] {0,0,60,60} [state=000b063000002623] [sc=0x1160170f8:-moz-viewport]<
+ HTMLScroll(html)(-1)@0x1160180d0 {0,0,0,0} [state=000b020000000403] [content=0x115e4d640] [sc=0x116017768:-moz-viewport-scroll]<
+ ...
+ Canvas(html)(-1)@0x116017e08 {0,0,60,60} ink-overflow=0,0,8340,2196 scr-overflow=0,0,8220,2196 [state=000b002000000601] [content=0x115e4d640] [sc=0x11687e0f8:-moz-scrolled-canvas]<
+ Block(html)(-1)@0x11687e578 {0,0,60,2196} ink-overflow=0,0,8340,2196 scr-overflow=0,0,8220,2196 [state=000b100000d00601] [content=0x115e4d640] [sc=0x11687e4b8,parent=0x0]<
+ line 0x11687ec48: count=1 state=block,clean,prevmarginclean,not impacted,not wrapped,before:nobr,after:nobr[0x48] bm=480 {480,480,0,1236} ink-overflow=360,426,7980,1410 scr-overflow=480,480,7740,1236 <
+ Block(body)(1)@0x11687ebb0 {480,480,0,1236} ink-overflow=-120,-54,7980,1410 scr-overflow=0,0,7740,1236 [state=000b120000100601] [content=0x115ed8980] [sc=0x11687e990]<
+ line 0x116899170: count=1 state=inline,clean,prevmarginclean,not impacted,not wrapped,before:nobr,after:nobr[0x0] {0,0,7740,1236} ink-overflow=-120,-54,7980,1410 scr-overflow=0,0,7740,1236 <
+ nsTextControlFrame@0x11687f068 {0,66,7740,1170} ink-overflow=-120,-120,7980,1410 scr-overflow=0,0,7740,1170 [state=0002000000004621] [content=0x115ca2c50] [sc=0x11687ea40]<
+ HTMLScroll(div)(-1)@0x11687f6b0 {180,240,7380,690} [state=0002000000084409] [content=0x11688c0c0] [sc=0x11687eb00]<
+ Block(div)(-1)@0x11687fcb8 {0,0,7380,690} [state=0002100000d04601] [content=0x11688c0c0] [sc=0x11687f990:-moz-scrolled-content]<
+ line 0x116899130: count=1 state=inline,clean,prevmarginclean,not impacted,not wrapped,before:nobr,after:nobr[0x100] {60,0,0,690} ink-overflow=60,510,0,0 scr-overflow=60,510,0,0 <
+ Text(0)""@0x1168990c0 {60,510,0,0} [state=0001000020404000] [content=0x11687ca10] [sc=0x11687fd88:-moz-non-element,parent=0x11687eb00] [run=0x115115e80][0,0,T]
+ ...
+
+
+* js
+
+ Dumps the current JS stack.
+
+ (lldb) js
+ 0 anonymous(aForce = false) ["chrome://browser/content/browser.js":13414]
+ this = [object Object]
+ 1 updateAppearance() ["chrome://browser/content/browser.js":13326]
+ this = [object Object]
+ 2 handleEvent(aEvent = [object Event]) ["chrome://browser/content/tabbrowser.xml":3811]
+ this = [object XULElement]
+
+
+* prefcnt EXPR
+
+ Shows the refcount of a given object. EXPR is an expression that is
+ evaluated, and can be either a pointer to or an actual refcounted
+ object. The object can be a standard nsISupports-like refcounted
+ object, a cycle-collected object or a mozilla::RefCounted<T> object.
+
+ (lldb) p this
+ (nsHTMLDocument *) $1 = 0x0000000116e9d800
+ (lldb) prefcnt this
+ 20
+ (lldb) p mDocumentURI
+ (nsCOMPtr<nsIURI>) $3 = {
+ mRawPtr = 0x0000000117163e50
+ }
+ (lldb) prefcnt mDocumentURI
+ 11
+
+
+* pstate EXPR
+
+ Shows the frame state bits (using their symbolic names) of a given frame.
+ EXPR is an expression that is evaluated, and must be an nsIFrame*.
+
+ (lldb) p this
+ (nsTextFrame *) $1 = 0x000000011f470b10
+ (lldb) p/x mState
+ (nsFrameState) $2 = 0x0000004080604000
+ (lldb) pstate this
+ TEXT_HAS_NONCOLLAPSED_CHARACTERS | TEXT_END_OF_LINE | TEXT_START_OF_LINE | NS_FRAME_PAINTED_THEBES | NS_FRAME_INDEPENDENT_SELECTION
+
+
+* ptag EXPR
+
+ Shows the DOM tag name of a node. EXPR is an expression that is
+ evaluated, and can be either an nsINode pointer or a concrete DOM
+ object.
+
+ (lldb) p this
+ (nsHTMLDocument *) $0 = 0x0000000116e9d800
+ (lldb) ptag this
+ (PermanentAtomImpl *) $1 = 0x0000000110133ac0 u"#document"
+ (lldb) p this->GetRootElement()
+ (mozilla::dom::HTMLSharedElement *) $2 = 0x0000000118429780
+ (lldb) ptag $2
+ (PermanentAtomImpl *) $3 = 0x0000000110123b80 u"html"
+
+
+Supported type summaries and synthetic children
+-----------------------------------------------
+
+In lldb terminology, type summaries are rules for how to display a value
+when using the "expression" command (or its familiar-to-gdb-users "p" alias),
+and synthetic children are fake member variables or array elements also
+added by custom rules.
+
+For objects that do have synthetic children defined for them, like nsTArray,
+the "expr -R -- EXPR" command can be used to show its actual member variables.
+
+
+* nsAString, nsACString,
+ nsFixedString, nsFixedCString,
+ nsAutoString, nsAutoCString
+
+ Strings have a type summary that shows the actual string.
+
+ (lldb) frame info
+ frame #0: 0x000000010400cfea XUL`nsCSSParser::ParseProperty(this=0x00007fff5fbf5248, aPropID=eCSSProperty_margin_top, aPropValue=0x00007fff5fbf53f8, aSheetURI=0x0000000115ae8c00, aBaseURI=0x0000000115ae8c00, aSheetPrincipal=0x000000010ff9e040, aDeclaration=0x00000001826fd580, aChanged=0x00007fff5fbf5247, aIsImportant=false, aIsSVGMode=false) + 74 at nsCSSParser.cpp:12851
+ (lldb) p aPropValue
+ (const nsAString) $16 = u"-25px"
+
+ (lldb) p this
+ (nsHTMLDocument *) $18 = 0x0000000115b56000
+ (lldb) p mContentType
+ (nsCString) $19 = {
+ nsACString = "text/html"
+ }
+
+* nscolor
+
+ nscolors (32-bit RGBA colors) have a type summary that shows the color as
+ one of the CSS 2.1 color keywords, a six digit hex color, an rgba() color,
+ or the "transparent" keyword.
+
+ (lldb) p this
+ (nsTextFrame *) $0 = 0x00000001168245e0
+ (lldb) p *this->StyleColor()
+ (const nsStyleColor) $1 = {
+ mColor = lime
+ }
+ (lldb) expr -R -- *this->StyleColor()
+ (const nsStyleColor) $2 = {
+ mColor = 4278255360
+ }
+
+* nsIAtom
+
+ Atoms have a type summary that shows the string value inside the atom.
+
+ (lldb) frame info
+ frame #0: 0x00000001028b8c49 XUL`mozilla::dom::Element::GetBoolAttr(this=0x0000000115ca1c50, aAttr=0x000000011012a640) const + 25 at Element.h:907
+ (lldb) p aAttr
+ (PermanentAtomImpl *) $1 = 0x000000011012a640 u"readonly"
+
+* nsTArray and friends
+
+ nsTArrays and their auto and fallible varieties have synthetic children
+ for their elements. This means when displaying them with "expr" (or "p"),
+ they will be shown like regular arrays, rather than showing the mHdr and
+ other fields.
+
+ (lldb) frame info
+ frame #0: 0x00000001043eb8a8 XUL`SVGTextFrame::DoGlyphPositioning(this=0x000000012f3f8778) + 248 at SVGTextFrame.cpp:4940
+ (lldb) p charPositions
+ (nsTArray<nsPoint>) $5 = {
+ [0] = {
+ mozilla::gfx::BasePoint<int, nsPoint> = {
+ x = 0
+ y = 816
+ }
+ }
+ [1] = {
+ mozilla::gfx::BasePoint<int, nsPoint> = {
+ x = 426
+ y = 816
+ }
+ }
+ [2] = {
+ mozilla::gfx::BasePoint<int, nsPoint> = {
+ x = 906
+ y = 816
+ }
+ }
+ }
+ (lldb) expr -R -- charPositions
+ (nsTArray<nsPoint>) $4 = {
+ nsTArray_Impl<nsPoint, nsTArrayInfallibleAllocator> = {
+ nsTArray_base<nsTArrayInfallibleAllocator, nsTArray_CopyWithMemutils> = {
+ mHdr = 0x000000012f3f1b80
+ }
+ }
+ }
+
+* nsTextNode, nsTextFragment
+
+ Text nodes have a type summary that shows the nsTextFragment in the
+ nsTextNode, which itself has a type summary that shows the text
+ content.
+
+ (lldb) p this
+ (nsTextFrame *) $14 = 0x000000011811bb10
+ (lldb) p mContent
+ (nsTextNode *) $15 = 0x0000000118130110 "Search or enter address"
+
diff --git a/python/lldbutils/lldbutils/__init__.py b/python/lldbutils/lldbutils/__init__.py
new file mode 100644
index 0000000000..e1263153df
--- /dev/null
+++ b/python/lldbutils/lldbutils/__init__.py
@@ -0,0 +1,17 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import lldb
+
+__all__ = ["content", "general", "gfx", "layout", "utils"]
+
+
+def init():
+ for name in __all__:
+ init = None
+ try:
+ init = __import__("lldbutils." + name, globals(), locals(), ["init"]).init
+ except AttributeError:
+ pass
+ if init:
+ init(lldb.debugger)
diff --git a/python/lldbutils/lldbutils/content.py b/python/lldbutils/lldbutils/content.py
new file mode 100644
index 0000000000..8f2bf22ccd
--- /dev/null
+++ b/python/lldbutils/lldbutils/content.py
@@ -0,0 +1,28 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from lldbutils import utils
+
+
+def summarize_text_fragment(valobj, internal_dict):
+ content_union = valobj.GetChildAtIndex(0)
+ state_union = valobj.GetChildAtIndex(1).GetChildMemberWithName("mState")
+ length = state_union.GetChildMemberWithName("mLength").GetValueAsUnsigned(0)
+ if state_union.GetChildMemberWithName("mIs2b").GetValueAsUnsigned(0):
+ field = "m2b"
+ else:
+ field = "m1b"
+ ptr = content_union.GetChildMemberWithName(field)
+ return utils.format_string(ptr, length)
+
+
+def ptag(debugger, command, result, dict):
+ """Displays the tag name of a content node."""
+ debugger.HandleCommand("expr (" + command + ")->mNodeInfo.mRawPtr->mInner.mName")
+
+
+def init(debugger):
+ debugger.HandleCommand(
+ "type summary add nsTextFragment -F lldbutils.content.summarize_text_fragment"
+ )
+ debugger.HandleCommand("command script add -f lldbutils.content.ptag ptag")
diff --git a/python/lldbutils/lldbutils/general.py b/python/lldbutils/lldbutils/general.py
new file mode 100644
index 0000000000..577b3847fa
--- /dev/null
+++ b/python/lldbutils/lldbutils/general.py
@@ -0,0 +1,165 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import lldb
+
+from lldbutils import utils
+
+
+def summarize_string(valobj, internal_dict):
+ data = valobj.GetChildMemberWithName("mData")
+ length = valobj.GetChildMemberWithName("mLength").GetValueAsUnsigned(0)
+ return utils.format_string(data, length)
+
+
+def summarize_atom(valobj, internal_dict):
+ target = lldb.debugger.GetSelectedTarget()
+ length = valobj.GetChildMemberWithName("mLength").GetValueAsUnsigned()
+ string = target.EvaluateExpression(
+ "(char16_t*)%s.GetUTF16String()" % valobj.GetName()
+ )
+ return utils.format_string(string, length)
+
+
+class TArraySyntheticChildrenProvider:
+ def __init__(self, valobj, internal_dict):
+ self.valobj = valobj
+ self.header = self.valobj.GetChildMemberWithName("mHdr")
+ self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)
+ self.element_size = self.element_type.GetByteSize()
+ header_size = self.header.GetType().GetPointeeType().GetByteSize()
+ self.element_base_addr = self.header.GetValueAsUnsigned(0) + header_size
+
+ def num_children(self):
+ return (
+ self.header.Dereference()
+ .GetChildMemberWithName("mLength")
+ .GetValueAsUnsigned(0)
+ )
+
+ def get_child_index(self, name):
+ try:
+ index = int(name)
+ if index >= self.num_children():
+ return None
+ # Ideally we'd use the exception type, but it's unclear what that is
+ # without knowing how to trigger the original exception.
+ except: # NOQA: E501, E722
+ pass
+ return None
+
+ def get_child_at_index(self, index):
+ if index >= self.num_children():
+ return None
+ addr = self.element_base_addr + index * self.element_size
+ return self.valobj.CreateValueFromAddress(
+ "[%d]" % index, addr, self.element_type
+ )
+
+
+def prefcnt(debugger, command, result, dict):
+ """Displays the refcount of an object."""
+ # We handled regular nsISupports-like refcounted objects and cycle collected
+ # objects.
+ target = debugger.GetSelectedTarget()
+ process = target.GetProcess()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ obj = frame.EvaluateExpression(command)
+ if obj.GetError().Fail():
+ print("could not evaluate expression")
+ return
+ obj = utils.dereference(obj)
+ field = obj.GetChildMemberWithName("mRefCnt")
+ if field.GetError().Fail():
+ field = obj.GetChildMemberWithName("refCnt")
+ if field.GetError().Fail():
+ print("not a refcounted object")
+ return
+ refcnt_type = field.GetType().GetCanonicalType().GetName()
+ if refcnt_type == "nsAutoRefCnt":
+ print(field.GetChildMemberWithName("mValue").GetValueAsUnsigned(0))
+ elif refcnt_type == "nsCycleCollectingAutoRefCnt":
+ print(
+ field.GetChildMemberWithName("mRefCntAndFlags").GetValueAsUnsigned(0) >> 2
+ )
+ elif refcnt_type == "mozilla::ThreadSafeAutoRefCnt":
+ print(
+ field.GetChildMemberWithName("mValue")
+ .GetChildMemberWithName("mValue")
+ .GetValueAsUnsigned(0)
+ )
+ elif refcnt_type == "int": # non-atomic mozilla::RefCounted object
+ print(field.GetValueAsUnsigned(0))
+ elif refcnt_type == "mozilla::Atomic<int>": # atomic mozilla::RefCounted object
+ print(field.GetChildMemberWithName("mValue").GetValueAsUnsigned(0))
+ else:
+ print("unknown mRefCnt type " + refcnt_type)
+
+
+# Used to work around http://llvm.org/bugs/show_bug.cgi?id=22211
+def callfunc(debugger, command, result, dict):
+ """Calls a function for which debugger information is unavailable by getting its address
+ from the symbol table. The function is assumed to return void."""
+
+ if "(" not in command:
+ print("Usage: callfunc your_function(args)")
+ return
+
+ command_parts = command.split("(")
+ funcname = command_parts[0].strip()
+ args = command_parts[1]
+
+ target = debugger.GetSelectedTarget()
+ symbols = target.FindFunctions(funcname).symbols
+ if not symbols:
+ print('Could not find a function symbol for a function called "%s"' % funcname)
+ return
+
+ sym = symbols[0]
+ arg_types = "()"
+ if sym.name and sym.name.startswith(funcname + "("):
+ arg_types = sym.name[len(funcname) :]
+ debugger.HandleCommand(
+ "print ((void(*)%s)0x%0x)(%s"
+ % (arg_types, sym.addr.GetLoadAddress(target), args)
+ )
+
+
+def init(debugger):
+ debugger.HandleCommand(
+ "type summary add nsAString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsACString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsFixedString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsFixedCString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsAutoString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsAutoCString -F lldbutils.general.summarize_string"
+ )
+ debugger.HandleCommand(
+ "type summary add nsAtom -F lldbutils.general.summarize_atom"
+ )
+ debugger.HandleCommand(
+ 'type synthetic add -x "nsTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider'
+ )
+ debugger.HandleCommand(
+ 'type synthetic add -x "AutoTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider' # NOQA: E501
+ )
+ debugger.HandleCommand(
+ 'type synthetic add -x "FallibleTArray<" -l lldbutils.general.TArraySyntheticChildrenProvider' # NOQA: E501
+ )
+ debugger.HandleCommand(
+ "command script add -f lldbutils.general.prefcnt -f lldbutils.general.prefcnt prefcnt"
+ )
+ debugger.HandleCommand(
+ "command script add -f lldbutils.general.callfunc -f lldbutils.general.callfunc callfunc"
+ )
diff --git a/python/lldbutils/lldbutils/gfx.py b/python/lldbutils/lldbutils/gfx.py
new file mode 100644
index 0000000000..7622dd4f9c
--- /dev/null
+++ b/python/lldbutils/lldbutils/gfx.py
@@ -0,0 +1,65 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+def summarize_nscolor(valobj, internal_dict):
+ colors = {
+ "#800000": "maroon",
+ "#ff0000": "red",
+ "#ffa500": "orange",
+ "#ffff00": "yellow",
+ "#808000": "olive",
+ "#800080": "purple",
+ "#ff00ff": "fuchsia",
+ "#ffffff": "white",
+ "#00ff00": "lime",
+ "#008000": "green",
+ "#000080": "navy",
+ "#0000ff": "blue",
+ "#00ffff": "aqua",
+ "#008080": "teal",
+ "#000000": "black",
+ "#c0c0c0": "silver",
+ "#808080": "gray",
+ }
+ value = valobj.GetValueAsUnsigned(0)
+ if value == 0:
+ return "transparent"
+ if value & 0xFF000000 != 0xFF000000:
+ return "rgba(%d, %d, %d, %f)" % (
+ value & 0xFF,
+ (value >> 8) & 0xFF,
+ (value >> 16) & 0xFF,
+ ((value >> 24) & 0xFF) / 255.0,
+ )
+ color = "#%02x%02x%02x" % (value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF)
+ if color in colors:
+ return colors[color]
+ return color
+
+
+def summarize_rect(valobj, internal_dict):
+ x = valobj.GetChildMemberWithName("x").GetValue()
+ y = valobj.GetChildMemberWithName("y").GetValue()
+ width = valobj.GetChildMemberWithName("width").GetValue()
+ height = valobj.GetChildMemberWithName("height").GetValue()
+ return "%s, %s, %s, %s" % (x, y, width, height)
+
+
+def rect_is_empty(valobj):
+ width = valobj.GetChildMemberWithName("width").GetValueAsSigned()
+ height = valobj.GetChildMemberWithName("height").GetValueAsSigned()
+ return width <= 0 or height <= 0
+
+
+def init(debugger):
+ debugger.HandleCommand(
+ "type summary add nscolor -v -F lldbutils.gfx.summarize_nscolor"
+ )
+ debugger.HandleCommand("type summary add nsRect -v -F lldbutils.gfx.summarize_rect")
+ debugger.HandleCommand(
+ "type summary add nsIntRect -v -F lldbutils.gfx.summarize_rect"
+ )
+ debugger.HandleCommand(
+ "type summary add gfxRect -v -F lldbutils.gfx.summarize_rect"
+ )
diff --git a/python/lldbutils/lldbutils/layout.py b/python/lldbutils/lldbutils/layout.py
new file mode 100644
index 0000000000..64cbc2c22f
--- /dev/null
+++ b/python/lldbutils/lldbutils/layout.py
@@ -0,0 +1,141 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+def frametree(debugger, command, result, dict):
+ """Dumps the frame tree containing the given nsIFrame*."""
+ debugger.HandleCommand("expr (" + command + ")->DumpFrameTree()")
+
+
+def frametree_pixels(debugger, command, result, dict):
+ """Dumps the frame tree containing the given nsIFrame* in CSS pixels."""
+ debugger.HandleCommand("expr (" + command + ")->DumpFrameTreeInCSSPixels()")
+
+
+def frametreelimited(debugger, command, result, dict):
+ """Dumps the subtree of a frame tree rooted at the given nsIFrame*."""
+ debugger.HandleCommand("expr (" + command + ")->DumpFrameTreeLimited()")
+
+
+def frametreelimited_pixels(debugger, command, result, dict):
+ """Dumps the subtree of a frame tree rooted at the given nsIFrame*
+ in CSS pixels."""
+ debugger.HandleCommand("expr (" + command + ")->DumpFrameTreeLimitedInCSSPixels()")
+
+
+def pstate(debugger, command, result, dict):
+ """Displays a frame's state bits symbolically."""
+ debugger.HandleCommand("expr mozilla::PrintFrameState(" + command + ")")
+
+
+def summarize_nscoord(valobj, internal_dict):
+ MAX = 0x3FFFFFFF
+ value = valobj.GetValueAsSigned(0)
+ if value == MAX:
+ return "NS_UNCONSTRAINEDSIZE"
+ if value == -MAX:
+ return "NS_INTRINSIC_ISIZE_UNKNOWN"
+ if value == MAX - (1000000 * 60):
+ return "INFINITE_ISIZE_COORD"
+
+ return "%.2gpx" % (value / 60.0)
+
+
+def _find_child_field(valobj, name, depth=0):
+ child = valobj.GetChildMemberWithName(name)
+ if child.IsValid():
+ return child
+ if depth > 4:
+ return None
+ return _find_child_field(valobj.GetChildAtIndex(0), name, depth + 1)
+
+
+def summarize_nspoint(valobj, internal_dict):
+ return "(%s, %s)" % (
+ summarize_nscoord(_find_child_field(valobj, "x"), {}),
+ summarize_nscoord(_find_child_field(valobj, "y"), {}),
+ )
+
+
+def summarize_nssize(valobj, internal_dict):
+ return "(%s, %s)" % (
+ summarize_nscoord(_find_child_field(valobj, "width"), {}),
+ summarize_nscoord(_find_child_field(valobj, "height"), {}),
+ )
+
+
+def summarize_nsrect(valobj, internal_dict):
+ return "%s x %s" % (
+ summarize_nspoint(valobj, {}),
+ summarize_nssize(valobj, {}),
+ )
+
+
+def summarize_writing_mode(valobj, internal_dict):
+ wm = valobj.GetChildMemberWithName("mWritingMode")
+
+ # Values from: servo/components/style/logical_geometry.rs:29
+ VERTICAL = 1 << 0
+ # INLINE_REVERSED = 1 << 1
+ VERTICAL_LR = 1 << 2
+ LINE_INVERTED = 1 << 3
+ RTL = 1 << 4
+ VERTICAL_SIDEWAYS = 1 << 5
+ TEXT_SIDEWAYS = 1 << 6
+ # UPRIGHT = 1 << 7
+
+ bits = wm.GetChildMemberWithName("_0").GetValueAsUnsigned()
+
+ result = ""
+ if bits & VERTICAL:
+ result += "vertical-"
+ if bits & VERTICAL_LR:
+ result += "lr"
+ else:
+ result += "rl"
+ if bits & (VERTICAL_SIDEWAYS | TEXT_SIDEWAYS):
+ result += "-sideways"
+ if bits & LINE_INVERTED:
+ result += "inverted"
+ else:
+ result += "horizontal"
+ if not (bits & RTL):
+ result += "-ltr"
+ else:
+ result += "-rtl"
+
+ return result
+
+
+def init(debugger):
+ debugger.HandleCommand("command script add -f lldbutils.layout.frametree frametree")
+ debugger.HandleCommand(
+ "command script add -f lldbutils.layout.frametree_pixels frametree_pixels"
+ )
+ debugger.HandleCommand(
+ "command script add -f lldbutils.layout.frametreelimited frametreelimited"
+ )
+ debugger.HandleCommand(
+ "command script add -f lldbutils.layout.frametreelimited_pixels frametreelimited_pixels"
+ )
+ debugger.HandleCommand("command alias ft frametree")
+ debugger.HandleCommand("command alias ftp frametree_pixels")
+ debugger.HandleCommand("command alias ftl frametreelimited")
+ debugger.HandleCommand("command alias ftlp frametreelimited_pixels")
+ debugger.HandleCommand("command script add -f lldbutils.layout.pstate pstate")
+
+ debugger.HandleCommand(
+ "type summary add nscoord -v -F lldbutils.layout.summarize_nscoord"
+ )
+ debugger.HandleCommand(
+ "type summary add nsPoint -v -F lldbutils.layout.summarize_nspoint"
+ )
+ debugger.HandleCommand(
+ "type summary add nsSize -v -F lldbutils.layout.summarize_nssize"
+ )
+ debugger.HandleCommand(
+ "type summary add nsRect -v -F lldbutils.layout.summarize_nsrect"
+ )
+ debugger.HandleCommand(
+ "type summary add mozilla::WritingMode -v -F lldbutils.layout.summarize_writing_mode"
+ )
diff --git a/python/lldbutils/lldbutils/utils.py b/python/lldbutils/lldbutils/utils.py
new file mode 100644
index 0000000000..fdcf7da366
--- /dev/null
+++ b/python/lldbutils/lldbutils/utils.py
@@ -0,0 +1,86 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+def format_char(c):
+ if c == 0:
+ return "\\0"
+ elif c == 0x07:
+ return "\\a"
+ elif c == 0x08:
+ return "\\b"
+ elif c == 0x0C:
+ return "\\f"
+ elif c == 0x0A:
+ return "\\n"
+ elif c == 0x0D:
+ return "\\r"
+ elif c == 0x09:
+ return "\\t"
+ elif c == 0x0B:
+ return "\\v"
+ elif c == 0x5C:
+ return "\\"
+ elif c == 0x22:
+ return '\\"'
+ elif c == 0x27:
+ return "\\'"
+ elif c < 0x20 or c >= 0x80 and c <= 0xFF:
+ return "\\x%02x" % c
+ elif c >= 0x0100:
+ return "\\u%04x" % c
+ else:
+ return chr(c)
+
+
+# Take an SBValue that is either a char* or char16_t* and formats it like lldb
+# would when printing it.
+def format_string(lldb_value, length=100):
+ ptr = lldb_value.GetValueAsUnsigned(0)
+ char_type = lldb_value.GetType().GetPointeeType()
+ if char_type.GetByteSize() == 1:
+ s = '"'
+ size = 1
+ mask = 0xFF
+ elif char_type.GetByteSize() == 2:
+ s = 'u"'
+ size = 2
+ mask = 0xFFFF
+ else:
+ return "(cannot format string with char type %s)" % char_type.GetName()
+ i = 0
+ terminated = False
+ while i < length:
+ c = (
+ lldb_value.CreateValueFromAddress(
+ "x", ptr + i * size, char_type
+ ).GetValueAsUnsigned(0)
+ & mask
+ )
+ if c == 0:
+ terminated = True
+ break
+ s += format_char(c)
+ i = i + 1
+ s += '"'
+ if not terminated and i != length:
+ s += "..."
+ return s
+
+
+# Dereferences a raw pointer, nsCOMPtr, RefPtr, nsAutoPtr, already_AddRefed or
+# mozilla::RefPtr; otherwise returns the value unchanged.
+def dereference(lldb_value):
+ if lldb_value.TypeIsPointerType():
+ return lldb_value.Dereference()
+ name = lldb_value.GetType().GetUnqualifiedType().GetName()
+ if (
+ name.startswith("nsCOMPtr<")
+ or name.startswith("RefPtr<")
+ or name.startswith("nsAutoPtr<")
+ or name.startswith("already_AddRefed<")
+ ):
+ return lldb_value.GetChildMemberWithName("mRawPtr")
+ if name.startswith("mozilla::RefPtr<"):
+ return lldb_value.GetChildMemberWithName("ptr")
+ return lldb_value