Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

360|iDev

393 views

Published on

The slides from my talk at 360|iDev 2017

Published in: Software
  • Be the first to comment

  • Be the first to like this

360|iDev

  1. 1. Advanced Debugging With XCode Extending LLDB Image by Aijaz Ansari. Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
  2. 2. Aijaz @_aijaz_
  3. 3. (Since 1982) 😱
  4. 4. Debug
  5. 5. Sample Project
  6. 6. Session id: String title: String? sessionDescription: String? startTime: Date endTime: Date speaker: Speaker? startDate: String init?(withId id: String) Speaker id: String name: String imagePath: String? bio: String? twitterHandle: String? website: String? sessions: [Session] init?(withId id: String) speaker sessions
  7. 7. Demo 1
  8. 8. Image by Aijaz Ansari. Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
  9. 9. Let’s Recap
  10. 10. # 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
  11. 11. Summaries in Python
  12. 12. #!/usr/bin/python import re regex = re.compile('[^"]*"(.*)"n$') sessionFormatter.py
  13. 13. #!/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
  14. 14. #!/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
  15. 15. #!/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
  16. 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 def format (valobj,internal_dict): sessionFormatter.py
  17. 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 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
  18. 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 (valobj,internal_dict): title = valobj.GetChildMemberWithName('title') name = valobj.GetChildMemberWithName(‘speaker’) .GetChildMemberWithName('name') return stripQuotes(title.GetObjectDescription()) + " by " + stripQuotes(name.GetObjectDescription()) sessionFormatter.py
  19. 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): 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
  20. 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()) def __lldb_init_module(debugger, dict): command = 'type summary add —-python-function sessionFormatter.format MyConf.Session' debugger.HandleCommand(command) sessionFormatter.py
  21. 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()) def __lldb_init_module(debugger, dict): command = 'type summary add —-python-function sessionFormatter.format MyConf.Session' debugger.HandleCommand(command) sessionFormatter.py
  22. 22. command script import …/sessionFormatter.py # Add things here, like type summaries, breakpoints # Even execute Swift or Objective-C code with 'expr' .lldbinit-MyConf
  23. 23. Project-Specific Loading in main() or application(_:didFinishLaunchingWithOptions:)
  24. 24. Any Questions?
  25. 25. { "version": 1, "speakers": [ {"id": 3, "img": “chad.jpg", "name": “Chad Perk", "twitter": "chad", "blog": “www.example.com/chad”, "bio": "Chad is the co-author of…” },
  26. 26. jq
  27. 27. http://feelgrafix.com/data_images/out/28/973590-avatar-the-last-airbender.jpg
  28. 28. $ 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"}]}] $
  29. 29. # Pretty print the jq $ jq '.' avatar.json command input filefilter
  30. 30. # Pretty print the jq $ jq '.' avatar.json ... { "sex": "M", "name": "Huan" } ] } ] $
  31. 31. # Pretty print the jq $ jq '.' avatar.json ... { "sex": "M", "name": "Huan" } ] } ] $ # The ']' on the last line tells me this is an array
  32. 32. # 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
  33. 33. # 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 filter into the input of the next
  34. 34. # List the keys of each object $ jq ' .[] | keys ' avatar.json [ "bending", "born", "children", "died", "identity", "name", "sex", "spouse" ] [ "bending", "born",… $
  35. 35. # The name of each character $ jq ' .[] | .name ' avatar.json foreach item in array extract the ‘name’ field
  36. 36. # The name of each character $ jq ‘.[] | .name’ avatar.json "Aang" "Katara" "Sokka" "Toph Beifong" "Iroh" "Zuko" "Kya" "Bumi" "Tenzin" "Lin Beifong" $
  37. 37. # The name of each female character $ jq ' .[] | select(.sex == "F") | .name ' avatar.json foreach item in array extract the ‘name’ field where sex is F
  38. 38. # The name of each female character $ jq ' .[] | select(.sex == "F") | .name ' avatar.json "Katara" "Toph Beifong" "Kya" "Lin Beifong" "Suyin Beifong” $
  39. 39. Demo 2
  40. 40. Image by Aijaz Ansari. Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
  41. 41. (lldb) jq ‘.speakers[]|keys’ jsonString jq filter (program) String 1. Gather input 2. Run the jq filter on the string, saving the output 3. Print the output
  42. 42. #!/usr/bin/python import commands import lldb import shlex
  43. 43. #!/usr/bin/python import commands import lldb import shlex # The actual python function that is bound to the lldb command. def jq_command(debugger, command, result, dict):
  44. 44. def jq_command(debugger, command, result, dict):
  45. 45. def jq_command(debugger, command, result, dict): target = debugger.GetSelectedTarget()
  46. 46. def jq_command(debugger, command, result, dict): target = debugger.GetSelectedTarget() process = target.GetProcess()
  47. 47. def jq_command(debugger, command, result, dict): target = debugger.GetSelectedTarget() process = target.GetProcess() thread = process.GetSelectedThread()
  48. 48. def jq_command(debugger, command, result, dict): target = debugger.GetSelectedTarget() process = target.GetProcess() thread = process.GetSelectedThread() frame = thread.GetSelectedFrame()
  49. 49. def jq_command(debugger, command, result, dict): frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
  50. 50. 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)
  51. 51. 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)
  52. 52. 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]
  53. 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) jq_filter = command_args[0] val = frame.var(command_args[1]) # access the variable
  54. 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) jq_filter = command_args[0] val = frame.var(command_args[1]) # access the variable val_string = eval(val.GetObjectDescription())
  55. 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] 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"
  56. 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 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"
  57. 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()) # 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"
  58. 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" # 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()
  59. 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" # 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()
  60. 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" # 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) )
  61. 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() 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
  62. 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() # 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
  63. 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) ) 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)
  64. 64. What have we learned?
  65. 65. Validate your hypothesis
  66. 66. (if you’re lucky enough to have a hypothesis.)
  67. 67. Observe
  68. 68. Analyze
  69. 69. Extend
  70. 70. Any Questions?
  71. 71. https://github.com/aijaz/lldbPythonScripts http://aijaz.net/2017/01/11/lldb-python/index.html https://github.com/facebook/chisel
  72. 72. 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
  73. 73. Thank You! https://www.theodysseyonline.com/overthinking-spongebob @_aijaz_

×