8#include <lldb/API/SBBreakpoint.h>
9#include <lldb/API/SBBreakpointLocation.h>
10#include <lldb/API/SBCommandInterpreter.h>
11#include <lldb/API/SBDebugger.h>
12#include <lldb/API/SBError.h>
13#include <lldb/API/SBEvent.h>
14#include <lldb/API/SBFileSpec.h>
15#include <lldb/API/SBFrame.h>
16#include <lldb/API/SBListener.h>
17#include <lldb/API/SBProcess.h>
18#include <lldb/API/SBStream.h>
19#include <lldb/API/SBTarget.h>
20#include <lldb/API/SBThread.h>
21#include <lldb/API/SBType.h>
22#include <lldb/API/SBValue.h>
23#include <lldb/API/SBValueList.h>
48 lldb::SBDebugger::Initialize();
51 debugger_ = lldb::SBDebugger::Create(
false);
52 if (!debugger_.IsValid()) {
53 setError(
"Failed to create LLDB debugger instance");
58 debugger_.SetAsync(
true);
61 listener_ = debugger_.GetListener();
62 if (!listener_.IsValid()) {
63 setError(
"Failed to create LLDB listener");
64 lldb::SBDebugger::Destroy(debugger_);
84 if (target_.IsValid()) {
85 debugger_.DeleteTarget(target_);
89 if (debugger_.IsValid()) {
90 lldb::SBDebugger::Destroy(debugger_);
94 lldb::SBDebugger::Terminate();
105 setError(
"LLDB not initialized");
111 target_ = debugger_.CreateTarget(
"",
"",
"",
false, error);
112 if (!target_.IsValid()) {
113 setError(
"Failed to create target: " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
118 lldb::SBAttachInfo attach_info(pid);
119 attach_info.SetListener(listener_);
121 process_ = target_.Attach(attach_info, error);
122 if (!process_.IsValid() || error.Fail()) {
123 setError(
"Failed to attach to PID " + std::to_string(pid) +
": " +
124 std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
125 debugger_.DeleteTarget(target_);
135 setError(
"LLDB not initialized");
141 target_ = debugger_.CreateTarget(
"",
"",
"",
false, error);
142 if (!target_.IsValid()) {
143 setError(
"Failed to create target: " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
148 lldb::SBAttachInfo attach_info;
149 attach_info.SetExecutable(process_name.c_str());
150 attach_info.SetWaitForLaunch(wait_for,
false);
151 attach_info.SetListener(listener_);
153 process_ = target_.Attach(attach_info, error);
154 if (!process_.IsValid() || error.Fail()) {
155 setError(
"Failed to attach to process '" + process_name +
156 "': " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
157 debugger_.DeleteTarget(target_);
170 lldb::SBError error = process_.Detach();
172 setError(
"Detach failed: " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
179 if (!process_.IsValid()) {
183 lldb::StateType
state = process_.GetState();
184 return state != lldb::eStateInvalid &&
state != lldb::eStateDetached &&
state != lldb::eStateExited;
188 if (!process_.IsValid()) {
191 return static_cast<pid_t
>(process_.GetProcessID());
195 if (!target_.IsValid()) {
199 lldb::SBFileSpec exe = target_.GetExecutable();
200 if (!exe.IsValid()) {
204 const char *filename = exe.GetFilename();
205 return filename ? filename :
"";
214 setError(
"Not attached to a process");
222 lldb::StateType current = process_.GetState();
223 if (current == lldb::eStateStopped || current == lldb::eStateSuspended) {
229 if (current != lldb::eStateRunning && current != lldb::eStateStepping) {
230 setError(
"Process is not running (state: " + std::to_string(
static_cast<int>(current)) +
")");
234 lldb::SBError error = process_.Stop();
236 setError(
"Failed to stop process: " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
241 if (!waitForState(lldb::eStateStopped, 5)) {
242 setError(
"Timeout waiting for process to stop");
252 setError(
"Not attached to a process");
260 lldb::StateType current = process_.GetState();
261 if (current == lldb::eStateRunning || current == lldb::eStateStepping) {
266 lldb::SBError error = process_.Continue();
268 setError(
"Failed to resume process: " + std::string(error.GetCString() ? error.GetCString() :
"unknown error"));
274 waitForState(lldb::eStateRunning, 2);
281 lldb::SBThread thread = getSelectedThreadInternal();
282 if (!thread.IsValid()) {
283 setError(
"No valid thread selected");
293 lldb::SBThread thread = getSelectedThreadInternal();
294 if (!thread.IsValid()) {
295 setError(
"No valid thread selected");
305 lldb::SBThread thread = getSelectedThreadInternal();
306 if (!thread.IsValid()) {
307 setError(
"No valid thread selected");
317 if (!process_.IsValid()) {
321 lldb::StateType lldb_state = process_.GetState();
322 switch (lldb_state) {
323 case lldb::eStateInvalid:
325 case lldb::eStateRunning:
326 case lldb::eStateStepping:
328 case lldb::eStateStopped:
329 case lldb::eStateSuspended:
331 case lldb::eStateExited:
333 case lldb::eStateCrashed:
335 case lldb::eStateDetached:
349 std::vector<ThreadInfo> result;
351 if (!process_.IsValid()) {
358 lldb::SBThread selected = process_.GetSelectedThread();
359 uint64_t selected_id = selected.IsValid() ? selected.GetThreadID() : 0;
361 uint32_t num_threads = process_.GetNumThreads();
362 result.reserve(num_threads);
364 for (uint32_t i = 0; i < num_threads; i++) {
365 lldb::SBThread thread = process_.GetThreadAtIndex(i);
366 if (thread.IsValid()) {
367 bool is_selected = (thread.GetThreadID() == selected_id);
368 result.push_back(threadToInfo(thread, is_selected));
376 lldb::SBThread thread = getSelectedThreadInternal();
377 if (!thread.IsValid()) {
380 return threadToInfo(thread,
true);
384 if (!process_.IsValid()) {
385 setError(
"No valid process");
389 uint32_t num_threads = process_.GetNumThreads();
390 for (uint32_t i = 0; i < num_threads; i++) {
391 lldb::SBThread thread = process_.GetThreadAtIndex(i);
392 if (thread.IsValid() && thread.GetThreadID() ==
thread_id) {
393 process_.SetSelectedThread(thread);
399 setError(
"Thread ID " + std::to_string(
thread_id) +
" not found");
408 std::vector<FrameInfo> result;
410 lldb::SBThread thread = getSelectedThreadInternal();
411 if (!thread.IsValid()) {
415 uint32_t num_frames = thread.GetNumFrames();
416 if (max_frames > 0 && num_frames > max_frames) {
417 num_frames = max_frames;
420 result.reserve(num_frames);
421 for (uint32_t i = 0; i < num_frames; i++) {
422 lldb::SBFrame frame = thread.GetFrameAtIndex(i);
423 if (frame.IsValid()) {
424 result.push_back(frameToInfo(frame));
432 lldb::SBFrame frame = getFrameInternal(frame_index);
433 if (!frame.IsValid()) {
436 return frameToInfo(frame);
444 uint32_t expand_depth)
const {
445 lldb::SBFrame frame = getFrameInternal(frame_index);
446 if (!frame.IsValid()) {
454 if (name.find(
'.') != std::string::npos || name.find(
"->") != std::string::npos || name.find(
'[') != std::string::npos) {
456 value = frame.GetValueForVariablePath(name.c_str());
459 value = frame.FindVariable(name.c_str());
462 if (!value.IsValid()) {
463 value = frame.FindRegister(name.c_str());
467 if (!value.IsValid()) {
471 return valueToInfo(value, expand_depth);
475 bool include_statics)
const {
476 std::vector<VariableInfo> result;
478 lldb::SBFrame frame = getFrameInternal(frame_index);
479 if (!frame.IsValid()) {
483 lldb::SBValueList vars = frame.GetVariables(include_args, include_locals, include_statics,
486 uint32_t num_vars = vars.GetSize();
487 result.reserve(num_vars);
489 for (uint32_t i = 0; i < num_vars; i++) {
490 lldb::SBValue value = vars.GetValueAtIndex(i);
491 if (value.IsValid()) {
492 result.push_back(valueToInfo(value, 0));
504 if (!target_.IsValid()) {
505 setError(
"No valid target");
509 lldb::SBBreakpoint bp = target_.BreakpointCreateByLocation(file.c_str(), line);
511 setError(
"Failed to create breakpoint at " + file +
":" + std::to_string(line));
515 if (!condition.empty()) {
516 bp.SetCondition(condition.c_str());
520 return static_cast<int32_t
>(bp.GetID());
524 if (!target_.IsValid()) {
525 setError(
"No valid target");
529 bool result = target_.BreakpointDelete(
static_cast<lldb::break_id_t
>(breakpoint_id));
531 setError(
"Failed to delete breakpoint " + std::to_string(breakpoint_id));
539 std::vector<BreakpointInfo> result;
541 if (!target_.IsValid()) {
545 uint32_t num_breakpoints = target_.GetNumBreakpoints();
546 result.reserve(num_breakpoints);
548 for (uint32_t i = 0; i < num_breakpoints; i++) {
549 lldb::SBBreakpoint bp = target_.GetBreakpointAtIndex(i);
551 result.push_back(breakpointToInfo(bp));
559 if (!target_.IsValid()) {
563 lldb::SBBreakpoint bp = target_.FindBreakpointByID(
static_cast<lldb::break_id_t
>(breakpoint_id));
568 return breakpointToInfo(bp);
572 if (!process_.IsValid()) {
573 setError(
"No valid process");
586 uint32_t timeout_sec = (timeout_ms + 999) / 1000;
589 bool got_event = listener_.WaitForEvent(timeout_sec, event);
591 setError(
"Timeout waiting for breakpoint");
595 lldb::StateType new_state = lldb::SBProcess::GetStateFromEvent(event);
596 if (new_state == lldb::eStateStopped) {
599 }
else if (new_state == lldb::eStateExited || new_state == lldb::eStateCrashed ||
600 new_state == lldb::eStateDetached) {
601 setError(
"Process exited/crashed while waiting for breakpoint");
612 uint32_t frame_index)
const {
613 lldb::SBFrame frame = getFrameInternal(frame_index);
614 if (!frame.IsValid()) {
618 lldb::SBExpressionOptions options;
619 options.SetIgnoreBreakpoints(
true);
620 options.SetFetchDynamicValue(lldb::eDynamicDontRunTarget);
622 lldb::SBValue result = frame.EvaluateExpression(expression.c_str(), options);
623 if (!result.IsValid()) {
627 lldb::SBError error = result.GetError();
632 return valueToInfo(result, 0);
639void LLDBController::setError(
const std::string &msg)
const { last_error_ = msg; }
641void LLDBController::clearError()
const { last_error_.clear(); }
643void LLDBController::syncProcessState()
const {
644 if (!listener_.IsValid() || !process_.IsValid()) {
650 while (listener_.PeekAtNextEvent(event)) {
651 listener_.GetNextEvent(event);
655bool LLDBController::waitForState(lldb::StateType target_state, uint32_t timeout_sec)
const {
656 if (!listener_.IsValid() || !process_.IsValid()) {
661 lldb::StateType current = process_.GetState();
662 if (current == target_state) {
668 auto start = std::chrono::steady_clock::now();
672 auto elapsed = std::chrono::steady_clock::now() - start;
673 auto elapsed_sec = std::chrono::duration_cast<std::chrono::seconds>(elapsed).count();
674 if (elapsed_sec >= timeout_sec) {
679 bool got_event = listener_.WaitForEvent(1, event);
681 lldb::StateType new_state = lldb::SBProcess::GetStateFromEvent(event);
682 if (new_state == target_state) {
686 if (new_state == lldb::eStateExited || new_state == lldb::eStateCrashed ||
687 new_state == lldb::eStateDetached) {
693 current = process_.GetState();
694 if (current == target_state) {
700lldb::SBThread LLDBController::getSelectedThreadInternal()
const {
701 if (!process_.IsValid()) {
702 return lldb::SBThread();
704 return process_.GetSelectedThread();
707lldb::SBFrame LLDBController::getFrameInternal(uint32_t index)
const {
708 lldb::SBThread thread = getSelectedThreadInternal();
709 if (!thread.IsValid()) {
710 return lldb::SBFrame();
712 return thread.GetFrameAtIndex(index);
715ThreadInfo LLDBController::threadToInfo(lldb::SBThread &thread,
bool is_selected) {
717 info.id = thread.GetThreadID();
718 info.index = thread.GetIndexID();
719 info.is_selected = is_selected;
721 const char *name = thread.GetName();
722 info.name = name ? name :
"";
725 lldb::StopReason stop_reason = thread.GetStopReason();
726 switch (stop_reason) {
727 case lldb::eStopReasonNone:
728 info.stop_reason =
"none";
730 case lldb::eStopReasonBreakpoint:
731 info.stop_reason =
"breakpoint";
733 case lldb::eStopReasonWatchpoint:
734 info.stop_reason =
"watchpoint";
736 case lldb::eStopReasonSignal:
737 info.stop_reason =
"signal";
739 case lldb::eStopReasonException:
740 info.stop_reason =
"exception";
742 case lldb::eStopReasonPlanComplete:
743 info.stop_reason =
"step_complete";
746 info.stop_reason =
"other";
751 lldb::SBFrame frame = thread.GetFrameAtIndex(0);
752 if (frame.IsValid()) {
753 const char *func_name = frame.GetFunctionName();
754 info.function = func_name ? func_name :
"";
756 lldb::SBLineEntry line_entry = frame.GetLineEntry();
757 if (line_entry.IsValid()) {
758 lldb::SBFileSpec file_spec = line_entry.GetFileSpec();
759 if (file_spec.IsValid()) {
760 const char *filename = file_spec.GetFilename();
761 info.file = filename ? filename :
"";
763 info.line = line_entry.GetLine();
770FrameInfo LLDBController::frameToInfo(lldb::SBFrame &frame) {
772 info.index = frame.GetFrameID();
773 info.pc = frame.GetPC();
774 info.fp = frame.GetFP();
776 const char *func_name = frame.GetFunctionName();
777 info.function = func_name ? func_name :
"";
779 lldb::SBLineEntry line_entry = frame.GetLineEntry();
780 if (line_entry.IsValid()) {
781 lldb::SBFileSpec file_spec = line_entry.GetFileSpec();
782 if (file_spec.IsValid()) {
783 const char *filename = file_spec.GetFilename();
784 info.file = filename ? filename :
"";
786 info.line = line_entry.GetLine();
792VariableInfo LLDBController::valueToInfo(lldb::SBValue value, uint32_t expand_depth)
const {
794 info.is_valid = value.IsValid();
796 if (!info.is_valid) {
800 const char *name = value.GetName();
801 info.name = name ? name :
"";
803 lldb::SBType type = value.GetType();
804 if (type.IsValid()) {
805 const char *type_name = type.GetName();
806 info.type = type_name ? type_name :
"";
807 info.size = type.GetByteSize();
808 info.is_pointer = type.IsPointerType();
809 info.is_aggregate = type.IsAggregateType();
813 const char *val_str = value.GetValue();
814 info.value = val_str ? val_str :
"";
817 const char *summary = value.GetSummary();
818 info.summary = summary ? summary :
"";
821 info.address = value.GetLoadAddress();
824 lldb::SBError error = value.GetError();
826 info.is_valid =
false;
830 if (expand_depth > 0 && info.is_aggregate) {
831 uint32_t num_children = value.GetNumChildren();
833 if (num_children > 100) {
837 info.children.reserve(num_children);
838 for (uint32_t i = 0; i < num_children; i++) {
839 lldb::SBValue child = value.GetChildAtIndex(i);
840 if (child.IsValid()) {
841 info.children.push_back(valueToInfo(child, expand_depth - 1));
849BreakpointInfo LLDBController::breakpointToInfo(lldb::SBBreakpoint bp) {
851 info.id =
static_cast<int32_t
>(bp.GetID());
852 info.enabled = bp.IsEnabled();
853 info.hit_count = bp.GetHitCount();
857 const char *condition = bp.GetCondition();
858 info.condition = condition ? condition :
"";
861 info.resolved = (bp.GetNumResolvedLocations() > 0);
863 if (bp.GetNumLocations() > 0) {
864 lldb::SBBreakpointLocation loc = bp.GetLocationAtIndex(0);
866 lldb::SBAddress addr = loc.GetAddress();
867 if (addr.IsValid()) {
868 lldb::SBLineEntry line_entry = addr.GetLineEntry();
869 if (line_entry.IsValid()) {
870 lldb::SBFileSpec file_spec = line_entry.GetFileSpec();
871 if (file_spec.IsValid()) {
873 file_spec.GetPath(path,
sizeof(path));
876 info.line = line_entry.GetLine();
int32_t setBreakpoint(const std::string &file, uint32_t line, const std::string &condition="")
Set a breakpoint at file:line.
std::optional< VariableInfo > evaluateExpression(const std::string &expression, uint32_t frame_index=0) const
Evaluate an expression in the current context.
bool attachByName(const std::string &process_name, bool wait_for=false)
Attach to a process by name.
bool removeBreakpoint(int32_t breakpoint_id)
Remove a breakpoint.
bool stepInto()
Single step the current thread (step into)
pid_t targetPid() const
Get the PID of the attached process.
std::string targetName() const
Get the name of the attached process.
bool selectThread(uint64_t thread_id)
Select a thread by ID.
std::optional< VariableInfo > readVariable(const std::string &name, uint32_t frame_index=0, uint32_t expand_depth=0) const
Read a variable from the current frame.
const std::string & lastError() const
Get the last error message.
std::vector< VariableInfo > listVariables(uint32_t frame_index=0, bool include_args=true, bool include_locals=true, bool include_statics=false) const
List all variables in scope at a frame.
bool initialize()
Initialize LLDB. Must be called before any other methods.
void shutdown()
Shutdown LLDB and release resources.
bool stop()
Stop the target process.
bool isAttached() const
Check if attached to a process.
std::vector< ThreadInfo > getThreads() const
Get list of all threads.
std::optional< FrameInfo > getFrame(uint32_t frame_index) const
Get a specific frame by index.
std::vector< BreakpointInfo > getBreakpoints() const
Get all breakpoints.
bool resume()
Resume the target process.
std::vector< FrameInfo > getFrames(uint32_t max_frames=0) const
Get stack frames for the selected thread.
bool stepOver()
Step over the current line.
std::optional< BreakpointInfo > getBreakpoint(int32_t breakpoint_id) const
Get information about a specific breakpoint.
bool stepOut()
Step out of the current function.
void detach()
Detach from the current process.
bool attach(pid_t pid)
Attach to a process by PID.
ProcessState state() const
Get the current process state.
std::optional< ThreadInfo > getSelectedThread() const
Get the currently selected thread.
bool waitForBreakpoint(uint32_t timeout_ms=0)
Wait for any breakpoint to be hit.
LLDB process attachment and control wrapper.
ProcessState
Process state enumeration.
@ Exited
Process has exited.
@ Crashed
Process crashed.
@ Detached
Detached from process.
@ Invalid
No valid process attached.
@ Running
Process is running normally.
@ Stopped
Process is stopped (breakpoint, signal, etc.)