diff --git a/lldb/docs/conf.py b/lldb/docs/conf.py --- a/lldb/docs/conf.py +++ b/lldb/docs/conf.py @@ -35,6 +35,9 @@ # also defines the URL these files will have in the generated website. automodapi_toctreedirnm = 'python_api' +# Disable sphinx-tabs tab closing feature. +sphinx_tabs_disable_tab_closing = True + # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -42,7 +45,7 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.intersphinx'] +extensions = ['sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.intersphinx', 'sphinx_tabs.tabs'] # Unless we only generate the basic manpage we need the plugin for generating # the Python API documentation. diff --git a/lldb/docs/index.rst b/lldb/docs/index.rst --- a/lldb/docs/index.rst +++ b/lldb/docs/index.rst @@ -133,8 +133,8 @@ use/variable use/symbolication use/symbols - use/python - use/python-reference + use/scripting-example + use/scripting-reference use/remote use/qemu-testing use/troubleshooting diff --git a/lldb/docs/use/python.rst b/lldb/docs/use/python.rst deleted file mode 100644 --- a/lldb/docs/use/python.rst +++ /dev/null @@ -1,801 +0,0 @@ -Python Scripting -================ - -LLDB has been structured from the beginning to be scriptable in two -ways -- a Unix Python session can initiate/run a debug session -non-interactively using LLDB; and within the LLDB debugger tool, Python -scripts can be used to help with many tasks, including inspecting -program data, iterating over containers and determining if a breakpoint -should stop execution or continue. This document will show how to do -some of these things by going through an example, explaining how to use -Python scripting to find a bug in a program that searches for text in a -large binary tree. - -.. contents:: - :local: - -The Test Program and Input --------------------------- - -We have a simple C program (dictionary.c) that reads in a text file, -and stores all the words from the file in a Binary Search Tree, sorted -alphabetically. It then enters a loop prompting the user for a word, -searching for the word in the tree (using Binary Search), and reporting -to the user whether or not it found the word in the tree. - -The input text file we are using to test our program contains the text -for William Shakespeare's famous tragedy "Romeo and Juliet". - -The Bug -------- - -When we try running our program, we find there is a problem. While it -successfully finds some of the words we would expect to find, such as -"love" or "sun", it fails to find the word "Romeo", which MUST be in -the input text file: - -:: - - % ./dictionary Romeo-and-Juliet.txt - Dictionary loaded. - Enter search word: love - Yes! - Enter search word: sun - Yes! - Enter search word: Romeo - No! - Enter search word: ^D - % - -Using Depth First Search ------------------------- - -Our first job is to determine if the word "Romeo" actually got inserted -into the tree or not. Since "Romeo and Juliet" has thousands of words, -trying to examine our binary search tree by hand is completely -impractical. Therefore we will write a Python script to search the tree -for us. We will write a recursive Depth First Search function that -traverses the entire tree searching for a word, and maintaining -information about the path from the root of the tree to the current -node. If it finds the word in the tree, it returns the path from the -root to the node containing the word. This is what our DFS function in -Python would look like, with line numbers added for easy reference in -later explanations: - -:: - - 1: def DFS (root, word, cur_path): - 2: root_word_ptr = root.GetChildMemberWithName ("word") - 3: left_child_ptr = root.GetChildMemberWithName ("left") - 4: right_child_ptr = root.GetChildMemberWithName ("right") - 5: root_word = root_word_ptr.GetSummary() - 6: end = len (root_word) - 1 - 7: if root_word[0] == '"' and root_word[end] == '"': - 8: root_word = root_word[1:end] - 9: end = len (root_word) - 1 - 10: if root_word[0] == '\'' and root_word[end] == '\'': - 11: root_word = root_word[1:end] - 12: if root_word == word: - 13: return cur_path - 14: elif word < root_word: - 15: if left_child_ptr.GetValue() == None: - 16: return "" - 17: else: - 18: cur_path = cur_path + "L" - 19: return DFS (left_child_ptr, word, cur_path) - 20: else: - 21: if right_child_ptr.GetValue() == None: - 22: return "" - 23: else: - 24: cur_path = cur_path + "R" - 25: return DFS (right_child_ptr, word, cur_path) - - -Accessing & Manipulating Program Variables ------------------------------------------- - -Before we can call any Python function on any of our program's -variables, we need to get the variable into a form that Python can -access. To show you how to do this we will look at the parameters for -the DFS function. The first parameter is going to be a node in our -binary search tree, put into a Python variable. The second parameter is -the word we are searching for (a string), and the third parameter is a -string representing the path from the root of the tree to our current -node. - -The most interesting parameter is the first one, the Python variable -that needs to contain a node in our search tree. How can we take a -variable out of our program and put it into a Python variable? What -kind of Python variable will it be? The answers are to use the LLDB API -functions, provided as part of the LLDB Python module. Running Python -from inside LLDB, LLDB will automatically give us our current frame -object as a Python variable, "lldb.frame". This variable has the type -`SBFrame` (see the LLDB API for more information about `SBFrame` -objects). One of the things we can do with a frame object, is to ask it -to find and return its local variable. We will call the API function -`SBFrame.FindVariable` on the lldb.frame object to give us our dictionary -variable as a Python variable: - -:: - - root = lldb.frame.FindVariable ("dictionary") - -The line above, executed in the Python script interpreter in LLDB, asks the -current frame to find the variable named "dictionary" and return it. We then -store the returned value in the Python variable named "root". This answers the -question of HOW to get the variable, but it still doesn't explain WHAT actually -gets put into "root". If you examine the LLDB API, you will find that the -`SBFrame` method "FindVariable" returns an object of type `SBValue`. `SBValue` -objects are used, among other things, to wrap up program variables and values. -There are many useful methods defined in the `SBValue` class to allow you to get -information or children values out of SBValues. For complete information, see -the header file SBValue.h. The `SBValue` methods that we use in our DFS function -are ``GetChildMemberWithName()``, ``GetSummary()``, and ``GetValue()``. - - -Explaining DFS Script in Detail -------------------------------- - -Before diving into the details of this code, it would be best to give a -high-level overview of what it does. The nodes in our binary search tree were -defined to have type ``tree_node *``, which is defined as: - -:: - - typedef struct tree_node - { - const char *word; - struct tree_node *left; - struct tree_node *right; - } tree_node; - -Lines 2-11 of DFS are getting data out of the current tree node and getting -ready to do the actual search; lines 12-25 are the actual depth-first search. -Lines 2-4 of our DFS function get the word, left and right fields out of the -current node and store them in Python variables. Since root_word_ptr is a -pointer to our word, and we want the actual word, line 5 calls GetSummary() to -get a string containing the value out of the pointer. Since GetSummary() adds -quotes around its result, lines 6-11 strip surrounding quotes off the word. - -Line 12 checks to see if the word in the current node is the one we are -searching for. If so, we are done, and line 13 returns the current path. -Otherwise, line 14 checks to see if we should go left (search word comes before -the current word). If we decide to go left, line 15 checks to see if the left -pointer child is NULL ("None" is the Python equivalent of NULL). If the left -pointer is NULL, then the word is not in this tree and we return an empty path -(line 16). Otherwise, we add an "L" to the end of our current path string, to -indicate we are going left (line 18), and then recurse on the left child (line -19). Lines 20-25 are the same as lines 14-19, except for going right rather -than going left. - -One other note: Typing something as long as our DFS function directly into the -interpreter can be difficult, as making a single typing mistake means having to -start all over. Therefore we recommend doing as we have done: Writing your -longer, more complicated script functions in a separate file (in this case -tree_utils.py) and then importing it into your LLDB Python interpreter. - - -The DFS Script in Action ------------------------- - -At this point we are ready to use the DFS function to see if the word "Romeo" -is in our tree or not. To actually use it in LLDB on our dictionary program, -you would do something like this: - -:: - - % lldb - (lldb) process attach -n "dictionary" - Architecture set to: x86_64. - Process 521 stopped - * thread #1: tid = 0x2c03, 0x00007fff86c8bea0 libSystem.B.dylib`read$NOCANCEL + 8, stop reason = signal SIGSTOP - frame #0: 0x00007fff86c8bea0 libSystem.B.dylib`read$NOCANCEL + 8 - (lldb) breakpoint set -n find_word - Breakpoint created: 1: name = 'find_word', locations = 1, resolved = 1 - (lldb) continue - Process 521 resuming - Process 521 stopped - * thread #1: tid = 0x2c03, 0x0000000100001830 dictionary`find_word + 16 - at dictionary.c:105, stop reason = breakpoint 1.1 - frame #0: 0x0000000100001830 dictionary`find_word + 16 at dictionary.c:105 - 102 int - 103 find_word (tree_node *dictionary, char *word) - 104 { - -> 105 if (!word || !dictionary) - 106 return 0; - 107 - 108 int compare_value = strcmp (word, dictionary->word); - (lldb) script - Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. - >>> import tree_utils - >>> root = lldb.frame.FindVariable ("dictionary") - >>> current_path = "" - >>> path = tree_utils.DFS (root, "Romeo", current_path) - >>> print path - LLRRL - >>> ^D - (lldb) - -The first bit of code above shows starting lldb, attaching to the dictionary -program, and getting to the find_word function in LLDB. The interesting part -(as far as this example is concerned) begins when we enter the script command -and drop into the embedded interactive Python interpreter. We will go over this -Python code line by line. The first line - -:: - - import tree_utils - - -imports the file where we wrote our DFS function, tree_utils.py, into Python. -Notice that to import the file we leave off the ".py" extension. We can now -call any function in that file, giving it the prefix "tree_utils.", so that -Python knows where to look for the function. The line - -:: - - root = lldb.frame.FindVariable ("dictionary") - - -gets our program variable "dictionary" (which contains the binary search tree) -and puts it into the Python variable "root". See Accessing & Manipulating -Program Variables in Python above for more details about how this works. The -next line is - -:: - - current_path = "" - -This line initializes the current_path from the root of the tree to our current -node. Since we are starting at the root of the tree, our current path starts as -an empty string. As we go right and left through the tree, the DFS function -will append an 'R' or an 'L' to the current path, as appropriate. The line - -:: - - path = tree_utils.DFS (root, "Romeo", current_path) - -calls our DFS function (prefixing it with the module name so that Python can -find it). We pass in our binary tree stored in the variable root, the word we -are searching for, and our current path. We assign whatever path the DFS -function returns to the Python variable path. - -Finally, we want to see if the word was found or not, and if so we want to see -the path through the tree to the word. So we do - -:: - - print path - -From this we can see that the word "Romeo" was indeed found in the tree, and -the path from the root of the tree to the node containing "Romeo" is -left-left-right-right-left. - -Using Breakpoint Command Scripts --------------------------------- - -We are halfway to figuring out what the problem is. We know the word we are -looking for is in the binary tree, and we know exactly where it is in the -binary tree. Now we need to figure out why our binary search algorithm is not -finding the word. We will do this using breakpoint command scripts. - -The idea is as follows. The binary search algorithm has two main decision -points: the decision to follow the right branch; and, the decision to follow -the left branch. We will set a breakpoint at each of these decision points, and -attach a Python breakpoint command script to each breakpoint. The breakpoint -commands will use the global path Python variable that we got from our DFS -function. Each time one of these decision breakpoints is hit, the script will -compare the actual decision with the decision the front of the path variable -says should be made (the first character of the path). If the actual decision -and the path agree, then the front character is stripped off the path, and -execution is resumed. In this case the user never even sees the breakpoint -being hit. But if the decision differs from what the path says it should be, -then the script prints out a message and does NOT resume execution, leaving the -user sitting at the first point where a wrong decision is being made. - -Python Breakpoint Command Scripts Are Not What They Seem --------------------------------------------------------- - -What do we mean by that? When you enter a Python breakpoint command in LLDB, it -appears that you are entering one or more plain lines of Python. BUT LLDB then -takes what you entered and wraps it into a Python FUNCTION (just like using the -"def" Python command). It automatically gives the function an obscure, unique, -hard-to-stumble-across function name, and gives it two parameters: frame and -bp_loc. When the breakpoint gets hit, LLDB wraps up the frame object where the -breakpoint was hit, and the breakpoint location object for the breakpoint that -was hit, and puts them into Python variables for you. It then calls the Python -function that was created for the breakpoint command, and passes in the frame -and breakpoint location objects. - -So, being practical, what does this mean for you when you write your Python -breakpoint commands? It means that there are two things you need to keep in -mind: 1. If you want to access any Python variables created outside your -script, you must declare such variables to be global. If you do not declare -them as global, then the Python function will treat them as local variables, -and you will get unexpected behavior. 2. All Python breakpoint command scripts -automatically have a frame and a bp_loc variable. The variables are pre-loaded -by LLDB with the correct context for the breakpoint. You do not have to use -these variables, but they are there if you want them. - -The Decision Point Breakpoint Commands --------------------------------------- - -This is what the Python breakpoint command script would look like for the -decision to go right: - -:: - - global path - if path[0] == 'R': - path = path[1:] - thread = frame.GetThread() - process = thread.GetProcess() - process.Continue() - else: - print "Here is the problem; going right, should go left!" - Just as a reminder, LLDB is going to take this script and wrap it up in a function, like this: - - - def some_unique_and_obscure_function_name (frame, bp_loc): - global path - if path[0] == 'R': - path = path[1:] - thread = frame.GetThread() - process = thread.GetProcess() - process.Continue() - else: - print "Here is the problem; going right, should go left!" - -LLDB will call the function, passing in the correct frame and breakpoint -location whenever the breakpoint gets hit. There are several things to notice -about this function. The first one is that we are accessing and updating a -piece of state (the path variable), and actually conditioning our behavior -based upon this variable. Since the variable was defined outside of our script -(and therefore outside of the corresponding function) we need to tell Python -that we are accessing a global variable. That is what the first line of the -script does. Next we check where the path says we should go and compare it to -our decision (recall that we are at the breakpoint for the decision to go -right). If the path agrees with our decision, then we strip the first character -off of the path. - -Since the decision matched the path, we want to resume execution. To do this we -make use of the frame parameter that LLDB guarantees will be there for us. We -use LLDB API functions to get the current thread from the current frame, and -then to get the process from the thread. Once we have the process, we tell it -to resume execution (using the Continue() API function). - -If the decision to go right does not agree with the path, then we do not resume -execution. We allow the breakpoint to remain stopped (by doing nothing), and we -print an informational message telling the user we have found the problem, and -what the problem is. - -Actually Using The Breakpoint Commands --------------------------------------- - -Now we will look at what happens when we actually use these breakpoint commands -on our program. Doing a source list -n find_word shows us the function -containing our two decision points. Looking at the code below, we see that we -want to set our breakpoints on lines 113 and 115: - -:: - - (lldb) source list -n find_word - File: /Volumes/Data/HD2/carolinetice/Desktop/LLDB-Web-Examples/dictionary.c. - 101 - 102 int - 103 find_word (tree_node *dictionary, char *word) - 104 { - 105 if (!word || !dictionary) - 106 return 0; - 107 - 108 int compare_value = strcmp (word, dictionary->word); - 109 - 110 if (compare_value == 0) - 111 return 1; - 112 else if (compare_value < 0) - 113 return find_word (dictionary->left, word); - 114 else - 115 return find_word (dictionary->right, word); - 116 } - 117 - - -So, we set our breakpoints, enter our breakpoint command scripts, and see what happens: - -:: - - (lldb) breakpoint set -l 113 - Breakpoint created: 2: file ='dictionary.c', line = 113, locations = 1, resolved = 1 - (lldb) breakpoint set -l 115 - Breakpoint created: 3: file ='dictionary.c', line = 115, locations = 1, resolved = 1 - (lldb) breakpoint command add -s python 2 - Enter your Python command(s). Type 'DONE' to end. - > global path - > if (path[0] == 'L'): - > path = path[1:] - > thread = frame.GetThread() - > process = thread.GetProcess() - > process.Continue() - > else: - > print "Here is the problem. Going left, should go right!" - > DONE - (lldb) breakpoint command add -s python 3 - Enter your Python command(s). Type 'DONE' to end. - > global path - > if (path[0] == 'R'): - > path = path[1:] - > thread = frame.GetThread() - > process = thread.GetProcess() - > process.Continue() - > else: - > print "Here is the problem. Going right, should go left!" - > DONE - (lldb) continue - Process 696 resuming - Here is the problem. Going right, should go left! - Process 696 stopped - * thread #1: tid = 0x2d03, 0x000000010000189f dictionary`find_word + 127 at dictionary.c:115, stop reason = breakpoint 3.1 - frame #0: 0x000000010000189f dictionary`find_word + 127 at dictionary.c:115 - 112 else if (compare_value < 0) - 113 return find_word (dictionary->left, word); - 114 else - -> 115 return find_word (dictionary->right, word); - 116 } - 117 - 118 void - (lldb) - - -After setting our breakpoints, adding our breakpoint commands and continuing, -we run for a little bit and then hit one of our breakpoints, printing out the -error message from the breakpoint command. Apparently at this point in the -tree, our search algorithm decided to go right, but our path says the node we -want is to the left. Examining the word at the node where we stopped, and our -search word, we see: - -:: - - (lldb) expr dictionary->word - (const char *) $1 = 0x0000000100100080 "dramatis" - (lldb) expr word - (char *) $2 = 0x00007fff5fbff108 "romeo" - -So the word at our current node is "dramatis", and the word we are searching -for is "romeo". "romeo" comes after "dramatis" alphabetically, so it seems like -going right would be the correct decision. Let's ask Python what it thinks the -path from the current node to our word is: - -:: - - (lldb) script print path - LLRRL - -According to Python we need to go left-left-right-right-left from our current -node to find the word we are looking for. Let's double check our tree, and see -what word it has at that node: - -:: - - (lldb) expr dictionary->left->left->right->right->left->word - (const char *) $4 = 0x0000000100100880 "Romeo" - -So the word we are searching for is "romeo" and the word at our DFS location is -"Romeo". Aha! One is uppercase and the other is lowercase: We seem to have a -case conversion problem somewhere in our program (we do). - -This is the end of our example on how you might use Python scripting in LLDB to -help you find bugs in your program. - -Source Files for The Example ----------------------------- - -The complete code for the Dictionary program (with case-conversion bug), the -DFS function and other Python script examples (tree_utils.py) used for this -example are available below. - -tree_utils.py - Example Python functions using LLDB's API, including DFS - -:: - - """ - # ===-- tree_utils.py ---------------------------------------*- Python -*-===// - # - # The LLVM Compiler Infrastructure - # - # This file is distributed under the University of Illinois Open Source - # License. See LICENSE.TXT for details. - # - # ===---------------------------------------------------------------------===// - - tree_utils.py - A set of functions for examining binary - search trees, based on the example search tree defined in - dictionary.c. These functions contain calls to LLDB API - functions, and assume that the LLDB Python module has been - imported. - - For a thorough explanation of how the DFS function works, and - for more information about dictionary.c go to - http://lldb.llvm.org/scripting.html - """ - - - def DFS(root, word, cur_path): - """ - Recursively traverse a binary search tree containing - words sorted alphabetically, searching for a particular - word in the tree. Also maintains a string representing - the path from the root of the tree to the current node. - If the word is found in the tree, return the path string. - Otherwise return an empty string. - - This function assumes the binary search tree is - the one defined in dictionary.c It uses LLDB API - functions to examine and traverse the tree nodes. - """ - - # Get pointer field values out of node 'root' - - root_word_ptr = root.GetChildMemberWithName("word") - left_child_ptr = root.GetChildMemberWithName("left") - right_child_ptr = root.GetChildMemberWithName("right") - - # Get the word out of the word pointer and strip off - # surrounding quotes (added by call to GetSummary). - - root_word = root_word_ptr.GetSummary() - end = len(root_word) - 1 - if root_word[0] == '"' and root_word[end] == '"': - root_word = root_word[1:end] - end = len(root_word) - 1 - if root_word[0] == '\'' and root_word[end] == '\'': - root_word = root_word[1:end] - - # Main depth first search - - if root_word == word: - return cur_path - elif word < root_word: - - # Check to see if left child is NULL - - if left_child_ptr.GetValue() is None: - return "" - else: - cur_path = cur_path + "L" - return DFS(left_child_ptr, word, cur_path) - else: - - # Check to see if right child is NULL - - if right_child_ptr.GetValue() is None: - return "" - else: - cur_path = cur_path + "R" - return DFS(right_child_ptr, word, cur_path) - - - def tree_size(root): - """ - Recursively traverse a binary search tree, counting - the nodes in the tree. Returns the final count. - - This function assumes the binary search tree is - the one defined in dictionary.c It uses LLDB API - functions to examine and traverse the tree nodes. - """ - if (root.GetValue is None): - return 0 - - if (int(root.GetValue(), 16) == 0): - return 0 - - left_size = tree_size(root.GetChildAtIndex(1)) - right_size = tree_size(root.GetChildAtIndex(2)) - - total_size = left_size + right_size + 1 - return total_size - - - def print_tree(root): - """ - Recursively traverse a binary search tree, printing out - the words at the nodes in alphabetical order (the - search order for the binary tree). - - This function assumes the binary search tree is - the one defined in dictionary.c It uses LLDB API - functions to examine and traverse the tree nodes. - """ - if (root.GetChildAtIndex(1).GetValue() is not None) and ( - int(root.GetChildAtIndex(1).GetValue(), 16) != 0): - print_tree(root.GetChildAtIndex(1)) - - print root.GetChildAtIndex(0).GetSummary() - - if (root.GetChildAtIndex(2).GetValue() is not None) and ( - int(root.GetChildAtIndex(2).GetValue(), 16) != 0): - print_tree(root.GetChildAtIndex(2)) - - -dictionary.c - Sample dictionary program, with bug - -:: - - //===-- dictionary.c ---------------------------------------------*- C -*-===// - // - // The LLVM Compiler Infrastructure - // - // This file is distributed under the University of Illinois Open Source - // License. See LICENSE.TXT for details. - // - //===---------------------------------------------------------------------===// - #include - #include - #include - #include - - typedef struct tree_node { - const char *word; - struct tree_node *left; - struct tree_node *right; - } tree_node; - - /* Given a char*, returns a substring that starts at the first - alphabet character and ends at the last alphabet character, i.e. it - strips off beginning or ending quotes, punctuation, etc. */ - - char *strip(char **word) { - char *start = *word; - int len = strlen(start); - char *end = start + len - 1; - - while ((start < end) && (!isalpha(start[0]))) - start++; - - while ((end > start) && (!isalpha(end[0]))) - end--; - - if (start > end) - return NULL; - - end[1] = '\0'; - *word = start; - - return start; - } - - /* Given a binary search tree (sorted alphabetically by the word at - each node), and a new word, inserts the word at the appropriate - place in the tree. */ - - void insert(tree_node *root, char *word) { - if (root == NULL) - return; - - int compare_value = strcmp(word, root->word); - - if (compare_value == 0) - return; - - if (compare_value < 0) { - if (root->left != NULL) - insert(root->left, word); - else { - tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); - new_node->word = strdup(word); - new_node->left = NULL; - new_node->right = NULL; - root->left = new_node; - } - } else { - if (root->right != NULL) - insert(root->right, word); - else { - tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); - new_node->word = strdup(word); - new_node->left = NULL; - new_node->right = NULL; - root->right = new_node; - } - } - } - - /* Read in a text file and storea all the words from the file in a - binary search tree. */ - - void populate_dictionary(tree_node **dictionary, char *filename) { - FILE *in_file; - char word[1024]; - - in_file = fopen(filename, "r"); - if (in_file) { - while (fscanf(in_file, "%s", word) == 1) { - char *new_word = (strdup(word)); - new_word = strip(&new_word); - if (*dictionary == NULL) { - tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); - new_node->word = new_word; - new_node->left = NULL; - new_node->right = NULL; - *dictionary = new_node; - } else - insert(*dictionary, new_word); - } - } - } - - /* Given a binary search tree and a word, search for the word - in the binary search tree. */ - - int find_word(tree_node *dictionary, char *word) { - if (!word || !dictionary) - return 0; - - int compare_value = strcmp(word, dictionary->word); - - if (compare_value == 0) - return 1; - else if (compare_value < 0) - return find_word(dictionary->left, word); - else - return find_word(dictionary->right, word); - } - - /* Print out the words in the binary search tree, in sorted order. */ - - void print_tree(tree_node *dictionary) { - if (!dictionary) - return; - - if (dictionary->left) - print_tree(dictionary->left); - - printf("%s\n", dictionary->word); - - if (dictionary->right) - print_tree(dictionary->right); - } - - int main(int argc, char **argv) { - tree_node *dictionary = NULL; - char buffer[1024]; - char *filename; - int done = 0; - - if (argc == 2) - filename = argv[1]; - - if (!filename) - return -1; - - populate_dictionary(&dictionary, filename); - fprintf(stdout, "Dictionary loaded.\nEnter search word: "); - while (!done && fgets(buffer, sizeof(buffer), stdin)) { - char *word = buffer; - int len = strlen(word); - int i; - - for (i = 0; i < len; ++i) - word[i] = tolower(word[i]); - - if ((len > 0) && (word[len - 1] == '\n')) { - word[len - 1] = '\0'; - len = len - 1; - } - - if (find_word(dictionary, word)) - fprintf(stdout, "Yes!\n"); - else - fprintf(stdout, "No!\n"); - - fprintf(stdout, "Enter search word: "); - } - - fprintf(stdout, "\n"); - return 0; - } - - -The text for "Romeo and Juliet" can be obtained from the Gutenberg Project -(http://www.gutenberg.org). - diff --git a/lldb/docs/use/scripting-example.rst b/lldb/docs/use/scripting-example.rst new file mode 100644 --- /dev/null +++ b/lldb/docs/use/scripting-example.rst @@ -0,0 +1,1197 @@ +Scripting Example +================= + +LLDB has been structured from the beginning to be scriptable in two +ways -- a Unix Python session can initiate/run a debug session +non-interactively using LLDB; and within the LLDB debugger tool, Python +scripts can be used to help with many tasks, including inspecting program +data, iterating over containers and determining if a breakpoint should +stop execution or continue. + +Now, Lua is also capable of doing such kind of tasks. Code blocks can be +switched to Lua if you want. + +This document will show how to do some of these things by going through an +example, explaining how to use scripting to find a bug in a program that +searches for text in a large binary tree. + +You can check :doc:`/use/scripting-reference` if you want to learn more about +scripting features in LLDB. + +.. contents:: + :local: + +The Test Program and Input +-------------------------- + +We have a simple C program (dictionary.c) that reads in a text file, +and stores all the words from the file in a Binary Search Tree, sorted +alphabetically. It then enters a loop prompting the user for a word, +searching for the word in the tree (using Binary Search), and reporting +to the user whether or not it found the word in the tree. + +The input text file we are using to test our program contains the text +for William Shakespeare's famous tragedy "Romeo and Juliet". + +The Bug +------- + +When we try running our program, we find there is a problem. While it +successfully finds some of the words we would expect to find, such as +"love" or "sun", it fails to find the word "Romeo", which MUST be in +the input text file: + +:: + + % ./dictionary Romeo-and-Juliet.txt + Dictionary loaded. + Enter search word: love + Yes! + Enter search word: sun + Yes! + Enter search word: Romeo + No! + Enter search word: ^D + % + +Using Depth First Search +------------------------ + +Our first job is to determine if the word "Romeo" actually got inserted +into the tree or not. Since "Romeo and Juliet" has thousands of words, +trying to examine our binary search tree by hand is completely +impractical. Therefore we will write a script to search the tree +for us. We will write a recursive Depth First Search function that +traverses the entire tree searching for a word, and maintaining +information about the path from the root of the tree to the current +node. If it finds the word in the tree, it returns the path from the +root to the node containing the word. This is what our DFS function would look +like, with line numbers added for easy reference in later explanations: + +.. tabs:: + .. code-tab:: python + + def DFS(root, word, cur_path): + root_word_ptr = root.GetChildMemberWithName("word") + left_child_ptr = root.GetChildMemberWithName("left") + right_child_ptr = root.GetChildMemberWithName("right") + root_word = root_word_ptr.GetSummary() + end = len root_word) - 1 + if root_word[0] == '"' and root_word[end] == '"': + root_word = root_word[1:end] + end = len(root_word) - 1 + if root_word[0] == '\'' and root_word[end] == '\'': + root_word = root_word[1:end] + if root_word == word: + return cur_path + elif word < root_word: + if left_child_ptr.GetValue() == None: + return "" + else: + cur_path = cur_path + "L" + return DFS(left_child_ptr, word, cur_path) + else: + if right_child_ptr.GetValue() == None: + return "" + else: + cur_path = cur_path + "R" + return DFS(right_child_ptr, word, cur_path) + + .. code-tab:: lua + + local function DFS(root, word, cur_path) + local root_word_ptr = root:GetChildMemberWithName("word") + local left_child_ptr = root:GetChildMemberWithName("left") + local right_child_ptr = root:GetChildMemberWithName("right") + local root_word = root_word_ptr:GetSummary() + local end_pos = #root_word + if root_word:sub(1, 1) == '"' and root_word:sub(end_pos, end_pos) == '"' then + root_word = root_word:sub(2, end_pos - 1) + end + end_pos = #root_word + if root_word:sub(1, 1) == '\'' and root_word:sub(end_pos, end_pos) == '\'' then + root_word = root_word:sub(2, end_pos - 1) + end + if root_word == word then + return cur_path + elseif word < root_word then + if not left_child_ptr:GetValue() then + return "" + else + cur_path = cur_path .. "L" + return DFS(left_child_ptr, word, cur_path) + end + else + if not right_child_ptr:GetValue() then + return "" + else + cur_path = cur_path .. "R" + return DFS(right_child_ptr, word, cur_path) + end + end + end + + return { DFS = DFS } + +Accessing & Manipulating Program Variables +------------------------------------------ + +Before we can call any script function on any of our program's +variables, we need to get the variable into a form that script can +access. To show you how to do this we will look at the parameters for +the DFS function. The first parameter is going to be a node in our +binary search tree, put into a script variable. The second parameter is +the word we are searching for (a string), and the third parameter is a +string representing the path from the root of the tree to our current +node. + +The most interesting parameter is the first one, the script variable +that needs to contain a node in our search tree. How can we take a +variable out of our program and put it into a script variable? What +kind of script variable will it be? The answers are to use the LLDB API +functions, provided as part of the LLDB module. Running script interpreter +from inside LLDB, LLDB will automatically give us our current frame +object as a script variable, "lldb.frame". This variable has the type +`SBFrame` (see the LLDB API for more information about `SBFrame` +objects). One of the things we can do with a frame object, is to ask it +to find and return its local variable. We will call the API function +`SBFrame.FindVariable` on the lldb.frame object to give us our dictionary +variable as a script variable: + +.. tabs:: + .. code-tab:: python + + .. code-tab:: lua + +.. tabs:: + .. code-tab:: python + + root = lldb.frame.FindVariable("dictionary") + + .. code-tab:: lua + + root = lldb.frame:FindVariable("dictionary") + +The line above, executed in the script interpreter in LLDB, asks the +current frame to find the variable named "dictionary" and return it. We then +store the returned value in the script variable named "root". This answers the +question of HOW to get the variable, but it still doesn't explain WHAT actually +gets put into "root". If you examine the LLDB API, you will find that the +`SBFrame` method "FindVariable" returns an object of type `SBValue`. `SBValue` +objects are used, among other things, to wrap up program variables and values. +There are many useful methods defined in the `SBValue` class to allow you to get +information or children values out of SBValues. For complete information, see +the header file SBValue.h. The `SBValue` methods that we use in our DFS function +are ``GetChildMemberWithName()``, ``GetSummary()``, and ``GetValue()``. + + +Explaining DFS Script in Detail +------------------------------- + +Before diving into the details of this code, it would be best to give a +high-level overview of what it does. The nodes in our binary search tree were +defined to have type ``tree_node *``, which is defined as: + +:: + + typedef struct tree_node + { + const char *word; + struct tree_node *left; + struct tree_node *right; + } tree_node; + +.. tabs:: + .. code-tab:: python + :linenos: + :emphasize-lines: 2-13 + + def DFS(root, word, cur_path): + # get members and value + root_word_ptr = root.GetChildMemberWithName("word") + left_child_ptr = root.GetChildMemberWithName("left") + right_child_ptr = root.GetChildMemberWithName("right") + root_word = root_word_ptr.GetSummary() + # strip off quotes + end = len(root_word) - 1 + if root_word[0] == '"' and root_word[end] == '"': + root_word = root_word[1:end] + end = len(root_word) - 1 + if root_word[0] == '\'' and root_word[end] == '\'': + root_word = root_word[1:end] + # actual DFS begins + if root_word == word: + # found, return current path + return cur_path + elif word < root_word: + if left_child_ptr.GetValue() == None: + # not found, empty path is returned + return "" + else: + # append "L" to current path + cur_path = cur_path + "L" + # recurse on the left child + return DFS(left_child_ptr, word, cur_path) + else: + if right_child_ptr.GetValue() == None: + # not found, empty path is returned + return "" + else: + # append "R" to current path + cur_path = cur_path + "R" + # recurse on the right child + return DFS(right_child_ptr, word, cur_path) + + .. code-tab:: lua + :linenos: + :emphasize-lines: 2-15 + + local function DFS(root, word, cur_path) + -- get members and value + local root_word_ptr = root:GetChildMemberWithName("word") + local left_child_ptr = root:GetChildMemberWithName("left") + local right_child_ptr = root:GetChildMemberWithName("right") + local root_word = root_word_ptr:GetSummary() + -- strip off quotes + local end_pos = #root_word + if root_word:sub(1, 1) == '"' and root_word:sub(end_pos, end_pos) == '"' then + root_word = root_word:sub(2, end_pos - 1) + end + end_pos = #root_word + if root_word:sub(1, 1) == '\'' and root_word:sub(end_pos, end_pos) == '\'' then + root_word = root_word:sub(2, end_pos - 1) + end + -- actual DFS begins + if root_word == word then + -- found, return current path + return cur_path + elseif word < root_word then + if not left_child_ptr:GetValue() then + -- not found, empty path is returned + return "" + else + -- append "L" to current path + cur_path = cur_path .. "L" + -- recurse on the left child + return DFS(left_child_ptr, word, cur_path) + end + else + if not right_child_ptr:GetValue() then + -- not found, empty path is returned + return "" + else + -- append "R" to current path + cur_path = cur_path .. "R" + -- recurse on the right child + return DFS(right_child_ptr, word, cur_path) + end + end + end + + return { DFS = DFS } + +The emphasized lines in the above code are getting data out of the current tree +node and getting ready to do the actual search. + +.. tabs:: + .. code-tab:: python + :linenos: + :lineno-start: 2 + + # get members and value + root_word_ptr = root.GetChildMemberWithName("word") + left_child_ptr = root.GetChildMemberWithName("left") + right_child_ptr = root.GetChildMemberWithName("right") + root_word = root_word_ptr.GetSummary() + + .. code-tab:: lua + :linenos: + :lineno-start: 2 + + -- get members and value + local root_word_ptr = root:GetChildMemberWithName("word") + local left_child_ptr = root:GetChildMemberWithName("left") + local right_child_ptr = root:GetChildMemberWithName("right") + local root_word = root_word_ptr:GetSummary() + +This part of our DFS function get the word, left and right fields out of the +current node and store them in script variables. Since ``root_word_ptr`` is a +pointer to our word, and we want the actual word, we need to call GetSummary() +to get a string containing the value out of the pointer. + + +.. tabs:: + .. code-tab:: python + :linenos: + :lineno-start: 7 + + # strip off quotes + end = len(root_word) - 1 + if root_word[0] == '"' and root_word[end] == '"': + root_word = root_word[1:end] + end = len(root_word) - 1 + if root_word[0] == '\'' and root_word[end] == '\'': + root_word = root_word[1:end] + + .. code-tab:: lua + :linenos: + :lineno-start: 7 + + -- strip off quotes + local end_pos = #root_word + if root_word:sub(1, 1) == '"' and root_word:sub(end_pos, end_pos) == '"' then + root_word = root_word:sub(2, end_pos - 1) + end + end_pos = #root_word + if root_word:sub(1, 1) == '\'' and root_word:sub(end_pos, end_pos) == '\'' then + root_word = root_word:sub(2, end_pos - 1) + end + +Since GetSummary() adds quotes around its result, this part strips surrounding +quotes off the word. + +.. tabs:: + .. code-tab:: python + :linenos: + :lineno-start: 14 + + # actual DFS begins + if root_word == word: + # found, return current path + return cur_path + elif word < root_word: + if left_child_ptr.GetValue() == None: + # not found, empty path is returned + return "" + else: + # append "L" to current path + cur_path = cur_path + "L" + # recurse on the left child + return DFS(left_child_ptr, word, cur_path) + else: + if right_child_ptr.GetValue() == None: + # not found, empty path is returned + return "" + else: + # append "R" to current path + cur_path = cur_path + "R" + # recurse on the right child + return DFS(right_child_ptr, word, cur_path) + + .. code-tab:: lua + :linenos: + :lineno-start: 16 + + -- actual DFS begins + if root_word == word then + -- found, return current path + return cur_path + elseif word < root_word then + if not left_child_ptr:GetValue() then + -- not found, empty path is returned + return "" + else + -- append "L" to current path + cur_path = cur_path .. "L" + -- recurse on the left child + return DFS(left_child_ptr, word, cur_path) + end + else + if not right_child_ptr:GetValue() then + -- not found, empty path is returned + return "" + else + -- append "R" to current path + cur_path = cur_path .. "R" + -- recurse on the right child + return DFS(right_child_ptr, word, cur_path) + end + end + +Then it checks to see if the word in the current node is the one we are +searching for. If so, we are done, and the current path is returned. + +Otherwise, it checks to see if we should go left (search word comes before +the current word). If we decide to go left, it checks to see if the left +pointer child is NULL ("None" is the Python equivalent of NULL, "nil" is the Lua +equivalent). If the left pointer is NULL, then the word is not in this tree and +we return an empty path. Otherwise, we add an "L" to the end of our current path +string, to indicate we are going left, and then recurse on the left child. + +The final "else" part is the same as the previous, except for going right rather +than going left. + +One other note: Typing something as long as our DFS function directly into the +interpreter can be difficult, as making a single typing mistake means having to +start all over. Therefore we recommend doing as we have done: Writing your +longer, more complicated script functions in a separate file (in this case +``tree_utils.py`` for Python, ``tree_utils.lua`` for Lua) and then importing it +into your LLDB script interpreter. + + +The DFS Script in Action +------------------------ + +At this point we are ready to use the DFS function to see if the word "Romeo" +is in our tree or not. To actually use it in LLDB on our dictionary program, +you would do something like this: + +.. tabs:: + .. code-tab:: python + + % lldb + (lldb) process attach -n "dictionary" + Process 95616 stopped + * thread #1, name = 'dictionary', stop reason = signal SIGSTOP + frame #0: 0x00007fcf4906a862 libc.so.6 __read + 18 + libc.so.6 __read: + -> 0x7fcf4906a862 <+18>: cmpq $-0x1000, %rax ; imm = 0xF000 + 0x7fcf4906a868 <+24>: ja 0x7fcf4906a8c0 ; <+112> + 0x7fcf4906a86a <+26>: retq + 0x7fcf4906a86b <+27>: nopl (%rax,%rax) + + Executable module set to "/home/test/Desktop/dictionary". + Architecture set to: x86_64-pc-linux-gnu. + (lldb) breakpoint set -n find_word + Breakpoint 1: where = dictionary find_word + 16 at dictionary.c:108:9, address = 0x000055f9ac1fa5c0 + (lldb) continue + Process 95616 resuming + Process 95616 stopped + * thread #1, name = 'dictionary', stop reason = breakpoint 1.1 + frame #0: 0x000055f9ac1fa5c0 dictionary find_word(dictionary=0x000055f9ae14b4b0, word="romeo") at dictionary.c:108:9 + (lldb) script + Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. + >>> import tree_utils + >>> root = lldb.frame.FindVariable("dictionary") + >>> current_path = "" + >>> path = tree_utils.DFS(root, "Romeo", current_path) + >>> print(path) + LR + >>> ^D + now exiting InteractiveConsole... + (lldb) + + .. code-tab:: lua + :emphasize-lines: 21 + + % lldb + (lldb) process attach -n "dictionary" + Process 95616 stopped + * thread #1, name = 'dictionary', stop reason = signal SIGSTOP + frame #0: 0x00007fcf4906a862 libc.so.6 __read + 18 + libc.so.6 __read: + -> 0x7fcf4906a862 <+18>: cmpq $-0x1000, %rax ; imm = 0xF000 + 0x7fcf4906a868 <+24>: ja 0x7fcf4906a8c0 ; <+112> + 0x7fcf4906a86a <+26>: retq + 0x7fcf4906a86b <+27>: nopl (%rax,%rax) + + Executable module set to "/home/test/Desktop/dictionary". + Architecture set to: x86_64-pc-linux-gnu. + (lldb) breakpoint set -n find_word + Breakpoint 1: where = dictionary find_word + 16 at dictionary.c:108:9, address = 0x000055f9ac1fa5c0 + (lldb) continue + Process 95616 resuming + Process 95616 stopped + * thread #1, name = 'dictionary', stop reason = breakpoint 1.1 + frame #0: 0x000055f9ac1fa5c0 dictionary find_word(dictionary=0x000055f9ae14b4b0, word="romeo") at dictionary.c:108:9 + (lldb) script -l lua -- + >>> tree_utils = require("tree_utils") + >>> root = lldb.frame:FindVariable("dictionary") + >>> current_path = "" + >>> path = tree_utils.DFS(root, "Romeo", current_path) + >>> print(path) + LR + >>> ^D + (lldb) + +The first bit of code above shows starting lldb, attaching to the dictionary +program, and getting to the find_word function in LLDB. The interesting part +(as far as this example is concerned) begins when we enter the script command +and drop into the embedded interactive script interpreter. We will go over this +scripting code line by line. The first line + +.. tabs:: + .. code-tab:: python + + import tree_utils + + .. code-tab:: lua + + tree_utils = require("tree_utils") + +imports the file where we wrote our DFS function (tree_utils.py or tree_utils.lua) +into scripting environment. Notice that to import the file we leave off the ".py" +extension. We can now call any function in that file, giving it the prefix +"tree_utils.", so that interpreter knows where to look for the function. The line + +.. tabs:: + .. code-tab:: python + + root = lldb.frame.FindVariable("dictionary") + + .. code-tab:: lua + + root = lldb.frame:FindVariable("dictionary") + +gets our program variable "dictionary" (which contains the binary search tree) +and puts it into the script variable "root". See Accessing & Manipulating +Program Variables above for more details about how this works. The +next line is + +:: + + current_path = "" + +This line initializes the current_path from the root of the tree to our current +node. Since we are starting at the root of the tree, our current path starts as +an empty string. As we go right and left through the tree, the DFS function +will append an 'R' or an 'L' to the current path, as appropriate. The line + +:: + + path = tree_utils.DFS(root, "Romeo", current_path) + +calls our DFS function (prefixing it with the module name so that interpreter +can find it). We pass in our binary tree stored in the variable root, the word +we are searching for, and our current path. We assign whatever path the DFS +function returns to the variable path. + +Finally, we want to see if the word was found or not, and if so we want to see +the path through the tree to the word. So we do + +:: + + print(path) + +From this we can see that the word "Romeo" was indeed found in the tree, and +the path from the root of the tree to the node containing "Romeo" is +left-left-right-right-left. + +Using Breakpoint Command Scripts +-------------------------------- + +We are halfway to figuring out what the problem is. We know the word we are +looking for is in the binary tree, and we know exactly where it is in the +binary tree. Now we need to figure out why our binary search algorithm is not +finding the word. We will do this using breakpoint command scripts. + +The idea is as follows. The binary search algorithm has two main decision +points: the decision to follow the right branch; and, the decision to follow +the left branch. We will set a breakpoint at each of these decision points, and +attach a breakpoint command script to each breakpoint. The breakpoint +commands will use the global ``path`` variable that we got from our DFS +function. Each time one of these decision breakpoints is hit, the script will +compare the actual decision with the decision the front of the path variable +says should be made (the first character of the path). If the actual decision +and the path agree, then the front character is stripped off the path, and +execution is resumed. In this case the user never even sees the breakpoint +being hit. But if the decision differs from what the path says it should be, +then the script prints out a message and does NOT resume execution, leaving the +user sitting at the first point where a wrong decision is being made. + +Breakpoint Command Scripts Are Not What They Seem +------------------------------------------------- + +What do we mean by that? When you enter a breakpoint command script in LLDB, it +appears that you are entering one or more plain lines of scripts. BUT LLDB then +takes what you entered and wraps it into a function (just like using the +"def" Python command, or "function" in Lua syntax). It automatically gives the +function an obscure, unique, hard-to-stumble-across function name, and gives it +two parameters: frame and bp_loc. When the breakpoint gets hit, LLDB wraps up +the frame object where the breakpoint was hit, and the breakpoint location object +for the breakpoint that was hit, and puts them into script variables for you. +It then calls the wrapped function that was created for the breakpoint command, +and passes in the frame and breakpoint location objects. + +So, being practical, what does this mean for you when you write your breakpoint +command scripts? It means that there are two things you need to keep in +mind: 1. (Python only) If you want to access any variables created outside your +script, you must declare such variables to be global. If you do not declare +them as global, then the Python function will treat them as local variables, +and you will get unexpected behavior. 2. All breakpoint command scripts +automatically have a frame and a bp_loc variable. The variables are pre-loaded +by LLDB with the correct context for the breakpoint. You do not have to use +these variables, but they are there if you want them. + +The Decision Point Breakpoint Commands +-------------------------------------- + +This is what the breakpoint command script would look like for the decision to +go right: + +.. tabs:: + .. code-tab:: python + + global path + if path[0] == 'R': + path = path[1:] + thread = frame.GetThread() + process = thread.GetProcess() + process.Continue() + else: + print("Here is the problem; going right, should go left!") + + .. code-tab:: lua + + if path:sub(1, 1) == 'R' then + path = path:sub(2, #path) + thread = frame:GetThread() + process = thread:GetProcess() + process:Continue() + else + print("Here is the problem; going right, should go left!") + end + +Just as a reminder, LLDB is going to take this script and wrap it up in a function, like this: + +.. tabs:: + .. code-tab:: python + + def some_unique_and_obscure_function_name(frame, bp_loc): + global path + if path[0] == 'R': + path = path[1:] + thread = frame.GetThread() + process = thread.GetProcess() + process.Continue() + else: + print("Here is the problem; going right, should go left!") + + .. code-tab:: lua + + function some_unique_and_obscure_function_name(frame, bp_loc) + if path:sub(1, 1) == 'R' then + path = path:sub(2, #path) + thread = frame:GetThread() + process = thread:GetProcess() + process:Continue() + else + print("Here is the problem; going right, should go left!") + end + end + +LLDB will call the function, passing in the correct frame and breakpoint +location whenever the breakpoint gets hit. There are several things to notice +about this function. The first one is that we are accessing and updating a +piece of state (the path variable), and actually conditioning our behavior +based upon this variable. In Python, since the variable was defined outside of +our script (and therefore outside of the corresponding function) we need to tell +it that we are accessing a global variable, while this is completely unnecessary +in Lua. That is what the first line of the script does. Next we check where the +path says we should go and compare it to our decision (recall that we are at the +breakpoint for the decision to go right). If the path agrees with our decision, +then we strip the first character off of the path. + +Since the decision matched the path, we want to resume execution. To do this we +make use of the frame parameter that LLDB guarantees will be there for us. We +use LLDB API functions to get the current thread from the current frame, and +then to get the process from the thread. Once we have the process, we tell it +to resume execution (using the Continue() API function). + +If the decision to go right does not agree with the path, then we do not resume +execution. We allow the breakpoint to remain stopped (by doing nothing), and we +print an informational message telling the user we have found the problem, and +what the problem is. + +Actually Using The Breakpoint Commands +-------------------------------------- + +Now we will look at what happens when we actually use these breakpoint commands +on our program. Doing a ``source list -n find_word -c 20`` shows us 20 lines of +the function containing our two decision points. Looking at the code below, we +see that we want to set our breakpoints on lines 116 and 118: + +.. code-block:: python + :emphasize-lines: 17,19 + + (lldb) source list -n find_word -c 20 + File: /home/test/Desktop/dictionary.c + 102 } + 103 + 104 /* Given a binary search tree and a word, search for the word + 105 in the binary search tree. */ + 106 + 107 int find_word(tree_node *dictionary, char *word) { + 108 if (!word || !dictionary) + 109 return 0; + 110 + 111 int compare_value = strcmp(word, dictionary->word); + 112 + 113 if (compare_value == 0) + 114 return 1; + 115 else if (compare_value < 0) + 116 return find_word(dictionary->left, word); + 117 else + 118 return find_word(dictionary->right, word); + 119 } + 120 + 121 /* Print out the words in the binary search tree, in sorted order. */ + 122 + 123 void print_tree(tree_node *dictionary) { + 124 if (!dictionary) + + +So, we set our breakpoints, enter our breakpoint command scripts, and see what happens: + +:: + + (lldb) breakpoint set -l 116 + Breakpoint created: 2: file ='dictionary.c', line = 113, locations = 1, resolved = 1 + (lldb) breakpoint set -l 118 + Breakpoint created: 3: file ='dictionary.c', line = 115, locations = 1, resolved = 1 + (lldb) breakpoint command add -s python 2 + Enter your Python command(s). Type 'DONE' to end. + > global path + > if (path[0] == 'L'): + > path = path[1:] + > thread = frame.GetThread() + > process = thread.GetProcess() + > process.Continue() + > else: + > print "Here is the problem. Going left, should go right!" + > DONE + (lldb) breakpoint command add -s python 3 + Enter your Python command(s). Type 'DONE' to end. + > global path + > if (path[0] == 'R'): + > path = path[1:] + > thread = frame.GetThread() + > process = thread.GetProcess() + > process.Continue() + > else: + > print "Here is the problem. Going right, should go left!" + > DONE + (lldb) continue + Process 696 resuming + Here is the problem. Going right, should go left! + Process 696 stopped + * thread #1: tid = 0x2d03, 0x000000010000189f dictionary`find_word + 127 at dictionary.c:115, stop reason = breakpoint 3.1 + frame #0: 0x000000010000189f dictionary`find_word + 127 at dictionary.c:115 + 112 else if (compare_value < 0) + 113 return find_word (dictionary->left, word); + 114 else + -> 115 return find_word (dictionary->right, word); + 116 } + 117 + 118 void + (lldb) + + +After setting our breakpoints, adding our breakpoint commands and continuing, +we run for a little bit and then hit one of our breakpoints, printing out the +error message from the breakpoint command. Apparently at this point in the +tree, our search algorithm decided to go right, but our path says the node we +want is to the left. Examining the word at the node where we stopped, and our +search word, we see: + +:: + + (lldb) expr dictionary->word + (const char *) $1 = 0x0000000100100080 "dramatis" + (lldb) expr word + (char *) $2 = 0x00007fff5fbff108 "romeo" + +So the word at our current node is "dramatis", and the word we are searching +for is "romeo". "romeo" comes after "dramatis" alphabetically, so it seems like +going right would be the correct decision. Let's ask the script what it +thinks the path from the current node to our word is: + +:: + + (lldb) script print(path) + LLRRL + +According to the output above, we need to go left-left-right-right-left from our +current node to find the word we are looking for. Let's double check our tree, +and see what word it has at that node: + +:: + + (lldb) expr dictionary->left->left->right->right->left->word + (const char *) $4 = 0x0000000100100880 "Romeo" + +So the word we are searching for is "romeo" and the word at our DFS location is +"Romeo". Aha! One is uppercase and the other is lowercase: We seem to have a +case conversion problem somewhere in our program (we do). + +This is the end of our example on how you might use scripting in LLDB to help +you find bugs in your program. + +Source Files for The Example +---------------------------- + +The complete code for the Dictionary program (with case-conversion bug), the +DFS function and other script examples (tree_utils.py / tree_utils.lua) used +for this example are available below. + +tree_utils.py - Example Python functions using LLDB's API, including DFS + +tree_utils.lua - Example Lua functions using LLDB's API, including DFS + +.. tabs:: + .. code-tab:: python + + """ + # ===-- tree_utils.py ---------------------------------------*- Python -*-===// + # + # The LLVM Compiler Infrastructure + # + # This file is distributed under the University of Illinois Open Source + # License. See LICENSE.TXT for details. + # + # ===---------------------------------------------------------------------===// + + tree_utils.py - A set of functions for examining binary + search trees, based on the example search tree defined in + dictionary.c. These functions contain calls to LLDB API + functions, and assume that the LLDB Python module has been + imported. + + For a thorough explanation of how the DFS function works, and + for more information about dictionary.c go to + http://lldb.llvm.org/scripting.html + """ + + + def DFS(root, word, cur_path): + """ + Recursively traverse a binary search tree containing + words sorted alphabetically, searching for a particular + word in the tree. Also maintains a string representing + the path from the root of the tree to the current node. + If the word is found in the tree, return the path string. + Otherwise return an empty string. + + This function assumes the binary search tree is + the one defined in dictionary.c It uses LLDB API + functions to examine and traverse the tree nodes. + """ + + # Get pointer field values out of node 'root' + + root_word_ptr = root.GetChildMemberWithName("word") + left_child_ptr = root.GetChildMemberWithName("left") + right_child_ptr = root.GetChildMemberWithName("right") + + # Get the word out of the word pointer and strip off + # surrounding quotes (added by call to GetSummary). + + root_word = root_word_ptr.GetSummary() + end = len(root_word) - 1 + if root_word[0] == '"' and root_word[end] == '"': + root_word = root_word[1:end] + end = len(root_word) - 1 + if root_word[0] == '\'' and root_word[end] == '\'': + root_word = root_word[1:end] + + # Main depth first search + + if root_word == word: + return cur_path + elif word < root_word: + + # Check to see if left child is NULL + + if left_child_ptr.GetValue() is None: + return "" + else: + cur_path = cur_path + "L" + return DFS(left_child_ptr, word, cur_path) + else: + + # Check to see if right child is NULL + + if right_child_ptr.GetValue() is None: + return "" + else: + cur_path = cur_path + "R" + return DFS(right_child_ptr, word, cur_path) + + + def tree_size(root): + """ + Recursively traverse a binary search tree, counting + the nodes in the tree. Returns the final count. + + This function assumes the binary search tree is + the one defined in dictionary.c It uses LLDB API + functions to examine and traverse the tree nodes. + """ + if (root.GetValue is None): + return 0 + + if (int(root.GetValue(), 16) == 0): + return 0 + + left_size = tree_size(root.GetChildAtIndex(1)) + right_size = tree_size(root.GetChildAtIndex(2)) + + total_size = left_size + right_size + 1 + return total_size + + + def print_tree(root): + """ + Recursively traverse a binary search tree, printing out + the words at the nodes in alphabetical order (the + search order for the binary tree). + + This function assumes the binary search tree is + the one defined in dictionary.c It uses LLDB API + functions to examine and traverse the tree nodes. + """ + if (root.GetChildAtIndex(1).GetValue() is not None) and ( + int(root.GetChildAtIndex(1).GetValue(), 16) != 0): + print_tree(root.GetChildAtIndex(1)) + + print root.GetChildAtIndex(0).GetSummary() + + if (root.GetChildAtIndex(2).GetValue() is not None) and ( + int(root.GetChildAtIndex(2).GetValue(), 16) != 0): + print_tree(root.GetChildAtIndex(2)) + + .. code-tab:: lua + + local function DFS(root, word, cur_path) + --[[ + Recursively traverse a binary search tree containing + words sorted alphabetically, searching for a particular + word in the tree. Also maintains a string representing + the path from the root of the tree to the current node. + If the word is found in the tree, return the path string. + Otherwise return an empty string. + + This function assumes the binary search tree is + the one defined in dictionary.c It uses LLDB API + functions to examine and traverse the tree nodes. + ]] + + -- Get pointer field values out of node 'root' + local root_word_ptr = root:GetChildMemberWithName("word") + local left_child_ptr = root:GetChildMemberWithName("left") + local right_child_ptr = root:GetChildMemberWithName("right") + + -- Get the word out of the word pointer and strip off + -- surrounding quotes (added by call to GetSummary). + + local root_word = root_word_ptr:GetSummary() + local end_pos = #root_word + if root_word:sub(1, 1) == '"' and root_word:sub(end_pos, end_pos) == '"' then + root_word = root_word:sub(2, end_pos - 1) + end + end_pos = #root_word + if root_word:sub(1, 1) == '\'' and root_word:sub(end_pos, end_pos) == '\'' then + root_word = root_word:sub(2, end_pos - 1) + end + + -- Main depth first search + + if root_word == word then + return cur_path + elseif word < root_word then + + -- Check to see if left child is NULL + + if not left_child_ptr:GetValue() then + return "" + else + cur_path = cur_path .. "L" + return DFS(left_child_ptr, word, cur_path) + end + else + + -- Check to see if right child is NULL + + if not right_child_ptr:GetValue() then + return "" + else + cur_path = cur_path .. "R" + return DFS(right_child_ptr, word, cur_path) + end + end + end + + return { DFS = DFS } + +dictionary.c - Sample dictionary program, with bug + +.. code-block:: c + + //===-- dictionary.c ---------------------------------------------*- C -*-===// + // + // The LLVM Compiler Infrastructure + // + // This file is distributed under the University of Illinois Open Source + // License. See LICENSE.TXT for details. + // + //===---------------------------------------------------------------------===// + #include + #include + #include + #include + + typedef struct tree_node { + const char *word; + struct tree_node *left; + struct tree_node *right; + } tree_node; + + /* Given a char*, returns a substring that starts at the first + alphabet character and ends at the last alphabet character, i.e. it + strips off beginning or ending quotes, punctuation, etc. */ + + char *strip(char **word) { + char *start = *word; + int len = strlen(start); + char *end = start + len - 1; + + while ((start < end) && (!isalpha(start[0]))) + start++; + + while ((end > start) && (!isalpha(end[0]))) + end--; + + if (start > end) + return NULL; + + end[1] = '\0'; + *word = start; + + return start; + } + + /* Given a binary search tree (sorted alphabetically by the word at + each node), and a new word, inserts the word at the appropriate + place in the tree. */ + + void insert(tree_node *root, char *word) { + if (root == NULL) + return; + + int compare_value = strcmp(word, root->word); + + if (compare_value == 0) + return; + + if (compare_value < 0) { + if (root->left != NULL) + insert(root->left, word); + else { + tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); + new_node->word = strdup(word); + new_node->left = NULL; + new_node->right = NULL; + root->left = new_node; + } + } else { + if (root->right != NULL) + insert(root->right, word); + else { + tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); + new_node->word = strdup(word); + new_node->left = NULL; + new_node->right = NULL; + root->right = new_node; + } + } + } + + /* Read in a text file and storea all the words from the file in a + binary search tree. */ + + void populate_dictionary(tree_node **dictionary, char *filename) { + FILE *in_file; + char word[1024]; + + in_file = fopen(filename, "r"); + if (in_file) { + while (fscanf(in_file, "%s", word) == 1) { + char *new_word = (strdup(word)); + new_word = strip(&new_word); + if (*dictionary == NULL) { + tree_node *new_node = (tree_node *)malloc(sizeof(tree_node)); + new_node->word = new_word; + new_node->left = NULL; + new_node->right = NULL; + *dictionary = new_node; + } else + insert(*dictionary, new_word); + } + } + } + + /* Given a binary search tree and a word, search for the word + in the binary search tree. */ + + int find_word(tree_node *dictionary, char *word) { + if (!word || !dictionary) + return 0; + + int compare_value = strcmp(word, dictionary->word); + + if (compare_value == 0) + return 1; + else if (compare_value < 0) + return find_word(dictionary->left, word); + else + return find_word(dictionary->right, word); + } + + /* Print out the words in the binary search tree, in sorted order. */ + + void print_tree(tree_node *dictionary) { + if (!dictionary) + return; + + if (dictionary->left) + print_tree(dictionary->left); + + printf("%s\n", dictionary->word); + + if (dictionary->right) + print_tree(dictionary->right); + } + + int main(int argc, char **argv) { + tree_node *dictionary = NULL; + char buffer[1024]; + char *filename; + int done = 0; + + if (argc == 2) + filename = argv[1]; + + if (!filename) + return -1; + + populate_dictionary(&dictionary, filename); + fprintf(stdout, "Dictionary loaded.\nEnter search word: "); + while (!done && fgets(buffer, sizeof(buffer), stdin)) { + char *word = buffer; + int len = strlen(word); + int i; + + for (i = 0; i < len; ++i) + word[i] = tolower(word[i]); + + if ((len > 0) && (word[len - 1] == '\n')) { + word[len - 1] = '\0'; + len = len - 1; + } + + if (find_word(dictionary, word)) + fprintf(stdout, "Yes!\n"); + else + fprintf(stdout, "No!\n"); + + fprintf(stdout, "Enter search word: "); + } + + fprintf(stdout, "\n"); + return 0; + } + + +The text for "Romeo and Juliet" can be obtained from the Gutenberg Project +(https://www.gutenberg.org/files/1513/1513-0.txt). + diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/scripting-reference.rst rename from lldb/docs/use/python-reference.rst rename to lldb/docs/use/scripting-reference.rst --- a/lldb/docs/use/python-reference.rst +++ b/lldb/docs/use/scripting-reference.rst @@ -1,23 +1,32 @@ -Python Reference -================ +Scripting Reference +=================== -The entire LLDB API is available as Python functions through a script bridging -interface. This means the LLDB API's can be used directly from python either -interactively or to build python apps that provide debugger features. - -Additionally, Python can be used as a programmatic interface within the lldb -command interpreter (we refer to this for brevity as the embedded interpreter). +Python can be used as a programmatic interface within the LLDB command +interpreter (we refer to this for brevity as the embedded interpreter). Of course, in this context it has full access to the LLDB API - with some additional conveniences we will call out in the FAQ. +Additionally, the entire LLDB API is available as Python functions through +a script bridging interface. This means the LLDB API's can be used directly +from Python either interactively or to build Python apps that provide debugger +features. + +At present, Lua is experimentally available as an embedded interpreter in LLDB. +Moreover, LLDB API is also visible to standalone Lua. Since some scripting +features are still unavailable in Lua, there is no language switch tab on code +blocks of unsupported features. + +If you want to use Lua interpreter, you should ensure that your LLDB is compiled +with Lua support. See :doc:`Building ` for instructions. + .. contents:: :local: -Documentation --------------- +Built-in Documentation +---------------------- -The LLDB API is contained in a python module named lldb. A useful resource when -writing Python extensions is the lldb Python classes reference guide. +The LLDB API is contained in a Python module named ``lldb``. A useful resource +when writing Python extensions is the `lldb` Python classes reference guide. The documentation is also accessible in an interactive debugger session with the following command: @@ -54,7 +63,8 @@ | ... -Or you can get help using any python object, here we use the lldb.process + +Or you can get help using any Python object, here we use the lldb.process object which is a global variable in the lldb module which represents the currently selected process: @@ -74,25 +84,53 @@ | ... -Embedded Python Interpreter +Embedded Script Interpreter --------------------------- -The embedded python interpreter can be accessed in a variety of ways from -within LLDB. The easiest way is to use the lldb command script with no -arguments at the lldb command prompt: +The embedded script interpreter can be accessed in a variety of ways from +within LLDB. The easiest way is to use the LLDB command ``script`` with no +arguments at the LLDB command prompt. + +By default, the embedded script interpreter is Python. To choose the language +you prefer (e.g. Lua), you can specify default language via +``--script-language`` options when launching LLDB: + +:: + + $ lldb --script-language lua + +or pass a parameter to LLDB command ``script``. :: - (lldb) script - Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. - >>> 2+3 - 5 - >>> hex(12345) - '0x3039' - >>> + (lldb) script -l lua -- + +Language features are all available in the embedded script interpreter: + +.. tabs:: + .. code-tab:: python + + (lldb) script + Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. + >>> 2+3 + 5 + >>> hex(12345) + '0x3039' + >>> + + .. code-tab:: lua + :emphasize-lines: 1 -This drops you into the embedded python interpreter. When running under the -script command, lldb sets some convenience variables that give you quick access + (lldb) script -l lua -- + >>> t = { 1, 2, 3 } + >>> print(t[1]) + 1 + >>> print(t[1] + t[2]) + 3 + >>> + +This drops you into the embedded script interpreter. When running under the +script command, LLDB sets some convenience variables that give you quick access to the currently selected entities that characterize the program and debugger state. In each case, if there is no currently selected entity of the appropriate type, the variable's IsValid method will return false. These @@ -136,62 +174,86 @@ on entry to the embedded interpreter. They do not update as you use the LLDB API's to change, for example, the currently selected stack frame or thread. -Moreover, they are only defined and meaningful while in the interactive Python +Moreover, they are only defined and meaningful while in the interactive script interpreter. There is no guarantee on their value in any other situation, hence -you should not use them when defining Python formatters, breakpoint scripts and -commands (or any other Python extension point that LLDB provides). For the +you should not use them when defining custom formatters, breakpoint scripts and +commands (or any other script extension point that LLDB provides). For the latter you'll be passed an `SBDebugger`, `SBTarget`, `SBProcess`, `SBThread` or `SBframe` instance and you can use the functions from the "Equivalent" column to navigate between them. -As a rationale for such behavior, consider that lldb can run in a multithreaded +As a rationale for such behavior, consider that LLDB can run in a multithreaded environment, and another thread might call the "script" command, changing the value out from under you. To get started with these objects and LLDB scripting, please note that almost -all of the lldb Python objects are able to briefly describe themselves when you -pass them to the Python print function: - -:: - - (lldb) script - Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. - >>> print lldb.debugger - Debugger (instance: "debugger_1", id: 1) - >>> print lldb.target - a.out - >>> print lldb.process - SBProcess: pid = 59289, state = stopped, threads = 1, executable = a.out - >>> print lldb.thread - SBThread: tid = 0x1f03 - >>> print lldb.frame - frame #0: 0x0000000100000bb6 a.out main + 54 at main.c:16 - - -Running a python script when a breakpoint gets hit --------------------------------------------------- - -One very powerful use of the lldb Python API is to have a python script run -when a breakpoint gets hit. Adding python scripts to breakpoints provides a way -to create complex breakpoint conditions and also allows for smart logging and -data gathering. - -When your process hits a breakpoint to which you have attached some python +all of the lldb objects are able to briefly describe themselves when you pass +them to ``print`` function: + +.. tabs:: + .. code-tab:: python + + (lldb) script + Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. + >>> print lldb.debugger + Debugger (instance: "debugger_1", id: 1) + >>> print lldb.target + a.out + >>> print lldb.process + SBProcess: pid = 59289, state = stopped, threads = 1, executable = a.out + >>> print lldb.thread + SBThread: tid = 0x1f03 + >>> print lldb.frame + frame #0: 0x0000000100000bb6 a.out main + 54 at main.c:16 + + .. code-tab:: lua + + (lldb) script -l lua -- + >>> print(lldb.debugger) + Debugger (instance: "debugger_1", id: 1) + >>> print(lldb.target) + a.out + >>> print(lldb.process) + SBProcess: pid = 3385871, state = stopped, threads = 1, executable = a.out + >>> print(lldb.thread) + thread #1: tid = 3385871, name = 'a.out', stop reason = breakpoint 1.1 + >>> print(lldb.frame) + frame #0: 0x000055555555514f a.out main at test.c:5:3 + +Running a script when a breakpoint gets hit +------------------------------------------- + +One very powerful use of the LLDB API is to have a script run when a breakpoint +gets hit. Adding command scripts to breakpoints provides a way to create complex +breakpoint conditions and also allows for smart logging and data gathering. + +When your process hits a breakpoint to which you have attached some script code, the code is executed as the body of a function which takes three arguments: -:: +.. tabs:: + .. code-tab:: python + + def breakpoint_function_wrapper(frame, bp_loc, internal_dict): + # Your code goes here + + .. code-tab:: lua - def breakpoint_function_wrapper(frame, bp_loc, internal_dict): - # Your code goes here + function breakpoint_function_wrapper(frame, bp_loc) + -- Your code goes here or: -:: +.. tabs:: + .. code-tab:: python + + def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict): + # Your code goes here - def breakpoint_function_wrapper(frame, bp_loc, extra_args, internal_dict): - # Your code goes here + .. code-tab:: lua + function breakpoint_function_wrapper(frame, bp_loc, ...) + -- Your code goes here +-------------------+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+ | Argument | Type | Description | @@ -209,10 +271,10 @@ | | | you could take the function name from a field in the `extra_args`, making the callback more general. The `-k` and `-v` options | | | | to `breakpoint command add` will be passed as a Dictionary in the `extra_args` parameter, or you can provide it with the SB API's. | +-------------------+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+ -| `internal_dict` | `dict` | The python session dictionary as a standard python dictionary object. | +| `internal_dict` | `dict` | The Python session dictionary as a standard python dictionary object. | +-------------------+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+ -Optionally, a Python breakpoint command can return a value. Returning False +Optionally, a breakpoint command script can return a value. Returning False tells LLDB that you do not want to stop at the breakpoint. Any other return value (including None or leaving out the return statement altogether) is akin to telling LLDB to actually stop at the breakpoint. This can be useful in @@ -220,7 +282,7 @@ conditions are met, and you do not want to inspect the program state manually at every stop and then continue. -An example will show how simple it is to write some python code and attach it +An example will show how simple it is to write some script code and attach it to a breakpoint. The following example will allow you to track the order in which the functions in a given shared library are first executed during one run of your program. This is a simple method to gather an order file which can be @@ -242,38 +304,60 @@ Here is the code: -:: - - (lldb) breakpoint set --func-regex=. --shlib=libfoo.dylib - Breakpoint created: 1: regex = '.', module = libfoo.dylib, locations = 223 - (lldb) script counter = 0 - (lldb) breakpoint command add --script-type python 1 - Enter your Python command(s). Type 'DONE' to end. - > # Increment our counter. Since we are in a function, this must be a global python variable - > global counter - > counter += 1 - > # Get the name of the function - > name = frame.GetFunctionName() - > # Print the order and the function name - > print '[%i] %s' % (counter, name) - > # Disable the current breakpoint location so it doesn't get hit again - > bp_loc.SetEnabled(False) - > # No need to stop here - > return False - > DONE - -The breakpoint command add command above attaches a python script to breakpoint 1. To remove the breakpoint command: +.. tabs:: + .. code-tab:: python + + (lldb) breakpoint set --func-regex=. --shlib=libfoo.dylib + Breakpoint created: 1: regex = '.', module = libfoo.dylib, locations = 223 + (lldb) script counter = 0 + (lldb) breakpoint command add --script-type python 1 + Enter your Python command(s). Type 'DONE' to end. + > # Increment our counter. Since we are in a function, this must be a global python variable + > global counter + > counter += 1 + > # Get the name of the function + > name = frame.GetFunctionName() + > # Print the order and the function name + > print '[%i] %s' % (counter, name) + > # Disable the current breakpoint location so it doesn't get hit again + > bp_loc.SetEnabled(False) + > # No need to stop here + > return False + > DONE + + .. code-tab:: lua + + (lldb) breakpoint set --func-regex=. --shlib=libfoo.dylib + Breakpoint created: 1: regex = '.', module = libfoo.dylib, locations = 223 + (lldb) script -l lua -- counter = 0 + (lldb) breakpoint command add -s lua 1 + Enter your Lua command(s). Type 'quit' to end. + The commands are compiled as the body of the following Lua function + function (frame, bp_loc, ...) end + ..> -- Increment our counter + ..> counter = counter + 1 + ..> -- Get the name of the function + ..> name = frame:GetFunctionName() + ..> -- Print the order and the function name + ..> print(string.format('[%i] %s', counter, name)) + ..> -- Disable the current breakpoint location so it doesn't get hit again + ..> bp_loc:SetEnabled(false) + ..> -- No need to stop here + ..> return false + ..> quit + +The breakpoint command add command above attaches a script to breakpoint 1. +To remove the breakpoint command: :: (lldb) breakpoint command delete 1 -Using the python api's to create custom breakpoints ---------------------------------------------------- +Create custom breakpoints (Python only) +--------------------------------------- - -Another use of the Python API's in lldb is to create a custom breakpoint +Another use of the scripting API's in lldb is to create a custom breakpoint resolver. This facility was added in r342259. It allows you to provide the algorithm which will be used in the breakpoint's @@ -423,8 +507,8 @@ you pass in empty lists, the breakpoint will use the default "search everywhere,accept everything" filter. -Using the python API' to create custom stepping logic ------------------------------------------------------ +Create custom stepping logic (Python only) +------------------------------------------ A slightly esoteric use of the Python API's is to construct custom stepping types. LLDB's stepping is driven by a stack of "thread plans" and a fairly @@ -486,8 +570,8 @@ while performing the step-over. -Create a new lldb command using a Python function -------------------------------------------------- +Create a new lldb command (Python only) +--------------------------------------- Python functions can be used to create new LLDB command interpreter commands, which will work like all the natively defined lldb commands. This provides a @@ -527,7 +611,7 @@ +-------------------+--------------------------------+----------------------------------------------------------------------------------------------------------------------------------+ | `debugger` | `lldb.SBDebugger` | The current debugger object. | +-------------------+--------------------------------+----------------------------------------------------------------------------------------------------------------------------------+ -| `command` | `python string` | A python string containing all arguments for your command. If you need to chop up the arguments | +| `command` | `string` | A python string containing all arguments for your command. If you need to chop up the arguments | | | | try using the `shlex` module's shlex.split(command) to properly extract the | | | | arguments. | +-------------------+--------------------------------+----------------------------------------------------------------------------------------------------------------------------------+ @@ -682,19 +766,21 @@ (lldb) pofoo anotherString $2 = 0x000000010010aba0 Let's Be Friendsfoobar -Using the lldb.py module in Python ----------------------------------- +Using the lldb module in Python and Lua +--------------------------------------- LLDB has all of its core code build into a shared library which gets used by the `lldb` command line application. On macOS this shared library is a framework: LLDB.framework and on other unix variants the program is a shared -library: lldb.so. LLDB also provides an lldb.py module that contains the -bindings from LLDB into Python. To use the LLDB.framework to create your own -stand-alone python programs, you will need to tell python where to look in -order to find this module. This is done by setting the PYTHONPATH environment -variable, adding a path to the directory that contains the lldb.py python -module. The lldb driver program has an option to report the path to the lldb -module. You can use that to point to correct lldb.py: +library: lldb.so. + +LLDB also provides an lldb.py module that contains the bindings from LLDB into +Python. To use the LLDB.framework to create your own stand-alone python +programs, you will need to tell python where to look in order to find this +module. This is done by setting the PYTHONPATH environment variable, adding a +path to the directory that contains the lldb.py python module. The lldb driver +program has an option to report the path to the lldb module. You can use that to +point to correct lldb.py: For csh and tcsh: @@ -711,81 +797,158 @@ Alternately, you can append the LLDB Python directory to the sys.path list directly in your Python code before importing the lldb module. -Now your python scripts are ready to import the lldb module. Below is a python -script that will launch a program from the current working directory called -"a.out", set a breakpoint at "main", and then run and hit the breakpoint, and -print the process, thread and frame objects if the process stopped: - -:: - - #!/usr/bin/env python - - import lldb - import os - - def disassemble_instructions(insts): - for i in insts: - print i - - # Set the path to the executable to debug - exe = "./a.out" - - # Create a new debugger instance - debugger = lldb.SBDebugger.Create() - - # When we step or continue, don't return from the function until the process - # stops. Otherwise we would have to handle the process events ourselves which, while doable is - #a little tricky. We do this by setting the async mode to false. - debugger.SetAsync (False) - - # Create a target from a file and arch - print "Creating a target for '%s'" % exe - - target = debugger.CreateTargetWithFileAndArch (exe, lldb.LLDB_ARCH_DEFAULT) - - if target: - # If the target is valid set a breakpoint at main - main_bp = target.BreakpointCreateByName ("main", target.GetExecutable().GetFilename()); - - print main_bp - - # Launch the process. Since we specified synchronous mode, we won't return - # from this function until we hit the breakpoint at main - process = target.LaunchSimple (None, None, os.getcwd()) - - # Make sure the launch went ok - if process: - # Print some simple process info - state = process.GetState () - print process - if state == lldb.eStateStopped: - # Get the first thread - thread = process.GetThreadAtIndex (0) - if thread: - # Print some simple thread info - print thread - # Get the first frame - frame = thread.GetFrameAtIndex (0) - if frame: - # Print some simple frame info - print frame - function = frame.GetFunction() - # See if we have debug info (a function) - if function: - # We do have a function, print some info for the function - print function - # Now get all instructions for this function and print them - insts = function.GetInstructions(target) - disassemble_instructions (insts) - else: - # See if we have a symbol in the symbol table for where we stopped - symbol = frame.GetSymbol(); - if symbol: - # We do have a symbol, print some info for the symbol - print symbol - -Writing lldb frame recognizers in Python ----------------------------------------- +Now your scripts are ready to import the lldb module. Below is a script that +will launch a program from the current working directory called "a.out", set a +breakpoint at "main", and then run and hit the breakpoint, and print the +process, thread and frame objects if the process stopped: + +.. tabs:: + .. code-tab:: python + + #!/usr/bin/env python + + import lldb + import os + + def disassemble_instructions(insts): + for i in insts: + print i + + # Set the path to the executable to debug + exe = "./a.out" + + # Create a new debugger instance + debugger = lldb.SBDebugger.Create() + + # When we step or continue, don't return from the function until the process + # stops. Otherwise we would have to handle the process events ourselves which, while doable is + #a little tricky. We do this by setting the async mode to false. + debugger.SetAsync (False) + + # Create a target from a file and arch + print "Creating a target for '%s'" % exe + + target = debugger.CreateTargetWithFileAndArch (exe, lldb.LLDB_ARCH_DEFAULT) + + if target: + # If the target is valid set a breakpoint at main + main_bp = target.BreakpointCreateByName ("main", target.GetExecutable().GetFilename()); + + print main_bp + + # Launch the process. Since we specified synchronous mode, we won't return + # from this function until we hit the breakpoint at main + process = target.LaunchSimple (None, None, os.getcwd()) + + # Make sure the launch went ok + if process: + # Print some simple process info + state = process.GetState () + print process + if state == lldb.eStateStopped: + # Get the first thread + thread = process.GetThreadAtIndex (0) + if thread: + # Print some simple thread info + print thread + # Get the first frame + frame = thread.GetFrameAtIndex (0) + if frame: + # Print some simple frame info + print frame + function = frame.GetFunction() + # See if we have debug info (a function) + if function: + # We do have a function, print some info for the function + print function + # Now get all instructions for this function and print them + insts = function.GetInstructions(target) + disassemble_instructions (insts) + else: + # See if we have a symbol in the symbol table for where we stopped + symbol = frame.GetSymbol(); + if symbol: + # We do have a symbol, print some info for the symbol + print symbol + + .. code-tab:: lua + + local lldb = require('lldb') + + function disassemble_instructions(insts) + for _, i in ipairs(insts) do + print(i) + end + end + + -- Set the path to the executable to debug + local exe = './a.out' + + -- Debugger requires initialization before using in Lua + lldb.SBDebugger.Initialize() + -- Create a new debugger instance + local debugger = lldb.SBDebugger.Create() + + -- When we step or continue, don't return from the function until the process + -- stops. Otherwise we would have to handle the process events ourselves which, while doable is + -- a little tricky. We do this by setting the async mode to false. + debugger:SetAsync(false) + + -- Create a target from a file and arch + print(string.format("Creating a target for '%s'", exe)) + + local target = debugger:CreateTargetWithFileAndArch(exe, lldb.LLDB_ARCH_DEFAULT) + + if target then + -- If the target is valid set a breakpoint at main + local main_bp = target:BreakpointCreateByName("main", target:GetExecutable():GetFilename()); + + print(main_bp) + + -- Launch the process. Since we specified synchronous mode, we won't return + -- from this function until we hit the breakpoint at main + local process = target:LaunchSimple(nil, nil, nil) + + -- Make sure the launch went ok + if process then + -- Print some simple process info + local state = process:GetState() + print(process) + if state == lldb.eStateStopped then + -- Get the first thread + thread = process:GetThreadAtIndex(0) + if thread then + -- Print some simple thread info + print(thread) + -- Get the first frame + frame = thread:GetFrameAtIndex(0) + if frame then + -- Print some simple frame info + print(frame) + func = frame:GetFunction() + -- See if we have debug info (a function) + if func:IsValid() then + -- We do have a function, print some info for the function + print(func) + -- Now get all instructions for this function and print them + local insts = func:GetInstructions(target) + disassemble_instructions(insts) + else + -- See if we have a symbol in the symbol table for where we stopped + symbol = frame:GetSymbol() + if symbol then + -- We do have a symbol, print some info for the symbol + print(symbol) + end + end + end + end + end + end + end + +Writing lldb frame recognizers in Python (Python only) +------------------------------------------------------ Frame recognizers allow for retrieving information about special frames based on ABI, arguments or other special properties of that frame, even without @@ -835,8 +998,8 @@ (lldb) frame variable (int) fd = 3 -Writing Target Stop-Hooks in Python: ------------------------------------- +Writing Target Stop-Hooks in Python (Python only) +------------------------------------------------- Stop hooks fire whenever the process stops just before control is returned to the user. Stop hooks can either be a set of lldb command-line commands, or can