mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 11:02:04 +08:00
Logs just helped someone on Discord debug an issue in a way that would not have been possible just by stepping the Python script. It was that LLDB could not find the lldb-server binary. We do talk about logs elsewhere but I think it's fine to repeat here since it's a lot of people's first experience with scripting.
166 lines
5.8 KiB
Markdown
166 lines
5.8 KiB
Markdown
# Implementing Standalone Scripts
|
|
|
|
### Configuring `PYTHONPATH`
|
|
|
|
LLDB has all of its core code built into a shared library which gets used by
|
|
the `lldb` command line application.
|
|
- On macOS this shared library is a framework: `LLDB.framework`.
|
|
- 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:
|
|
|
|
For csh and tcsh:
|
|
|
|
```csh
|
|
% setenv PYTHONPATH `lldb -P`
|
|
```
|
|
|
|
For sh and bash:
|
|
|
|
```bash
|
|
$ export PYTHONPATH=`lldb -P`
|
|
```
|
|
|
|
Alternatively, you can append the LLDB Python directory to the sys.path list
|
|
directly in your Python code before importing the lldb module.
|
|
|
|
### Initialization
|
|
|
|
The standard test for `__main__`, like many python modules do, is useful for
|
|
creating scripts that can be run from the command line. However, for command
|
|
line scripts, the debugger instance must be created manually. Sample code would
|
|
look like:
|
|
|
|
```python3
|
|
if __name__ == '__main__':
|
|
# Initialize the debugger before making any API calls.
|
|
lldb.SBDebugger.Initialize()
|
|
# Create a new debugger instance in your module if your module
|
|
# can be run from the command line. When we run a script from
|
|
# the command line, we won't have any debugger object in
|
|
# lldb.debugger, so we can just create it if it will be needed
|
|
debugger = lldb.SBDebugger.Create()
|
|
|
|
# Next, do whatever work this module should do when run as a command.
|
|
# ...
|
|
|
|
# Finally, dispose of the debugger you just made.
|
|
lldb.SBDebugger.Destroy(debugger)
|
|
# Terminate the debug session
|
|
lldb.SBDebugger.Terminate()
|
|
```
|
|
|
|
### Example
|
|
|
|
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:
|
|
|
|
```python3
|
|
#!/usr/bin/env python3
|
|
|
|
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)
|
|
```
|
|
|
|
### Expected Output
|
|
|
|
Exact output varies by system, but you should see something like this:
|
|
|
|
```
|
|
Creating a target for './a.out'
|
|
SBBreakpoint: id = 1, name = 'main', module = a.out, locations = 1
|
|
SBProcess: pid = 2593449, state = stopped, threads = 1, executable = a.out
|
|
thread #1: tid = 2593449, 0x0000aaaaaaaa0714 a.out`main at test.c:3:9, name = 'test.o', stop reason = breakpoint 1.1
|
|
frame #0: 0x0000aaaaaaaa0714 a.out`main at test.c:3:9
|
|
SBFunction: id = 0x0000002e, name = main, type = main
|
|
a.out[0x714]: mov w0, #0x0 ; =0
|
|
a.out[0x718]: ret
|
|
```
|
|
|
|
### Troubleshooting
|
|
|
|
You can use all the usual Python tools to debug scripts, and on top of that
|
|
you can enable LLDB's log channels. To do this in the script shown above, add
|
|
this line right after `debugger` has been assigned:
|
|
|
|
```python
|
|
debugger.EnableLog("lldb", ["all"])
|
|
```
|
|
|
|
`lldb` `all` enables a lot of different channels, so you will probably want
|
|
to enable only a few channels once you know what you are interested in.
|
|
|
|
This API call is the equivalent of `log enable lldb all` when using LLDB
|
|
interactively. All channels available to `log enable` can be enabled using
|
|
`EnableLog` too. |