summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE24
-rw-r--r--Makefile7
-rw-r--r--README.md24
-rw-r--r--compile_flags.txt1
-rw-r--r--example.c27
-rw-r--r--main.cpp148
7 files changed, 233 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cde9fe7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+example
+tdbg
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..31d811a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+BSD 2-Clause License
+
+Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ff1b104
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+all: tdbg example
+
+tdbg: main.cpp
+ clang++ main.cpp -o tdbg -I/usr/lib/llvm/21/include -L/usr/lib/llvm/21/lib -Wl,-rpath,/usr/lib/llvm/21/lib -llldb -std=c++17
+
+example: example.c
+ clang -g -o example example.c
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3f73f2c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+# Toy Debugger
+
+This project demonstrates how to use the LLDB C++ API to build a very basic
+debugger tool.
+
+**Requirements**
+
+```sh
+sudo xbps-install -S lldb21-devel clang llvm llvm-devel
+```
+
+After you clone the repository build the debugger and a sample program with
+`make`.
+
+Then run the debugger with example program with `./tdbg example`.
+
+**Available commands:**
+
+- `c` - Continue execution until the next breakpoint or stop
+- `s` - Step into the next instruction/function
+- `n` - Step over the next instruction/function
+- `bt` - Print a backtrace (call stack) of the current thread
+- `v` - Print local variables in the current stack frame
+- `q` - Kill the debugged process and exit
diff --git a/compile_flags.txt b/compile_flags.txt
new file mode 100644
index 0000000..3848ca8
--- /dev/null
+++ b/compile_flags.txt
@@ -0,0 +1 @@
+-I/usr/lib/llvm/21/include
diff --git a/example.c b/example.c
new file mode 100644
index 0000000..6a5119e
--- /dev/null
+++ b/example.c
@@ -0,0 +1,27 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef struct {
+ int q;
+ int w;
+} Bar;
+
+int main(void) {
+ const char *myenv = getenv("MYENV");
+
+ int a = 100;
+ int b = 123;
+ int c = a + b;
+
+ Bar bar = { .q = 565, .w = 949 };
+
+ printf("> MYENV: %s\n", myenv);
+ printf("> c: %d\n", c);
+ printf("> bar.q: %d\n", bar.q);
+
+ for (int i=0; i<10; i++) {
+ printf("> loop %d\n", i);
+ }
+
+ return 0;
+}
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..b1c8078
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,148 @@
+#include <lldb/API/LLDB.h>
+
+#include <iostream>
+#include <string>
+#include <thread>
+#include <chrono>
+
+using namespace lldb;
+
+void print_variables(SBFrame &frame) {
+ SBValueList vars = frame.GetVariables(true, true, false, true);
+
+ for (uint32_t i = 0; i < vars.GetSize(); ++i) {
+ SBValue var = vars.GetValueAtIndex(i);
+ std::cout << var.GetName() << " = ";
+
+ if (!var.IsValid()) {
+ std::cout << "(invalid)\n";
+ }
+ else if (var.GetType().IsPointerType()) {
+ uint64_t addr = var.GetValueAsUnsigned();
+ if (addr == 0) {
+ std::cout << "(null pointer)\n";
+ } else {
+ std::cout << "(pointer at 0x" << std::hex << addr << std::dec << ")\n";
+ }
+ }
+ else if (var.GetNumChildren() > 0) {
+ std::cout << "{ ";
+ uint32_t n = var.GetNumChildren();
+ for (uint32_t j = 0; j < n; ++j) {
+ SBValue child = var.GetChildAtIndex(j);
+ std::cout << child.GetName() << ": " << (child.IsValid() ? child.GetValue() : "(invalid)");
+ if (j + 1 < n) std::cout << ", ";
+ }
+ std::cout << " }\n";
+ } else {
+ std::cout << var.GetValue() << "\n";
+ }
+ }
+}
+
+
+void print_backtrace(SBThread &thread) {
+ int num_frames = thread.GetNumFrames();
+ for (int i = 0; i < num_frames; ++i) {
+ SBFrame frame = thread.GetFrameAtIndex(i);
+ std::cout << "#" << i << " " << frame.GetFunctionName()
+ << " at line " << frame.GetLineEntry().GetLine() << "\n";
+ }
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ std::cerr << "Usage: " << argv[0] << " <target_executable>\n";
+ return 1;
+ }
+
+ const char* target_path = argv[1];
+
+ // Initialize LLDB.
+ SBDebugger::Initialize();
+ SBDebugger debugger = SBDebugger::Create();
+ debugger.SetAsync(true);
+
+ SBTarget target = debugger.CreateTarget(target_path);
+ if (!target.IsValid()) {
+ std::cerr << "Failed to create target for " << target_path << "\n";
+ return 1;
+ }
+
+ SBBreakpoint bp = target.BreakpointCreateByName("main");
+ std::cout << "Breakpoint set at main\n";
+
+ SBProcess process = target.LaunchSimple(nullptr, nullptr, ".");
+ if (!process.IsValid()) {
+ std::cerr << "Failed to launch process\n";
+ return 1;
+ }
+
+ std::cout << "Process launched\n";
+
+ SBListener listener = debugger.GetListener();
+ SBEvent event;
+
+ while (true) {
+ // Wait for events from LLDB.
+ if (listener.WaitForEvent(1, event)) {
+ StateType state = SBProcess::GetStateFromEvent(event);
+
+ switch (state) {
+ case eStateStopped:
+ {
+ std::cout << "\nProcess stopped!\n";
+ SBThread thread = process.GetSelectedThread();
+ SBFrame frame = thread.GetFrameAtIndex(0);
+ std::cout << "Stopped at function: " << frame.GetFunctionName()
+ << ", line: " << frame.GetLineEntry().GetLine() << "\n";
+
+ // REPL loop for user commands.
+ std::string cmd;
+ while (true) {
+ std::cout << "(tdbg) ";
+ std::getline(std::cin, cmd);
+ if (cmd == "c") {
+ process.Continue();
+ break; // exit REPL, wait for next stop
+ } else if (cmd == "s") {
+ thread.StepInto();
+ break;
+ } else if (cmd == "n") {
+ thread.StepOver();
+ break;
+ } else if (cmd == "bt") {
+ print_backtrace(thread);
+ } else if (cmd == "v") {
+ print_variables(frame);
+ } else if (cmd == "q") {
+ process.Kill();
+ goto cleanup;
+ } else {
+ std::cout << "Commands: c=continue, s=step in, n=step over, bt=backtrace, v=variables, q=quit\n";
+ }
+ }
+ break;
+ }
+
+ case eStateExited:
+ {
+ std::cout << "Process exited\n";
+ goto cleanup;
+ }
+
+ case eStateRunning:
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+ }
+
+cleanup:
+ SBDebugger::Terminate();
+ return 0;
+}