This document discusses using the jq command line tool and Python to filter and format JSON data within Xcode's debugger LLDB. It describes running jq filters on JSON strings extracted from variables, saving the filter and JSON to temporary files for jq to process instead of using stdin. A Python function is defined to run jq from LLDB and return the output.
13. # In ~/.lldbinit, available to all LLDB sessions
type summary add --summary-string
"(${var._indexes[0]}, ${var._indexes[1]})" IndexPath
type summary add --summary-string
"${var.title} by ${var.speaker.name}" MyConf.Session
Quick & Dirty Type Summaries
16. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
sessionFormatter.py
17. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
sessionFormatter.py
18. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format ()
sessionFormatter.py
19. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
sessionFormatter.py
20. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
title = valobj.GetChildMemberWithName('title')
name = valobj.GetChildMemberWithName(āspeakerā)
.GetChildMemberWithName('name')
return stripQuotes(title.GetObjectDescription())
+ " by " +
stripQuotes(name.GetObjectDescription())
sessionFormatter.py
21. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
title = valobj.GetChildMemberWithName('title')
name = valobj.GetChildMemberWithName(āspeakerā)
.GetChildMemberWithName('name')
return stripQuotes(title.GetObjectDescription())
+ " by " +
stripQuotes(name.GetObjectDescription())
sessionFormatter.py
22. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
title = valobj.GetChildMemberWithName('title')
name = valobj.GetChildMemberWithName(āspeakerā)
.GetChildMemberWithName('name')
return stripQuotes(title.GetObjectDescription())
+ " by " +
stripQuotes(name.GetObjectDescription())
def __lldb_init_module(debugger, dict):
command = 'type summary add ā-python-function
sessionFormatter.format MyConf.Session'
debugger.HandleCommand(command)
sessionFormatter.py
23. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
title = valobj.GetChildMemberWithName('title')
name = valobj.GetChildMemberWithName(āspeakerā)
.GetChildMemberWithName('name')
return stripQuotes(title.GetObjectDescription())
+ " by " +
stripQuotes(name.GetObjectDescription())
def __lldb_init_module(debugger, dict):
command = 'type summary add ā-python-function
sessionFormatter.format MyConf.Session'
debugger.HandleCommand(command)
sessionFormatter.py
24. #!/usr/bin/python
import re
regex = re.compile('[^"]*"(.*)"n$')
def stripQuotes(str):
"""Utility method to strip the first and last quote
and anything outside them"""
match = regex.match(str)
if match :
return match.group(1)
return str
def format (valobj,internal_dict):
title = valobj.GetChildMemberWithName('title')
name = valobj.GetChildMemberWithName(āspeakerā)
.GetChildMemberWithName('name')
return stripQuotes(title.GetObjectDescription())
+ " by " +
stripQuotes(name.GetObjectDescription())
def __lldb_init_module(debugger, dict):
command = 'type summary add ā-python-function
sessionFormatter.format MyConf.Session'
debugger.HandleCommand(command)
sessionFormatter.py
25. command script import ā¦/sessionFormatter.py
# Add things here, like type summaries, breakpoints
# Even execute Swift or Objective-C code with 'expr'
.lldbinit-MyConf
31. $ cat avatar.json
[{"name":"Aang","sex":"M","born":-12,"died":153,"bending":["Air","Water","Earth","Fire","Energy"],"identity":
{"nationality":"Southern Air Temple","ethnicity":"Air Nomad"},"spouse":"Katara","children":[{"sex":"M","name":"Bumi"},
{"sex":"F","name":"Kya"},{"sex":"M","name":"Tenzin"}]},{"name":"Katara","sex":"F","born":85,"died":null,"bending":
["Water","Blood"],"identity":{"nationality":"Southern Water Tribe","ethnicity":"Water Tribe"},"spouse":"Aang","children":
[{"sex":"M","name":"Bumi"},{"sex":"F","name":"Kya"},{"sex":"M","name":"Tenzin"}]},{"name":"Sokka","sex":"M","born":84,"died":
164,"bending":[],"identity":{"nationality":"Southern Water Tribe","ethnicity":"Water Tribe"},"spouse":null,"children":[]},
{"name":"Toph Beifong","sex":"F","born":88,"died":null,"bending":["Earth","Metal"],"identity":{"nationality":"Gaoling, Earth
Kingdom","ethnicity":"Earth Kingdom"},"spouse":null,"children":[{"sex":"F","name":"Lin Beifong"},{"sex":"F","name":"Suyin
Beifong"}]},{"name":"Iroh","sex":"M","born":null,"died":null,"bending":["Fire","Energy"],"identity":{"nationality":"Fire Nation
Capital, Fire Nation","ethnicity":"Fire Nation"},"spouse":null,"children":[{"sex":"M","name":"Lu Ten"}]},
{"name":"Zuko","sex":"M","born":83,"died":null,"bending":["Fire","Energy"],"identity":{"nationality":"Fire Nation Capital, Fire
Nation","ethnicity":"Fire Nation"},"spouse":null,"children":[{"sex":"F","name":"Izumi"}]},
{"name":"Kya","sex":"F","born":null,"died":null,"bending":["Water"],"identity":{"nationality":"Southern Water
Tribe","ethnicity":"Water Tribe, Air Nomad"},"spouse":null,"children":[]},
{"name":"Bumi","sex":"M","born":null,"died":null,"bending":["Air"],"identity":{"nationality":"United Republic","ethnicity":"Water
Tribe, Air Nomad"},"spouse":null,"children":[]},{"name":"Tenzin","sex":"M","born":null,"died":null,"bending":["Air"],"identity":
{"nationality":"Republic City, United Republic","ethnicity":"Water Tribe, Air Nomad"},"spouse":null,"children":
[{"sex":"F","name":"Jinora"},{"sex":"F","name":"Ikki"},{"sex":"M","name":"Meelo"},{"sex":"M","name":"Rohan"}]},{"name":"Lin
Beifong","sex":"F","born":120,"died":null,"bending":["Earth","Metal"],"identity":{"nationality":"Republic City, United
Republic","ethnicity":"Earth Kingdom"},"spouse":null,"children":[]},{"name":"Suyin Beifong","sex":"F","born":
126,"died":null,"bending":["Earth","Metal"],"identity":{"nationality":"Republic City, United Republic","ethnicity":"Earth
Kingdom"},"spouse":null,"children":[{"sex":"M","name":"Bataar Jr."},{"sex":"F","name":"Opal"},{"sex":"M","name":"Wei"},
{"sex":"M","name":"Wing"},{"sex":"M","name":"Huan"}]}]
$
34. # Pretty print the jq
$ jq '.' avatar.json
...
{
"sex": "M",
"name": "Huan"
}
]
}
]
$
# The ']' on the last line tells me this is an array
35. # Pretty print the jq
$ jq '.' avatar.json
...
{
"sex": "M",
"name": "Huan"
}
]
}
]
$
# The ']' on the last line tells me this is an array
# The '}' on the 2nd-last line tells me this is an array of objects
36. # List the keys of each object
$ jq ' .[] | keys ' avatar.json
foreach item in array print the keys of each object
pipe the output of one ļ¬lter into the input of the next
37. # List the keys of each object
$ jq ' .[] | keys ' avatar.json
[
"bending",
"born",
"children",
"died",
"identity",
"name",
"sex",
"spouse"
]
[
"bending",
"born",ā¦
$
38. # The name of each character
$ jq ' .[] | .name ' avatar.json
foreach item in array extract the ānameā ļ¬eld
39. # The name of each character
$ jq ā.[] | .nameā avatar.json
"Aang"
"Katara"
"Sokka"
"Toph Beifong"
"Iroh"
"Zuko"
"Kya"
"Bumi"
"Tenzin"
"Lin Beifong"
$
40. # The name of each female character
$ jq ' .[] | select(.sex == "F") | .name ' avatar.json
foreach item in array
extract the ānameā ļ¬eld
where sex is F
41. # The name of each female character
$ jq ' .[] | select(.sex == "F") | .name ' avatar.json
"Katara"
"Toph Beifong"
"Kya"
"Lin Beifong"
"Suyin Beifongā
$
43. Image by Aijaz Ansari. Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
44. (lldb) jq ā.speakers[]|keysā jsonString
jq ļ¬lter (program) String
1. Gather input
2. Run the jq filter on the string, saving the output
3. Print the output
53. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
54. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
55. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
56. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
57. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
58. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
59. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry
about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
60. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>"
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry
about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq
will be invoked on the file, not using stdin
jq_json_file = ā/tmp/jq_json"
61. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
62. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
f = open(jq_filter_file, 'w')
f.write(jq_filter)
f.close()
63. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
f = open(jq_filter_file, 'w')
f.write(jq_filter)
f.close()
# invoke jq and capture the output
output = commands.getoutput("%s -f %s %s" % (
jq_exe, jq_filter_file, jq_json_file) )
64. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
f = open(jq_filter_file, 'w')
f.write(jq_filter)
f.close()
# invoke jq and capture the output
output = commands.getoutput("%s -f %s %s" % (
jq_exe, jq_filter_file, jq_json_file) )
print >>result, output
65. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
f = open(jq_filter_file, 'w')
f.write(jq_filter)
f.close()
# invoke jq and capture the output
output = commands.getoutput("%s -f %s %s" % (
jq_exe, jq_filter_file, jq_json_file) )
print >>result, output
Gather Input
Run Command
Print Output
66. def jq_command(debugger, command, result, dict):
frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
# The command is called like "jq <filter> <stringVar>ā
command_args = shlex.split(command)
jq_filter = command_args[0]
val = frame.var(command_args[1]) # access the variable
val_string = eval(val.GetObjectDescription())
# path to the jq executable.
jq_exe = "/Users/aijaz/local/bin/jq"
# We save the filter to a file so that we donāt have to worry about escaping special characters.
jq_filter_file = "/tmp/jq_filter"
# the value of the NSString variable is saved in this file jq will be invoked on the file, not using stdin
jq_json_file = "/tmp/jq_json"
# write the json file and jq filter to temp files
f = open(jq_json_file, 'w')
f.write(val_string)
f.close()
f = open(jq_filter_file, 'w')
f.write(jq_filter)
f.close()
# invoke jq and capture the output
output = commands.getoutput("%s -f %s %s" % (
jq_exe, jq_filter_file, jq_json_file) )
print >>result, output
def __lldb_init_module(debugger, dict):
# Add any commands contained in this module to LLDB
command = 'command script add -f jq.jq_command jq'
debugger.HandleCommand(command)
75. LLDB: https://lldb.llvm.org/
A blog post from January, where I write about most of the same stuff:
Extending LLDB: http://aijaz.net/2017/01/11/lldb-python/index.html
JQ
jq: https://stedolan.github.io/jq/
A Facebook library that adds a lot of cool extensions to LLDB
Facebook Chisel: https://github.com/facebook/chisel
WWDC Sessions:
Debugging Tips and Tricks https://developer.apple.com/videos/play/wwdc2016/417/
Whatās new in LLDB: https://developer.apple.com/videos/play/wwdc2015/402/
Introduction to LLDB and the Swift REPL https://developer.apple.com/videos/play/wwdc2014/409/
Advanced Swift Debugging in LLDB https://developer.apple.com/videos/play/wwdc2014/410/
Debugging in Xcode 6 https://developer.apple.com/videos/play/wwdc2014/413/
Debugging with Xcode https://developer.apple.com/videos/play/wwdc2013/407/
* Advanced Debugging with LLDB https://developer.apple.com/videos/play/wwdc2013/413/
* Debugging in Xcode https://developer.apple.com/videos/play/wwdc2012/412/
* Debugging with Xcode https://developer.apple.com/videos/play/wwdc2012/415/
*: Contains material related to this talk