First of all, I come up to use lldb debugger because I have macOS on m1 processor.
(Unfortunately gdb is not support m1 macs).
Then I thought a lot about how I should run debugger from program and come up to build the process using
ProcessBuilderand then write commands to the input of the processor.
Also, I decided that my application will not compile user code from the provided sources.
That is because of the primary goal of debugger it to debug and not compile, and also sometimes, compiling
some code is not a trivial task.
(When a user uses some external libraries)
I tried to support linux x86, but this attempt unfortunately failed. (But i don't gave up)
- The package
org.gnudebugger.appcontains API class, that are provided to user. - The package
org.gnudebugger.config.corecontains all abstract interfaces, which are used to create lldb debugger configurations and commands. - The package
org.gnudebugger.config.lldbcontains implementation of previous package. - The package
org.gnudebugger.debuggercontains abstractDebuggerand its implementationLldbDebugger
This architecture helps to easily extend application. For example, if I decide to add another debugger, I don't need to refactor a lot of code, just implement several classes
I wrote simple Unit tests to help me write code. See tests. But not focus on testing too much because it is not a main goal of this project.
- If I have enough time, I want to support gdb for linux x86.
- Have an idea how to create runtime interface and implement
stepfunction to go forward while debugging.
If you want to use lldb GNU debugger, you must compile your program using clang or gcc with -g option. Also I used --std=c99 flag to specify C standard.
For now, it only supports C. First of all you have to create instance of DebugDriver class, which is basic
class for sheath around GNU debugger. You can do it like this:
val debuggerDriver = DebugDriver("/usr/bin/lldb")Also, you may use DebugDriver as a library in your project.
Here is a .jar file.
To use my driver, you have to compile your code and provide executable file using function loadDebugExecutable.
It accepts a path as String and supports an absolute path or path relative to current directory.
debuggerDriver.loadDebugExecutable("debugee/target")To set a breakpoint you should run setBreakPoint function, which accepts file name and line number where
you want to place point.
For each breakpoint, you should specify a breakpoint handler.
This approach is good, that you can specify different
handlers for different breakpoints, but if you want the same handler at all points, you have to specify it multiple
times.
debuggerDriver.setBreakPoint("main.c", 5)My driver has setBreakPointHandler which accepts function with BufferedReader and OutputStream as arguments.
They are needed to invoke some driver function in the scope.
I didn't create something smarter than this, because I don't know how to not pass input and output from the process,
which is not running at the moment. Also don't know how to hide these parameters.
So, how does my breakpoint handler work? In the scope of the described function, you should write what you want to happen on
the break. But at the end you must invoke driver.resume(input, output) to finish handling point.
driver.getBackTrace(input, output)- return stack trace string representation. You may use it haw you want.debugger.getVarValueByName("i", input, output)- return a value in format(type) valueor returnERRORmessage.
import java.io.BufferedReader
import java.io.OutputStream
debuggerDriver.setBreakPointHandler { input: BufferedReader, output: OutputStream ->
println(debuggerDriver.getBackTrace(input, output))
println(debuggerDriver.getVarValueByName("i", input, output))
debuggerDriver.resume(input, output)
}To run DebugDriver you should use run() method, which accepts a List<String> of program arguments.