summaryrefslogtreecommitdiff
path: root/lldb/tools
diff options
context:
space:
mode:
Diffstat (limited to 'lldb/tools')
-rw-r--r--lldb/tools/CMakeLists.txt3
-rw-r--r--lldb/tools/debugserver/source/MacOSX/MachTask.mm11
-rw-r--r--lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp81
-rw-r--r--lldb/tools/debugserver/source/RNBRemote.cpp12
-rw-r--r--lldb/tools/driver/Options.td417
-rw-r--r--lldb/tools/lldb-dap/DAP.cpp10
-rw-r--r--lldb/tools/lldb-dap/DAP.h10
-rw-r--r--lldb/tools/lldb-dap/EventHelper.cpp10
-rw-r--r--lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp7
-rw-r--r--lldb/tools/lldb-dap/Options.td15
-rw-r--r--lldb/tools/lldb-dap/Protocol/DAPTypes.h15
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp2
-rw-r--r--lldb/tools/lldb-dap/README.md19
-rw-r--r--lldb/tools/lldb-dap/package.json30
-rw-r--r--lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts89
-rw-r--r--lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts5
-rw-r--r--lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts41
-rw-r--r--lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts10
-rw-r--r--lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts20
-rw-r--r--lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts1
-rw-r--r--lldb/tools/lldb-dap/tool/lldb-dap.cpp93
-rw-r--r--lldb/tools/lldb-mcp/CMakeLists.txt34
-rw-r--r--lldb/tools/lldb-mcp/lldb-mcp-Info.plist.in21
-rw-r--r--lldb/tools/lldb-mcp/lldb-mcp.cpp176
-rw-r--r--lldb/tools/yaml2macho-core/CMakeLists.txt14
-rw-r--r--lldb/tools/yaml2macho-core/CoreSpec.cpp165
-rw-r--r--lldb/tools/yaml2macho-core/CoreSpec.h80
-rw-r--r--lldb/tools/yaml2macho-core/LCNoteWriter.cpp97
-rw-r--r--lldb/tools/yaml2macho-core/LCNoteWriter.h33
-rw-r--r--lldb/tools/yaml2macho-core/MemoryWriter.cpp56
-rw-r--r--lldb/tools/yaml2macho-core/MemoryWriter.h26
-rw-r--r--lldb/tools/yaml2macho-core/ThreadWriter.cpp200
-rw-r--r--lldb/tools/yaml2macho-core/ThreadWriter.h24
-rw-r--r--lldb/tools/yaml2macho-core/Utility.cpp22
-rw-r--r--lldb/tools/yaml2macho-core/Utility.h18
-rw-r--r--lldb/tools/yaml2macho-core/yaml2macho.cpp250
36 files changed, 1839 insertions, 278 deletions
diff --git a/lldb/tools/CMakeLists.txt b/lldb/tools/CMakeLists.txt
index e2f039527ad7..4b54c1a50eb2 100644
--- a/lldb/tools/CMakeLists.txt
+++ b/lldb/tools/CMakeLists.txt
@@ -10,6 +10,7 @@ add_subdirectory(lldb-fuzzer EXCLUDE_FROM_ALL)
add_lldb_tool_subdirectory(lldb-instr)
add_lldb_tool_subdirectory(lldb-dap)
+add_lldb_tool_subdirectory(lldb-mcp)
if (LLDB_BUILD_LLDBRPC)
add_lldb_tool_subdirectory(lldb-rpc-gen)
endif()
@@ -27,3 +28,5 @@ endif()
if (LLDB_CAN_USE_LLDB_SERVER)
add_lldb_tool_subdirectory(lldb-server)
endif()
+
+add_lldb_tool_subdirectory(yaml2macho-core)
diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm b/lldb/tools/debugserver/source/MacOSX/MachTask.mm
index fd2ac64ac6cf..8ae9d4df9965 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm
+++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm
@@ -877,6 +877,17 @@ void *MachTask::ExceptionThread(void *arg) {
if (exception_message.CatchExceptionRaise(task)) {
if (exception_message.state.task_port != task) {
if (exception_message.state.IsValid()) {
+ pid_t new_pid = -1;
+ kern_return_t kr =
+ pid_for_task(exception_message.state.task_port, &new_pid);
+ pid_t old_pid = mach_proc->ProcessID();
+ if (kr == KERN_SUCCESS && old_pid != new_pid) {
+ DNBLogError("Got an exec mach message but the pid of "
+ "the new task and the pid of the old task "
+ "do not match, something is wrong.");
+ // exit the thread.
+ break;
+ }
// We exec'ed and our task port changed on us.
DNBLogThreadedIf(LOG_EXCEPTIONS,
"task port changed from 0x%4.4x to 0x%4.4x",
diff --git a/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp b/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
index 6ee1466612ee..e30e02a91152 100644
--- a/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
@@ -199,6 +199,24 @@ uint64_t DNBArchMachARM64::GetSP(uint64_t failValue) {
return failValue;
}
+static void log_signed_registers(arm_thread_state64_t *gpr, const char *desc) {
+ if (DNBLogEnabledForAny(LOG_THREAD)) {
+ const char *log_str = "%s signed regs "
+ "\n fp=%16.16llx"
+ "\n lr=%16.16llx"
+ "\n sp=%16.16llx"
+ "\n pc=%16.16llx";
+#if defined(DEBUGSERVER_IS_ARM64E)
+ DNBLogThreaded(log_str, desc, reinterpret_cast<uint64_t>(gpr->__opaque_fp),
+ reinterpret_cast<uint64_t>(gpr->__opaque_lr),
+ reinterpret_cast<uint64_t>(gpr->__opaque_sp),
+ reinterpret_cast<uint64_t>(gpr->__opaque_pc));
+#else
+ DNBLogThreaded(log_str, desc, gpr->__fp, gpr->__lr, gpr->__sp, gpr->__pc);
+#endif
+ }
+}
+
kern_return_t DNBArchMachARM64::GetGPRState(bool force) {
int set = e_regSetGPR;
// Check if we have valid cached registers
@@ -210,25 +228,29 @@ kern_return_t DNBArchMachARM64::GetGPRState(bool force) {
kern_return_t kret =
::thread_get_state(m_thread->MachPortNumber(), ARM_THREAD_STATE64,
(thread_state_t)&m_state.context.gpr, &count);
- if (DNBLogEnabledForAny(LOG_THREAD)) {
- uint64_t *x = &m_state.context.gpr.__x[0];
+ log_signed_registers(&m_state.context.gpr, "Values from thread_get_state");
- const char *log_str = "thread_get_state signed regs "
- "\n fp=%16.16llx"
- "\n lr=%16.16llx"
- "\n sp=%16.16llx"
- "\n pc=%16.16llx";
-#if defined(DEBUGSERVER_IS_ARM64E)
- DNBLogThreaded(log_str,
- reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp),
- reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_lr),
- reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp),
- reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
-#else
- DNBLogThreaded(log_str, m_state.context.gpr.__fp, m_state.context.gpr.__lr,
- m_state.context.gpr.__sp, m_state.context.gpr.__pc);
-#endif
+#if defined(THREAD_CONVERT_THREAD_STATE_TO_SELF) && defined(__LP64__)
+ if (kret == KERN_SUCCESS) {
+ mach_msg_type_number_t newcount = ARM_THREAD_STATE64_COUNT;
+ arm_thread_state64_t new_gpr;
+ kern_return_t convert_kret = thread_convert_thread_state(
+ m_thread->MachPortNumber(), THREAD_CONVERT_THREAD_STATE_TO_SELF,
+ ARM_THREAD_STATE64, (thread_state_t)&m_state.context.gpr, count,
+ (thread_state_t)&new_gpr, &newcount);
+ DNBLogThreadedIf(
+ LOG_THREAD,
+ "converted register values "
+ "to debugserver's keys, return value %d, old count %d new count %d",
+ convert_kret, count, newcount);
+ if (convert_kret == KERN_SUCCESS)
+ memcpy(&m_state.context.gpr, &new_gpr, count * 4);
+ log_signed_registers(&m_state.context.gpr,
+ "Values after thread_convert_thread_state");
+ }
+#endif // THREAD_CONVERT_THREAD_STATE_TO_SELF
+ if (DNBLogEnabledForAny(LOG_THREAD)) {
#if defined(DEBUGSERVER_IS_ARM64E)
uint64_t log_fp = clear_pac_bits(
reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp));
@@ -244,6 +266,7 @@ kern_return_t DNBArchMachARM64::GetGPRState(bool force) {
uint64_t log_sp = m_state.context.gpr.__sp;
uint64_t log_pc = m_state.context.gpr.__pc;
#endif
+ uint64_t *x = &m_state.context.gpr.__x[0];
DNBLogThreaded(
"thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x (count = %u) regs"
"\n x0=%16.16llx"
@@ -567,10 +590,28 @@ kern_return_t DNBArchMachARM64::GetSMEState(bool force) {
}
kern_return_t DNBArchMachARM64::SetGPRState() {
+ arm_thread_state64_t *state_to_set = &m_state.context.gpr;
+#if defined(THREAD_CONVERT_THREAD_STATE_FROM_SELF) && defined(__LP64__)
+ mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT;
+ mach_msg_type_number_t new_count = ARM_THREAD_STATE64_COUNT;
+ arm_thread_state64_t new_gpr;
+ memcpy(&new_gpr, &m_state.context.gpr, count * 4);
+ kern_return_t convert_kret = thread_convert_thread_state(
+ m_thread->MachPortNumber(), THREAD_CONVERT_THREAD_STATE_FROM_SELF,
+ ARM_THREAD_STATE64, (thread_state_t)&m_state.context.gpr, count,
+ (thread_state_t)&new_gpr, &new_count);
+ if (convert_kret == KERN_SUCCESS)
+ state_to_set = &new_gpr;
+ DNBLogThreadedIf(LOG_THREAD,
+ "converted register values "
+ "to inferior's keys, return value %d, count %d",
+ convert_kret, new_count);
+#endif // THREAD_CONVERT_THREAD_STATE_TO_SELF
+
int set = e_regSetGPR;
- kern_return_t kret = ::thread_set_state(
- m_thread->MachPortNumber(), ARM_THREAD_STATE64,
- (thread_state_t)&m_state.context.gpr, e_regSetGPRCount);
+ kern_return_t kret =
+ ::thread_set_state(m_thread->MachPortNumber(), ARM_THREAD_STATE64,
+ (thread_state_t)state_to_set, e_regSetGPRCount);
m_state.SetError(set, Write,
kret); // Set the current write error for this register set
m_state.InvalidateRegisterSetState(set); // Invalidate the current register
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 102b2ab3e848..d9fb22c6a1c0 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -4423,12 +4423,12 @@ rnb_err_t RNBRemote::HandlePacket_qSpeedTest(const char *p) {
return HandlePacket_ILLFORMED(
__FILE__, __LINE__, p,
"Didn't find response_size value at right offset");
- else if (*end == ';') {
- static char g_data[4 * 1024 * 1024 + 16];
- strcpy(g_data, "data:");
- memset(g_data + 5, 'a', response_size);
- g_data[response_size + 5] = '\0';
- return SendPacket(g_data);
+ else if (*end == ';' && response_size < (4 * 1024 * 1024)) {
+ std::vector<char> buf(response_size + 6, 'a');
+ memcpy(buf.data(), "data:", 5);
+ buf[buf.size() - 1] = '\0';
+ rnb_err_t return_value = SendPacket(buf.data());
+ return return_value;
} else {
return SendErrorPacket("E79");
}
diff --git a/lldb/tools/driver/Options.td b/lldb/tools/driver/Options.td
index 1d8372c4aa40..311e2602cc41 100644
--- a/lldb/tools/driver/Options.td
+++ b/lldb/tools/driver/Options.td
@@ -1,233 +1,248 @@
include "llvm/Option/OptParser.td"
-class F<string name>: Flag<["--", "-"], name>;
-class S<string name>: Separate<["--", "-"], name>;
+class F<string name> : Flag<["--", "-"], name>;
+class S<string name> : Separate<["--", "-"], name>;
class R<list<string> prefixes, string name>
- : Option<prefixes, name, KIND_REMAINING_ARGS>;
+ : Option<prefixes, name, KIND_REMAINING_ARGS>;
// Please keep this in sync with the man page in docs/man/lldb.rst
// Attaching options.
def grp_attach : OptionGroup<"attaching">, HelpText<"ATTACHING">;
-def attach_name: Separate<["--", "-"], "attach-name">,
- MetaVarName<"<name>">,
- HelpText<"Tells the debugger to attach to a process with the given name.">,
- Group<grp_attach>;
-def: Separate<["-"], "n">,
- Alias<attach_name>,
- HelpText<"Alias for --attach-name">,
- Group<grp_attach>;
+def attach_name
+ : Separate<["--", "-"], "attach-name">,
+ MetaVarName<"<name>">,
+ HelpText<
+ "Tells the debugger to attach to a process with the given name.">,
+ Group<grp_attach>;
+def : Separate<["-"], "n">,
+ Alias<attach_name>,
+ HelpText<"Alias for --attach-name">,
+ Group<grp_attach>;
def wait_for
: F<"wait-for">,
HelpText<"Tells the debugger to wait for the process with the name "
"specified by --attach-name to launch before attaching.">,
Group<grp_attach>;
-def: Flag<["-"], "w">,
- Alias<wait_for>,
- HelpText<"Alias for --wait-for">,
- Group<grp_attach>;
-
-def attach_pid: Separate<["--", "-"], "attach-pid">,
- MetaVarName<"<pid>">,
- HelpText<"Tells the debugger to attach to a process with the given pid.">,
- Group<grp_attach>;
-def: Separate<["-"], "p">,
- Alias<attach_pid>,
- HelpText<"Alias for --attach-pid">,
- Group<grp_attach>;
+def : Flag<["-"], "w">,
+ Alias<wait_for>,
+ HelpText<"Alias for --wait-for">,
+ Group<grp_attach>;
+def attach_pid
+ : Separate<["--", "-"], "attach-pid">,
+ MetaVarName<"<pid>">,
+ HelpText<"Tells the debugger to attach to a process with the given pid.">,
+ Group<grp_attach>;
+def : Separate<["-"], "p">,
+ Alias<attach_pid>,
+ HelpText<"Alias for --attach-pid">,
+ Group<grp_attach>;
// Scripting options.
def grp_scripting : OptionGroup<"scripting">, HelpText<"SCRIPTING">;
-def python_path: F<"python-path">,
- HelpText<"Prints out the path to the lldb.py file for this version of lldb.">,
- Group<grp_scripting>;
-def: Flag<["-"], "P">,
- Alias<python_path>,
- HelpText<"Alias for --python-path">,
- Group<grp_scripting>;
-
-def print_script_interpreter_info: F<"print-script-interpreter-info">,
- HelpText<"Prints out a json dictionary with information about the scripting language interpreter.">,
- Group<grp_scripting>;
-
-def script_language: Separate<["--", "-"], "script-language">,
- MetaVarName<"<language>">,
- HelpText<"Tells the debugger to use the specified scripting language for user-defined scripts.">,
- Group<grp_scripting>;
-def: Separate<["-"], "l">,
- Alias<script_language>,
- HelpText<"Alias for --script-language">,
- Group<grp_scripting>;
+def python_path
+ : F<"python-path">,
+ HelpText<
+ "Prints out the path to the lldb.py file for this version of lldb.">,
+ Group<grp_scripting>;
+def : Flag<["-"], "P">,
+ Alias<python_path>,
+ HelpText<"Alias for --python-path">,
+ Group<grp_scripting>;
+
+def print_script_interpreter_info
+ : F<"print-script-interpreter-info">,
+ HelpText<"Prints out a json dictionary with information about the "
+ "scripting language interpreter.">,
+ Group<grp_scripting>;
+
+def script_language : Separate<["--", "-"], "script-language">,
+ MetaVarName<"<language>">,
+ HelpText<"Tells the debugger to use the specified "
+ "scripting language for user-defined scripts.">,
+ Group<grp_scripting>;
+def : Separate<["-"], "l">,
+ Alias<script_language>,
+ HelpText<"Alias for --script-language">,
+ Group<grp_scripting>;
// Repl options.
def grp_repl : OptionGroup<"repl">, HelpText<"REPL">;
-def repl: Flag<["--", "-"], "repl">,
- HelpText<"Runs lldb in REPL mode with a stub process.">,
- Group<grp_repl>;
-def: Flag<["-"], "r">,
- Alias<repl>,
- HelpText<"Alias for --repl">,
- Group<grp_repl>;
-def repl_: Joined<["--", "-"], "repl=">,
- MetaVarName<"<flags>">,
- HelpText<"Runs lldb in REPL mode with a stub process with the given flags.">,
- Group<grp_repl>;
-def: Joined<["-"], "r=">,
- MetaVarName<"<flags>">,
- Alias<repl_>,
- HelpText<"Alias for --repl=<flags>">,
- Group<grp_repl>;
-
-def repl_language: Separate<["--", "-"], "repl-language">,
- MetaVarName<"<language>">,
- HelpText<"Chooses the language for the REPL.">,
- Group<grp_repl>;
-def: Separate<["-"], "R">,
- Alias<repl_language>,
- HelpText<"Alias for --repl-language">,
- Group<grp_repl>;
-
+def repl : Flag<["--", "-"], "repl">,
+ HelpText<"Runs lldb in REPL mode with a stub process.">,
+ Group<grp_repl>;
+def : Flag<["-"], "r">,
+ Alias<repl>,
+ HelpText<"Alias for --repl">,
+ Group<grp_repl>;
+def repl_
+ : Joined<["--", "-"], "repl=">,
+ MetaVarName<"<flags>">,
+ HelpText<
+ "Runs lldb in REPL mode with a stub process with the given flags.">,
+ Group<grp_repl>;
+def : Joined<["-"], "r=">,
+ MetaVarName<"<flags>">,
+ Alias<repl_>,
+ HelpText<"Alias for --repl=<flags>">,
+ Group<grp_repl>;
+
+def repl_language : Separate<["--", "-"], "repl-language">,
+ MetaVarName<"<language>">,
+ HelpText<"Chooses the language for the REPL.">,
+ Group<grp_repl>;
+def : Separate<["-"], "R">,
+ Alias<repl_language>,
+ HelpText<"Alias for --repl-language">,
+ Group<grp_repl>;
// Command options.
def grp_command : OptionGroup<"command">, HelpText<"COMMANDS">;
-def no_lldbinit: F<"no-lldbinit">,
- HelpText<"Do not automatically parse any '.lldbinit' files.">,
- Group<grp_command>;
-def: Flag<["-"], "x">,
- Alias<no_lldbinit>,
- HelpText<"Alias for --no-lldbinit">,
- Group<grp_command>;
-def local_lldbinit: F<"local-lldbinit">,
- HelpText<"Allow the debugger to parse the .lldbinit files in the current working directory, unless --no-lldbinit is passed.">,
- Group<grp_command>;
-
-def batch: F<"batch">,
- HelpText<"Tells the debugger to run the commands from -s, -S, -o & -O, and then quit.">,
- Group<grp_command>;
-def: Flag<["-"], "b">,
- Alias<batch>,
- HelpText<"Alias for --batch">,
- Group<grp_command>;
-
-def source_quietly: F<"source-quietly">,
- HelpText<"Tells the debugger not to echo commands while sourcing files or one-line commands provided on the command line.">,
- Group<grp_command>;
-def: Flag<["-"], "Q">,
- Alias<source_quietly>,
- HelpText<"Alias for --source-quietly">,
- Group<grp_command>;
-
-def one_line_on_crash: Separate<["--", "-"], "one-line-on-crash">,
- MetaVarName<"<command>">,
- HelpText<"When in batch mode, tells the debugger to run this one-line lldb command if the target crashes.">,
- Group<grp_command>;
-def: Separate<["-"], "k">,
- Alias<one_line_on_crash>,
- HelpText<"Alias for --one-line-on-crash">,
- Group<grp_command>;
-
-def source_on_crash: Separate<["--", "-"], "source-on-crash">,
- MetaVarName<"<file>">,
- HelpText<"When in batch mode, tells the debugger to source this file of lldb commands if the target crashes.">,
- Group<grp_command>;
-def: Separate<["-"], "K">,
- Alias<source_on_crash>,
- HelpText<"Alias for --source-on-crash">,
- Group<grp_command>;
-
-def source: Separate<["--", "-"], "source">,
- MetaVarName<"<file>">,
- HelpText<"Tells the debugger to read in and execute the lldb commands in the given file, after any file has been loaded.">,
- Group<grp_command>;
-def: Separate<["-"], "s">,
- Alias<source>,
- HelpText<"Alias for --source">,
- Group<grp_command>;
-
-def source_before_file: Separate<["--", "-"], "source-before-file">,
- MetaVarName<"<file>">,
- HelpText<"Tells the debugger to read in and execute the lldb commands in the given file, before any file has been loaded.">,
- Group<grp_command>;
-def: Separate<["-"], "S">,
- Alias<source_before_file>,
- HelpText<"Alias for --source-before-file">,
- Group<grp_command>;
-
-def one_line: Separate<["--", "-"], "one-line">,
- MetaVarName<"<command>">,
- HelpText<"Tells the debugger to execute this one-line lldb command after any file provided on the command line has been loaded.">,
- Group<grp_command>;
-def: Separate<["-"], "o">,
- Alias<one_line>,
- HelpText<"Alias for --one-line">,
- Group<grp_command>;
-
-def one_line_before_file: Separate<["--", "-"], "one-line-before-file">,
- MetaVarName<"<command>">,
- HelpText<"Tells the debugger to execute this one-line lldb command before any file provided on the command line has been loaded.">,
- Group<grp_command>;
-def: Separate<["-"], "O">,
- Alias<one_line_before_file>,
- HelpText<"Alias for --one-line-before-file">,
- Group<grp_command>;
-
+def no_lldbinit : F<"no-lldbinit">,
+ HelpText<"Do not automatically parse any '.lldbinit' files.">,
+ Group<grp_command>;
+def : Flag<["-"], "x">,
+ Alias<no_lldbinit>,
+ HelpText<"Alias for --no-lldbinit">,
+ Group<grp_command>;
+def local_lldbinit
+ : F<"local-lldbinit">,
+ HelpText<"Allow the debugger to parse the .lldbinit files in the current "
+ "working directory, unless --no-lldbinit is passed.">,
+ Group<grp_command>;
+
+def batch : F<"batch">,
+ HelpText<"Tells the debugger to run the commands from -s, -S, -o & "
+ "-O, and then quit.">,
+ Group<grp_command>;
+def : Flag<["-"], "b">,
+ Alias<batch>,
+ HelpText<"Alias for --batch">,
+ Group<grp_command>;
+
+def source_quietly
+ : F<"source-quietly">,
+ HelpText<"Tells the debugger not to echo commands while sourcing files "
+ "or one-line commands provided on the command line.">,
+ Group<grp_command>;
+def : Flag<["-"], "Q">,
+ Alias<source_quietly>,
+ HelpText<"Alias for --source-quietly">,
+ Group<grp_command>;
+
+def one_line_on_crash
+ : Separate<["--", "-"], "one-line-on-crash">,
+ MetaVarName<"<command>">,
+ HelpText<"When in batch mode, tells the debugger to run this one-line "
+ "lldb command if the target crashes.">,
+ Group<grp_command>;
+def : Separate<["-"], "k">,
+ Alias<one_line_on_crash>,
+ HelpText<"Alias for --one-line-on-crash">,
+ Group<grp_command>;
+
+def source_on_crash
+ : Separate<["--", "-"], "source-on-crash">,
+ MetaVarName<"<file>">,
+ HelpText<"When in batch mode, tells the debugger to source this file of "
+ "lldb commands if the target crashes.">,
+ Group<grp_command>;
+def : Separate<["-"], "K">,
+ Alias<source_on_crash>,
+ HelpText<"Alias for --source-on-crash">,
+ Group<grp_command>;
+
+def source
+ : Separate<["--", "-"], "source">,
+ MetaVarName<"<file>">,
+ HelpText<"Tells the debugger to read in and execute the lldb commands in "
+ "the given file, after any file has been loaded.">,
+ Group<grp_command>;
+def : Separate<["-"], "s">,
+ Alias<source>,
+ HelpText<"Alias for --source">,
+ Group<grp_command>;
+
+def source_before_file
+ : Separate<["--", "-"], "source-before-file">,
+ MetaVarName<"<file>">,
+ HelpText<"Tells the debugger to read in and execute the lldb commands in "
+ "the given file, before any file has been loaded.">,
+ Group<grp_command>;
+def : Separate<["-"], "S">,
+ Alias<source_before_file>,
+ HelpText<"Alias for --source-before-file">,
+ Group<grp_command>;
+
+def one_line
+ : Separate<["--", "-"], "one-line">,
+ MetaVarName<"<command>">,
+ HelpText<"Tells the debugger to execute this one-line lldb command after "
+ "any file provided on the command line has been loaded.">,
+ Group<grp_command>;
+def : Separate<["-"], "o">,
+ Alias<one_line>,
+ HelpText<"Alias for --one-line">,
+ Group<grp_command>;
+
+def one_line_before_file
+ : Separate<["--", "-"], "one-line-before-file">,
+ MetaVarName<"<command>">,
+ HelpText<"Tells the debugger to execute this one-line lldb command "
+ "before any file provided on the command line has been loaded.">,
+ Group<grp_command>;
+def : Separate<["-"], "O">,
+ Alias<one_line_before_file>,
+ HelpText<"Alias for --one-line-before-file">,
+ Group<grp_command>;
// General options.
-def version: F<"version">,
- HelpText<"Prints out the current version number of the LLDB debugger.">;
-def: Flag<["-"], "v">,
- Alias<version>,
- HelpText<"Alias for --version">;
-
-def help: F<"help">,
- HelpText<"Prints out the usage information for the LLDB debugger.">;
-def: Flag<["-"], "h">,
- Alias<help>,
- HelpText<"Alias for --help">;
-
-def core: Separate<["--", "-"], "core">,
- MetaVarName<"<filename>">,
- HelpText<"Tells the debugger to use the full path to <filename> as the core file.">;
-def: Separate<["-"], "c">,
- Alias<core>,
- HelpText<"Alias for --core">;
-
-def editor: F<"editor">,
- HelpText<"Tells the debugger to open source files using the host's \"external editor\" mechanism.">;
-def: Flag<["-"], "e">,
- Alias<editor>,
- HelpText<"Alias for --editor">;
-
-def no_use_colors: F<"no-use-colors">,
- HelpText<"Do not use colors.">;
-def: Flag<["-"], "X">,
- Alias<no_use_colors>,
- HelpText<"Alias for --no-use-color">;
-
-def file: Separate<["--", "-"], "file">,
- MetaVarName<"<filename>">,
- HelpText<"Tells the debugger to use the file <filename> as the program to be debugged.">;
-def: Separate<["-"], "f">,
- Alias<file>,
- HelpText<"Alias for --file">;
-
-def arch: Separate<["--", "-"], "arch">,
- MetaVarName<"<architecture>">,
- HelpText<"Tells the debugger to use the specified architecture when starting and running the program.">;
-def: Separate<["-"], "a">,
- Alias<arch>,
- HelpText<"Alias for --arch">;
-
-def debug: F<"debug">,
- HelpText<"Tells the debugger to print out extra information for debugging itself.">;
-def: Flag<["-"], "d">,
- Alias<debug>,
- HelpText<"Alias for --debug">;
+def version
+ : F<"version">,
+ HelpText<"Prints out the current version number of the LLDB debugger.">;
+def : Flag<["-"], "v">, Alias<version>, HelpText<"Alias for --version">;
+
+def help : F<"help">,
+ HelpText<"Prints out the usage information for the LLDB debugger.">;
+def : Flag<["-"], "h">, Alias<help>, HelpText<"Alias for --help">;
+
+def core : Separate<["--", "-"], "core">,
+ MetaVarName<"<filename>">,
+ HelpText<"Tells the debugger to use the full path to <filename> as "
+ "the core file.">;
+def : Separate<["-"], "c">, Alias<core>, HelpText<"Alias for --core">;
+
+def editor : F<"editor">,
+ HelpText<"Tells the debugger to open source files using the "
+ "host's \"external editor\" mechanism.">;
+def : Flag<["-"], "e">, Alias<editor>, HelpText<"Alias for --editor">;
+
+def no_use_colors : F<"no-use-colors">, HelpText<"Do not use colors.">;
+def : Flag<["-"], "X">,
+ Alias<no_use_colors>,
+ HelpText<"Alias for --no-use-colors">;
+
+def file : Separate<["--", "-"], "file">,
+ MetaVarName<"<filename>">,
+ HelpText<"Tells the debugger to use the file <filename> as the "
+ "program to be debugged.">;
+def : Separate<["-"], "f">, Alias<file>, HelpText<"Alias for --file">;
+
+def arch : Separate<["--", "-"], "arch">,
+ MetaVarName<"<architecture>">,
+ HelpText<"Tells the debugger to use the specified architecture when "
+ "starting and running the program.">;
+def : Separate<["-"], "a">, Alias<arch>, HelpText<"Alias for --arch">;
+
+def debug : F<"debug">,
+ HelpText<"Tells the debugger to print out extra information for "
+ "debugging itself.">;
+def : Flag<["-"], "d">, Alias<debug>, HelpText<"Alias for --debug">;
def REM : R<["--"], "">;
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index b1ad38d98389..f76656e98ca0 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -121,12 +121,13 @@ static std::string capitalize(llvm::StringRef str) {
llvm::StringRef DAP::debug_adapter_path = "";
DAP::DAP(Log *log, const ReplMode default_repl_mode,
- std::vector<std::string> pre_init_commands,
+ std::vector<std::string> pre_init_commands, bool no_lldbinit,
llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop)
: log(log), transport(transport), broadcaster("lldb-dap"),
progress_event_reporter(
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
- repl_mode(default_repl_mode), m_client_name(client_name), m_loop(loop) {
+ repl_mode(default_repl_mode), no_lldbinit(no_lldbinit),
+ m_client_name(client_name), m_loop(loop) {
configuration.preInitCommands = std::move(pre_init_commands);
RegisterRequests();
}
@@ -1068,6 +1069,11 @@ llvm::Error DAP::Loop() {
out.Stop();
err.Stop();
StopEventHandlers();
+
+ // Destroy the debugger when the session ends. This will trigger the
+ // debugger's destroy callbacks for earlier logging and clean-ups, rather
+ // than waiting for the termination of the lldb-dap process.
+ lldb::SBDebugger::Destroy(debugger);
});
while (true) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 04f70f76a09c..71681fd4b51e 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -156,6 +156,9 @@ struct DAP final : private DAPTransport::MessageHandler {
/// The set of features supported by the connected client.
llvm::DenseSet<ClientFeature> clientFeatures;
+ /// Whether to disable sourcing .lldbinit files.
+ bool no_lldbinit;
+
/// The initial thread list upon attaching.
std::vector<protocol::Thread> initial_thread_list;
@@ -178,13 +181,16 @@ struct DAP final : private DAPTransport::MessageHandler {
/// \param[in] pre_init_commands
/// LLDB commands to execute as soon as the debugger instance is
/// allocated.
+ /// \param[in] no_lldbinit
+ /// Whether to disable sourcing .lldbinit files.
/// \param[in] transport
/// Transport for this debug session.
/// \param[in] loop
/// Main loop associated with this instance.
DAP(Log *log, const ReplMode default_repl_mode,
- std::vector<std::string> pre_init_commands, llvm::StringRef client_name,
- DAPTransport &transport, lldb_private::MainLoop &loop);
+ std::vector<std::string> pre_init_commands, bool no_lldbinit,
+ llvm::StringRef client_name, DAPTransport &transport,
+ lldb_private::MainLoop &loop);
~DAP();
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index bfb05a387d04..ecd630cb530d 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -230,15 +230,7 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) {
// Send a "terminated" event to indicate the process is done being
// debugged.
-void SendTerminatedEvent(DAP &dap) {
- // Prevent races if the process exits while we're being asked to disconnect.
- llvm::call_once(dap.terminated_event_flag, [&] {
- dap.RunTerminateCommands();
- // Send a "terminated" event
- llvm::json::Object event(CreateTerminatedEventObject(dap.target));
- dap.SendJSON(llvm::json::Value(std::move(event)));
- });
-}
+void SendTerminatedEvent(DAP &dap) { dap.SendTerminatedEvent(); }
// Grab any STDOUT and STDERR from the process and send it up to VS Code
// via an "output" event to the "stdout" and "stderr" categories.
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index b499a69876e2..9069de4a3a69 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -42,8 +42,11 @@ llvm::Expected<InitializeResponse> InitializeRequestHandler::Run(
// The sourceInitFile option is not part of the DAP specification. It is an
// extension used by the test suite to prevent sourcing `.lldbinit` and
- // changing its behavior.
- if (arguments.lldbExtSourceInitFile.value_or(true)) {
+ // changing its behavior. The CLI flag --no-lldbinit takes precedence over
+ // the DAP parameter.
+ bool should_source_init_files =
+ !dap.no_lldbinit && arguments.lldbExtSourceInitFile.value_or(true);
+ if (should_source_init_files) {
dap.debugger.SkipLLDBInitFiles(false);
dap.debugger.SkipAppInitFiles(false);
lldb::SBCommandReturnObject init;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index 867753e9294a..c8492c6a62b2 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -61,3 +61,18 @@ def pre_init_command: S<"pre-init-command">,
def: Separate<["-"], "c">,
Alias<pre_init_command>,
HelpText<"Alias for --pre-init-command">;
+
+def no_lldbinit: F<"no-lldbinit">,
+ HelpText<"Do not automatically parse any '.lldbinit' files.">;
+def: Flag<["-"], "x">,
+ Alias<no_lldbinit>,
+ HelpText<"Alias for --no-lldbinit">;
+
+def connection_timeout: S<"connection-timeout">,
+ MetaVarName<"<timeout>">,
+ HelpText<"When using --connection, the number of seconds to wait for new "
+ "connections after the server has started and after all clients have "
+ "disconnected. Each new connection will reset the timeout. When the "
+ "timeout is reached, the server will be closed and the process will exit. "
+ "Not specifying this argument or specifying non-positive values will "
+ "cause the server to wait for new connections indefinitely.">;
diff --git a/lldb/tools/lldb-dap/Protocol/DAPTypes.h b/lldb/tools/lldb-dap/Protocol/DAPTypes.h
index 7fccf1359a73..23ba93946bde 100644
--- a/lldb/tools/lldb-dap/Protocol/DAPTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/DAPTypes.h
@@ -16,6 +16,7 @@
#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_DAP_TYPES_H
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_DAP_TYPES_H
+#include "lldb/lldb-defines.h"
#include "lldb/lldb-types.h"
#include "llvm/Support/JSON.h"
#include <optional>
@@ -50,29 +51,29 @@ llvm::json::Value toJSON(const SourceLLDBData &);
struct Symbol {
/// The symbol id, usually the original symbol table index.
- uint32_t id;
+ uint32_t id = 0;
/// True if this symbol is debug information in a symbol.
- bool isDebug;
+ bool isDebug = false;
/// True if this symbol is not actually in the symbol table, but synthesized
/// from other info in the object file.
- bool isSynthetic;
+ bool isSynthetic = false;
/// True if this symbol is globally visible.
- bool isExternal;
+ bool isExternal = false;
/// The symbol type.
- lldb::SymbolType type;
+ lldb::SymbolType type = lldb::eSymbolTypeInvalid;
/// The symbol file address.
- lldb::addr_t fileAddress;
+ lldb::addr_t fileAddress = LLDB_INVALID_ADDRESS;
/// The symbol load address.
std::optional<lldb::addr_t> loadAddress;
/// The symbol size.
- lldb::addr_t size;
+ lldb::addr_t size = 0;
/// The symbol name.
std::string name;
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index eab7211e1897..e1806d6230a8 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -219,7 +219,7 @@ bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA,
OM.map("clientName", IRA.clientName) && OM.map("locale", IRA.locale) &&
OM.map("linesStartAt1", IRA.linesStartAt1) &&
OM.map("columnsStartAt1", IRA.columnsStartAt1) &&
- OM.map("pathFormat", IRA.pathFormat) &&
+ OM.mapOptional("pathFormat", IRA.pathFormat) &&
OM.map("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile);
}
diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index f88f3ced6f25..39dabcc1342c 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -275,9 +275,9 @@ User settings can set the default value for the following supported
| **exitCommands** | [string] | `[]` |
| **terminateCommands** | [string] | `[]` |
-To adjust your settings, open the Settings editor via the
-`File > Preferences > Settings` menu or press `Ctrl+`, on Windows/Linux and
-`Cmd+`, on Mac.
+To adjust your settings, open the Settings editor
+via the `File > Preferences > Settings` menu or press `Ctrl+,` on Windows/Linux,
+and the `VS Code > Settings... > Settings` menu or press `Cmd+,` on Mac.
## Debug Console
@@ -372,6 +372,19 @@ for more details on Debug Adapter Protocol events and the VS Code
[debug.onDidReceiveDebugSessionCustomEvent](https://code.visualstudio.com/api/references/vscode-api#debug.onDidReceiveDebugSessionCustomEvent)
API for handling a custom event from an extension.
+## Server Mode
+
+lldb-dap supports a server mode that can be enabled via the following user settings.
+
+| Setting | Type | Default | |
+| -------------------------- | -------- | :-----: | --------- |
+| **Server Mode** | string | `False` | Run lldb-dap in server mode. When enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance.
+| **Connection Timeout** | number | `0` | When running lldb-dap in server mode, the time in seconds to wait for new connections after the server has started and after all clients have disconnected. Each new connection will reset the timeout. When the timeout is reached, the server will be closed and the process will exit. Specifying non-positive values will cause the server to wait for new connections indefinitely.
+
+To adjust your settings, open the Settings editor
+via the `File > Preferences > Settings` menu or press `Ctrl+,` on Windows/Linux,
+and the `VS Code > Settings... > Settings` menu or press `Cmd+,` on Mac.
+
## Contributing
`lldb-dap` and `lldb` are developed under the umbrella of the [LLVM project](https://llvm.org/).
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index f11b64aa72ae..9cc653cee405 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -106,6 +106,13 @@
"markdownDescription": "Run lldb-dap in server mode.\n\nWhen enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance.",
"default": false
},
+ "lldb-dap.connectionTimeout": {
+ "order": 0,
+ "scope": "resource",
+ "type": "number",
+ "markdownDescription": "When running lldb-dap in server mode, the time in seconds to wait for new connections after the server has started and after all clients have disconnected. Each new connection will reset the timeout. When the timeout is reached, the server will be closed and the process will exit. Specifying non-positive values will cause the server to wait for new connections indefinitely.",
+ "default": 0
+ },
"lldb-dap.arguments": {
"scope": "resource",
"type": "array",
@@ -398,6 +405,29 @@
},
"markdownDescription": "The list of additional arguments used to launch the debug adapter executable. Overrides any user or workspace settings."
},
+ "debugAdapterEnv": {
+ "anyOf": [
+ {
+ "type": "object",
+ "markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `{ \"FOO\": \"1\" }`",
+ "patternProperties": {
+ ".*": {
+ "type": "string"
+ }
+ },
+ "default": {}
+ },
+ {
+ "type": "array",
+ "markdownDescription": "Additional environment variables to set when launching the debug adapter executable. E.g. `[\"FOO=1\", \"BAR\"]`",
+ "items": {
+ "type": "string",
+ "pattern": "^((\\w+=.*)|^\\w+)$"
+ },
+ "default": []
+ }
+ ]
+ },
"program": {
"type": "string",
"description": "Path to the program to debug."
diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 157aa2ac76a1..f7e92ee95ca3 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -69,6 +69,40 @@ async function findDAPExecutable(): Promise<string | undefined> {
}
/**
+ * Validates the DAP environment provided in the debug configuration.
+ * It must be a dictionary of string keys and values OR an array of string values.
+ *
+ * @param debugConfigEnv The supposed DAP environment that will be validated
+ * @returns Whether or not the DAP environment is valid
+ */
+function validateDAPEnv(debugConfigEnv: any): boolean {
+ // If the env is an object, it should have string values.
+ // The keys are guaranteed to be strings.
+ if (
+ typeof debugConfigEnv === "object" &&
+ Object.values(debugConfigEnv).findIndex(
+ (entry) => typeof entry !== "string",
+ ) !== -1
+ ) {
+ return false;
+ }
+
+ // If the env is an array, it should have string values which match the regex.
+ if (
+ Array.isArray(debugConfigEnv) &&
+ debugConfigEnv.findIndex(
+ (entry) =>
+ typeof entry !== "string" || !/^((\\w+=.*)|^\\w+)$/.test(entry),
+ ) !== -1
+ ) {
+ return false;
+ }
+
+ // The env is valid.
+ return true;
+}
+
+/**
* Retrieves the lldb-dap executable path either from settings or the provided
* {@link vscode.DebugConfiguration}.
*
@@ -158,6 +192,51 @@ async function getDAPArguments(
}
/**
+ * Retrieves the environment that will be provided to lldb-dap either from settings or the provided
+ * {@link vscode.DebugConfiguration}.
+ *
+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
+ * @throws An {@link ErrorWithNotification} if something went wrong
+ * @returns The environment that will be provided to lldb-dap
+ */
+async function getDAPEnvironment(
+ workspaceFolder: vscode.WorkspaceFolder | undefined,
+ configuration: vscode.DebugConfiguration,
+): Promise<{ [key: string]: string }> {
+ const debugConfigEnv = configuration.debugAdapterEnv;
+ if (debugConfigEnv) {
+ if (validateDAPEnv(debugConfigEnv) === false) {
+ throw new ErrorWithNotification(
+ "The debugAdapterEnv property must be a dictionary of string keys and values OR an array of string values. Please update your launch configuration",
+ new ConfigureButton(),
+ );
+ }
+
+ // Transform, so that the returned value is always a dictionary.
+ if (Array.isArray(debugConfigEnv)) {
+ const ret: { [key: string]: string } = {};
+ for (const envVar of debugConfigEnv as string[]) {
+ const equalSignPos = envVar.search("=");
+ if (equalSignPos >= 0) {
+ ret[envVar.substr(0, equalSignPos)] = envVar.substr(equalSignPos + 1);
+ } else {
+ ret[envVar] = "";
+ }
+ }
+ return ret;
+ } else {
+ return debugConfigEnv;
+ }
+ }
+
+ const config = vscode.workspace.workspaceFile
+ ? vscode.workspace.getConfiguration("lldb-dap")
+ : vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
+ return config.get<{ [key: string]: string }>("environment") || {};
+}
+
+/**
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
*
@@ -182,12 +261,16 @@ export async function createDebugAdapterExecutable(
if (log_path) {
env["LLDBDAP_LOG"] = log_path;
} else if (
- vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false)
+ vscode.workspace
+ .getConfiguration("lldb-dap")
+ .get("captureSessionLogs", false)
) {
env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);
}
- const configEnvironment =
- config.get<{ [key: string]: string }>("environment") || {};
+ const configEnvironment = await getDAPEnvironment(
+ workspaceFolder,
+ configuration,
+ );
const dapPath = await getDAPExecutable(workspaceFolder, configuration);
const dbgOptions = {
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index 1ae87116141f..d35460ab68f0 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -207,10 +207,15 @@ export class LLDBDapConfigurationProvider
config.get<boolean>("serverMode", false) &&
(await isServerModeSupported(executable.command))
) {
+ const connectionTimeoutSeconds = config.get<number | undefined>(
+ "connectionTimeout",
+ undefined,
+ );
const serverInfo = await this.server.start(
executable.command,
executable.args,
executable.options,
+ connectionTimeoutSeconds,
);
if (!serverInfo) {
return undefined;
diff --git a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
index 5f9d8efdcb3a..774be50053a1 100644
--- a/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
+++ b/lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
@@ -11,6 +11,7 @@ import * as vscode from "vscode";
export class LLDBDapServer implements vscode.Disposable {
private serverProcess?: child_process.ChildProcessWithoutNullStreams;
private serverInfo?: Promise<{ host: string; port: number }>;
+ private serverSpawnInfo?: string[];
constructor() {
vscode.commands.registerCommand(
@@ -32,9 +33,20 @@ export class LLDBDapServer implements vscode.Disposable {
dapPath: string,
args: string[],
options?: child_process.SpawnOptionsWithoutStdio,
+ connectionTimeoutSeconds?: number,
): Promise<{ host: string; port: number } | undefined> {
- const dapArgs = [...args, "--connection", "listen://localhost:0"];
- if (!(await this.shouldContinueStartup(dapPath, dapArgs))) {
+ // Both the --connection and --connection-timeout arguments are subject to the shouldContinueStartup() check.
+ const connectionTimeoutArgs =
+ connectionTimeoutSeconds && connectionTimeoutSeconds > 0
+ ? ["--connection-timeout", `${connectionTimeoutSeconds}`]
+ : [];
+ const dapArgs = [
+ ...args,
+ "--connection",
+ "listen://localhost:0",
+ ...connectionTimeoutArgs,
+ ];
+ if (!(await this.shouldContinueStartup(dapPath, dapArgs, options?.env))) {
return undefined;
}
@@ -70,6 +82,7 @@ export class LLDBDapServer implements vscode.Disposable {
}
});
this.serverProcess = process;
+ this.serverSpawnInfo = this.getSpawnInfo(dapPath, dapArgs, options?.env);
});
return this.serverInfo;
}
@@ -85,12 +98,14 @@ export class LLDBDapServer implements vscode.Disposable {
private async shouldContinueStartup(
dapPath: string,
args: string[],
+ env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
): Promise<boolean> {
- if (!this.serverProcess || !this.serverInfo) {
+ if (!this.serverProcess || !this.serverInfo || !this.serverSpawnInfo) {
return true;
}
- if (isDeepStrictEqual(this.serverProcess.spawnargs, [dapPath, ...args])) {
+ const newSpawnInfo = this.getSpawnInfo(dapPath, args, env);
+ if (isDeepStrictEqual(this.serverSpawnInfo, newSpawnInfo)) {
return true;
}
@@ -102,11 +117,11 @@ export class LLDBDapServer implements vscode.Disposable {
The previous lldb-dap server was started with:
-${this.serverProcess.spawnargs.join(" ")}
+${this.serverSpawnInfo.join(" ")}
The new lldb-dap server will be started with:
-${dapPath} ${args.join(" ")}
+${newSpawnInfo.join(" ")}
Restarting the server will interrupt any existing debug sessions and start a new server.`,
},
@@ -143,4 +158,18 @@ Restarting the server will interrupt any existing debug sessions and start a new
this.serverInfo = undefined;
}
}
+
+ getSpawnInfo(
+ path: string,
+ args: string[],
+ env: NodeJS.ProcessEnv | { [key: string]: string } | undefined,
+ ): string[] {
+ return [
+ path,
+ ...args,
+ ...Object.entries(env ?? {}).map(
+ (entry) => String(entry[0]) + "=" + String(entry[1]),
+ ),
+ ];
+ }
}
diff --git a/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts b/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts
index 84b9387ffe49..951a5971e0bc 100644
--- a/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/ui/symbols-provider.ts
@@ -61,18 +61,18 @@ export class SymbolsProvider extends DisposableContext {
return;
}
- this.showSymbolsForModule(session, selectedModule.module);
+ await this.showSymbolsForModule(session, selectedModule.module);
}
private async showSymbolsForModule(session: vscode.DebugSession, module: DebugProtocol.Module) {
try {
const symbols = await this.getSymbolsForModule(session, module.id.toString());
- this.showSymbolsInNewTab(module.name.toString(), symbols);
+ await this.showSymbolsInNewTab(module.name.toString(), symbols);
} catch (error) {
if (error instanceof Error) {
- vscode.window.showErrorMessage("Failed to retrieve symbols: " + error.message);
+ await vscode.window.showErrorMessage("Failed to retrieve symbols: " + error.message);
} else {
- vscode.window.showErrorMessage("Failed to retrieve symbols due to an unknown error.");
+ await vscode.window.showErrorMessage("Failed to retrieve symbols due to an unknown error.");
}
return;
@@ -106,7 +106,7 @@ export class SymbolsProvider extends DisposableContext {
const symbolsTableScriptPath = panel.webview.asWebviewUri(vscode.Uri.joinPath(this.getExtensionResourcePath(), "symbols-table-view.js"));
panel.webview.html = getSymbolsTableHTMLContent(tabulatorJsPath, tabulatorCssPath, symbolsTableScriptPath);
- panel.webview.postMessage({ command: "updateSymbols", symbols: symbols });
+ await panel.webview.postMessage({ command: "updateSymbols", symbols: symbols });
}
private getExtensionResourcePath(): vscode.Uri {
diff --git a/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts b/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts
index 88e24f310878..c00e0d462569 100644
--- a/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts
+++ b/lldb/tools/lldb-dap/src-ts/ui/symbols-webview-html.ts
@@ -12,8 +12,13 @@ export function getSymbolsTableHTMLContent(tabulatorJsPath: vscode.Uri, tabulato
color: var(--vscode-editor-foreground);
}
+ .tabulator .tabulator-header {
+ background-color: var(--vscode-tree-tableOddRowsBackground);
+ color: var(--vscode-editor-foreground);
+ }
+
.tabulator .tabulator-header .tabulator-col {
- background-color: var(--vscode-editor-background);
+ background-color: var(--vscode-tree-tableOddRowsBackground);
color: var(--vscode-editor-foreground);
}
@@ -23,11 +28,22 @@ export function getSymbolsTableHTMLContent(tabulatorJsPath: vscode.Uri, tabulato
}
.tabulator-row.tabulator-row-even {
+ background-color: var(--vscode-tree-tableOddRowsBackground);
+ }
+
+ @media (hover:hover) and (pointer:fine){
+ .tabulator-row:hover {
+ background-color: var(--vscode-list-hoverBackground);
+ color: var(--vscode-list-hoverForeground);
+ }
+ }
+
+ .tabulator-row.tabulator-selected {
background-color: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
}
- .tabulator-row.tabulator-selected {
+ .tabulator .tabulator-tableholder .tabulator-table {
background-color: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
}
diff --git a/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts b/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts
index 8454378abef1..9d346818e384 100644
--- a/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts
+++ b/lldb/tools/lldb-dap/src-ts/webview/symbols-table-view.ts
@@ -95,6 +95,7 @@ const SYMBOLS_TABLE = new Tabulator("#symbols-table", {
height: "100vh",
columns: SYMBOL_TABLE_COLUMNS,
layout: "fitColumns",
+ selectableRows: false,
data: previousState?.symbols || [],
});
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index b74085f25f4e..93446c051eb5 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -223,6 +223,35 @@ static int DuplicateFileDescriptor(int fd) {
#endif
}
+static void
+ResetConnectionTimeout(std::mutex &connection_timeout_mutex,
+ MainLoopBase::TimePoint &conncetion_timeout_time_point) {
+ std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
+ conncetion_timeout_time_point = MainLoopBase::TimePoint();
+}
+
+static void
+TrackConnectionTimeout(MainLoop &loop, std::mutex &connection_timeout_mutex,
+ MainLoopBase::TimePoint &conncetion_timeout_time_point,
+ std::chrono::seconds ttl_seconds) {
+ MainLoopBase::TimePoint next_checkpoint =
+ std::chrono::steady_clock::now() + std::chrono::seconds(ttl_seconds);
+ {
+ std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
+ // We don't need to take the max of `ttl_time_point` and `next_checkpoint`,
+ // because `next_checkpoint` must be the latest.
+ conncetion_timeout_time_point = next_checkpoint;
+ }
+ loop.AddCallback(
+ [&connection_timeout_mutex, &conncetion_timeout_time_point,
+ next_checkpoint](MainLoopBase &loop) {
+ std::scoped_lock<std::mutex> lock(connection_timeout_mutex);
+ if (conncetion_timeout_time_point == next_checkpoint)
+ loop.RequestTermination();
+ },
+ next_checkpoint);
+}
+
static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
validateConnection(llvm::StringRef conn) {
auto uri = lldb_private::URI::Parse(conn);
@@ -255,10 +284,11 @@ validateConnection(llvm::StringRef conn) {
return make_error();
}
-static llvm::Error
-serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
- Log *log, const ReplMode default_repl_mode,
- const std::vector<std::string> &pre_init_commands) {
+static llvm::Error serveConnection(
+ const Socket::SocketProtocol &protocol, const std::string &name, Log *log,
+ const ReplMode default_repl_mode,
+ const std::vector<std::string> &pre_init_commands, bool no_lldbinit,
+ std::optional<std::chrono::seconds> connection_timeout_seconds) {
Status status;
static std::unique_ptr<Socket> listener = Socket::Create(protocol, status);
if (status.Fail()) {
@@ -283,6 +313,12 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
g_loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
});
+ static MainLoopBase::TimePoint g_connection_timeout_time_point;
+ static std::mutex g_connection_timeout_mutex;
+ if (connection_timeout_seconds)
+ TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,
+ g_connection_timeout_time_point,
+ connection_timeout_seconds.value());
std::condition_variable dap_sessions_condition;
std::mutex dap_sessions_mutex;
std::map<MainLoop *, DAP *> dap_sessions;
@@ -291,6 +327,11 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
&dap_sessions_mutex, &dap_sessions,
&clientCount](
std::unique_ptr<Socket> sock) {
+ // Reset the keep alive timer, because we won't be killing the server
+ // while this connection is being served.
+ if (connection_timeout_seconds)
+ ResetConnectionTimeout(g_connection_timeout_mutex,
+ g_connection_timeout_time_point);
std::string client_name = llvm::formatv("client_{0}", clientCount++).str();
DAP_LOG(log, "({0}) client connected", client_name);
@@ -303,8 +344,8 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
llvm::set_thread_name(client_name + ".runloop");
MainLoop loop;
Transport transport(client_name, log, io, io);
- DAP dap(log, default_repl_mode, pre_init_commands, client_name, transport,
- loop);
+ DAP dap(log, default_repl_mode, pre_init_commands, no_lldbinit,
+ client_name, transport, loop);
if (auto Err = dap.ConfigureIO()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
@@ -327,6 +368,12 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name,
std::unique_lock<std::mutex> lock(dap_sessions_mutex);
dap_sessions.erase(&loop);
std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock));
+
+ // Start the countdown to kill the server at the end of each connection.
+ if (connection_timeout_seconds)
+ TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,
+ g_connection_timeout_time_point,
+ connection_timeout_seconds.value());
});
client.detach();
});
@@ -456,6 +503,31 @@ int main(int argc, char *argv[]) {
connection.assign(path);
}
+ std::optional<std::chrono::seconds> connection_timeout_seconds;
+ if (llvm::opt::Arg *connection_timeout_arg =
+ input_args.getLastArg(OPT_connection_timeout)) {
+ if (!connection.empty()) {
+ llvm::StringRef connection_timeout_string_value =
+ connection_timeout_arg->getValue();
+ int connection_timeout_int_value;
+ if (connection_timeout_string_value.getAsInteger(
+ 10, connection_timeout_int_value)) {
+ llvm::errs() << "'" << connection_timeout_string_value
+ << "' is not a valid connection timeout value\n";
+ return EXIT_FAILURE;
+ }
+ // Ignore non-positive values.
+ if (connection_timeout_int_value > 0)
+ connection_timeout_seconds =
+ std::chrono::seconds(connection_timeout_int_value);
+ } else {
+ llvm::errs()
+ << "\"--connection-timeout\" requires \"--connection\" to be "
+ "specified\n";
+ return EXIT_FAILURE;
+ }
+ }
+
#if !defined(_WIN32)
if (input_args.hasArg(OPT_wait_for_debugger)) {
printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid());
@@ -508,6 +580,8 @@ int main(int argc, char *argv[]) {
pre_init_commands.push_back(arg);
}
+ bool no_lldbinit = input_args.hasArg(OPT_no_lldbinit);
+
if (!connection.empty()) {
auto maybeProtoclAndName = validateConnection(connection);
if (auto Err = maybeProtoclAndName.takeError()) {
@@ -520,7 +594,8 @@ int main(int argc, char *argv[]) {
std::string name;
std::tie(protocol, name) = *maybeProtoclAndName;
if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode,
- pre_init_commands)) {
+ pre_init_commands, no_lldbinit,
+ connection_timeout_seconds)) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"Connection failed: ");
return EXIT_FAILURE;
@@ -556,8 +631,8 @@ int main(int argc, char *argv[]) {
constexpr llvm::StringLiteral client_name = "stdio";
MainLoop loop;
Transport transport(client_name, log.get(), input, output);
- DAP dap(log.get(), default_repl_mode, pre_init_commands, client_name,
- transport, loop);
+ DAP dap(log.get(), default_repl_mode, pre_init_commands, no_lldbinit,
+ client_name, transport, loop);
// stdout/stderr redirection to the IDE's console
if (auto Err = dap.ConfigureIO(stdout, stderr)) {
diff --git a/lldb/tools/lldb-mcp/CMakeLists.txt b/lldb/tools/lldb-mcp/CMakeLists.txt
new file mode 100644
index 000000000000..5f61a1993cea
--- /dev/null
+++ b/lldb/tools/lldb-mcp/CMakeLists.txt
@@ -0,0 +1,34 @@
+add_lldb_tool(lldb-mcp
+ lldb-mcp.cpp
+
+ LINK_COMPONENTS
+ Option
+ Support
+ LINK_LIBS
+ liblldb
+ lldbInitialization
+ lldbHost
+ lldbProtocolMCP
+ )
+
+if(APPLE)
+ configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/lldb-mcp-Info.plist.in
+ ${CMAKE_CURRENT_BINARY_DIR}/lldb-mcp-Info.plist
+ )
+ target_link_options(lldb-mcp
+ PRIVATE LINKER:-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_BINARY_DIR}/lldb-mcp-Info.plist)
+endif()
+
+if(LLDB_BUILD_FRAMEWORK)
+ # In the build-tree, we know the exact path to the framework directory.
+ # The installed framework can be in different locations.
+ lldb_setup_rpaths(lldb-mcp
+ BUILD_RPATH
+ "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}"
+ INSTALL_RPATH
+ "@loader_path/../../../SharedFrameworks"
+ "@loader_path/../../System/Library/PrivateFrameworks"
+ "@loader_path/../../Library/PrivateFrameworks"
+ )
+endif()
diff --git a/lldb/tools/lldb-mcp/lldb-mcp-Info.plist.in b/lldb/tools/lldb-mcp/lldb-mcp-Info.plist.in
new file mode 100644
index 000000000000..4dc3ddd91280
--- /dev/null
+++ b/lldb/tools/lldb-mcp/lldb-mcp-Info.plist.in
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.apple.lldb-mcp</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>lldb-mcp</string>
+ <key>CFBundleVersion</key>
+ <string>${LLDB_VERSION}</string>
+ <key>SecTaskAccess</key>
+ <array>
+ <string>allowed</string>
+ <string>debug</string>
+ </array>
+</dict>
+</plist>
diff --git a/lldb/tools/lldb-mcp/lldb-mcp.cpp b/lldb/tools/lldb-mcp/lldb-mcp.cpp
new file mode 100644
index 000000000000..12545dcf3a3c
--- /dev/null
+++ b/lldb/tools/lldb-mcp/lldb-mcp.cpp
@@ -0,0 +1,176 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+#include "lldb/Host/File.h"
+#include "lldb/Host/MainLoop.h"
+#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Initialization/SystemInitializerCommon.h"
+#include "lldb/Initialization/SystemLifetimeManager.h"
+#include "lldb/Protocol/MCP/Server.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/Utility/UriParser.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/ManagedStatic.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/WithColor.h"
+#include <cstdlib>
+#include <memory>
+
+#if defined(_WIN32)
+#include <fcntl.h>
+#endif
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_protocol::mcp;
+
+using lldb_private::File;
+using lldb_private::MainLoop;
+using lldb_private::MainLoopBase;
+using lldb_private::NativeFile;
+
+namespace {
+
+inline void exitWithError(llvm::Error Err, StringRef Prefix = "") {
+ handleAllErrors(std::move(Err), [&](ErrorInfoBase &Info) {
+ WithColor::error(errs(), Prefix) << Info.message() << '\n';
+ });
+ std::exit(EXIT_FAILURE);
+}
+
+constexpr size_t kForwardIOBufferSize = 1024;
+
+void forwardIO(lldb_private::MainLoopBase &loop, lldb::IOObjectSP &from,
+ lldb::IOObjectSP &to) {
+ char buf[kForwardIOBufferSize];
+ size_t num_bytes = sizeof(buf);
+
+ if (llvm::Error err = from->Read(buf, num_bytes).takeError())
+ exitWithError(std::move(err));
+
+ // EOF reached.
+ if (num_bytes == 0)
+ return loop.RequestTermination();
+
+ if (llvm::Error err = to->Write(buf, num_bytes).takeError())
+ exitWithError(std::move(err));
+}
+
+void connectAndForwardIO(lldb_private::MainLoop &loop, ServerInfo &info,
+ IOObjectSP &input_sp, IOObjectSP &output_sp) {
+ auto uri = lldb_private::URI::Parse(info.connection_uri);
+ if (!uri)
+ exitWithError(createStringError("invalid connection_uri"));
+
+ std::optional<lldb_private::Socket::ProtocolModePair> protocol_and_mode =
+ lldb_private::Socket::GetProtocolAndMode(uri->scheme);
+
+ lldb_private::Status status;
+ std::unique_ptr<lldb_private::Socket> sock =
+ lldb_private::Socket::Create(protocol_and_mode->first, status);
+
+ if (status.Fail())
+ exitWithError(status.takeError());
+
+ if (uri->port && !uri->hostname.empty())
+ status = sock->Connect(
+ llvm::formatv("[{0}]:{1}", uri->hostname, *uri->port).str());
+ else
+ status = sock->Connect(uri->path);
+ if (status.Fail())
+ exitWithError(status.takeError());
+
+ IOObjectSP sock_sp = std::move(sock);
+ auto input_handle = loop.RegisterReadObject(
+ input_sp, std::bind(forwardIO, std::placeholders::_1, input_sp, sock_sp),
+ status);
+ if (status.Fail())
+ exitWithError(status.takeError());
+
+ auto socket_handle = loop.RegisterReadObject(
+ sock_sp, std::bind(forwardIO, std::placeholders::_1, sock_sp, output_sp),
+ status);
+ if (status.Fail())
+ exitWithError(status.takeError());
+
+ status = loop.Run();
+ if (status.Fail())
+ exitWithError(status.takeError());
+}
+
+llvm::ManagedStatic<lldb_private::SystemLifetimeManager> g_debugger_lifetime;
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
+#if !defined(__APPLE__)
+ llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
+ " and include the crash backtrace.\n");
+#else
+ llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL
+ " and include the crash report from "
+ "~/Library/Logs/DiagnosticReports/.\n");
+#endif
+
+#if defined(_WIN32)
+ // Windows opens stdout and stdin in text mode which converts \n to 13,10
+ // while the value is just 10 on Darwin/Linux. Setting the file mode to
+ // binary fixes this.
+ int result = _setmode(fileno(stdout), _O_BINARY);
+ assert(result);
+ result = _setmode(fileno(stdin), _O_BINARY);
+ UNUSED_IF_ASSERT_DISABLED(result);
+ assert(result);
+#endif
+
+ if (llvm::Error err = g_debugger_lifetime->Initialize(
+ std::make_unique<lldb_private::SystemInitializerCommon>(nullptr)))
+ exitWithError(std::move(err));
+
+ auto cleanup = make_scope_exit([] { g_debugger_lifetime->Terminate(); });
+
+ IOObjectSP input_sp = std::make_shared<NativeFile>(
+ fileno(stdin), File::eOpenOptionReadOnly, NativeFile::Unowned);
+
+ IOObjectSP output_sp = std::make_shared<NativeFile>(
+ fileno(stdout), File::eOpenOptionWriteOnly, NativeFile::Unowned);
+
+ static MainLoop loop;
+
+ sys::SetInterruptFunction([]() {
+ loop.AddPendingCallback(
+ [](MainLoopBase &loop) { loop.RequestTermination(); });
+ });
+
+ auto existing_servers = ServerInfo::Load();
+
+ if (!existing_servers)
+ exitWithError(existing_servers.takeError());
+
+ // FIXME: Launch `lldb -o 'protocol start MCP'`.
+ if (existing_servers->empty())
+ exitWithError(createStringError("No MCP servers running"));
+
+ // FIXME: Support selecting a specific server.
+ if (existing_servers->size() != 1)
+ exitWithError(
+ createStringError("To many MCP servers running, picking a specific "
+ "one is not yet implemented."));
+
+ ServerInfo &info = existing_servers->front();
+ connectAndForwardIO(loop, info, input_sp, output_sp);
+
+ return EXIT_SUCCESS;
+}
diff --git a/lldb/tools/yaml2macho-core/CMakeLists.txt b/lldb/tools/yaml2macho-core/CMakeLists.txt
new file mode 100644
index 000000000000..5d8ee3645d52
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_lldb_tool(yaml2macho-core
+ CoreSpec.cpp
+ LCNoteWriter.cpp
+ MemoryWriter.cpp
+ ThreadWriter.cpp
+ Utility.cpp
+ yaml2macho.cpp
+
+ LINK_COMPONENTS
+ Support
+ LINK_LIBS
+ lldbUtility
+ ${LLDB_SYSTEM_LIBS}
+)
diff --git a/lldb/tools/yaml2macho-core/CoreSpec.cpp b/lldb/tools/yaml2macho-core/CoreSpec.cpp
new file mode 100644
index 000000000000..22551a60b3a5
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/CoreSpec.cpp
@@ -0,0 +1,165 @@
+//===-- CoreSpec.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoreSpec.h"
+#include "llvm/BinaryFormat/MachO.h"
+#include "llvm/Support/YAMLTraits.h"
+#include <stdio.h>
+#include <string>
+
+using llvm::yaml::Input;
+using llvm::yaml::IO;
+using llvm::yaml::MappingTraits;
+
+template <> struct llvm::yaml::MappingTraits<RegisterNameAndValue> {
+ static void mapping(IO &io, RegisterNameAndValue &name_value) {
+ io.mapRequired("name", name_value.name);
+ io.mapRequired("value", name_value.value);
+ }
+};
+LLVM_YAML_IS_SEQUENCE_VECTOR(RegisterNameAndValue)
+
+template <> struct llvm::yaml::ScalarEnumerationTraits<RegisterFlavor> {
+ static void enumeration(IO &io, RegisterFlavor &flavor) {
+ io.enumCase(flavor, "gpr", RegisterFlavor::GPR);
+ io.enumCase(flavor, "fpr", RegisterFlavor::FPR);
+ io.enumCase(flavor, "exc", RegisterFlavor::EXC);
+ }
+};
+
+template <> struct llvm::yaml::MappingTraits<RegisterSet> {
+ static void mapping(IO &io, RegisterSet &regset) {
+ io.mapRequired("flavor", regset.flavor);
+ io.mapRequired("registers", regset.registers);
+ }
+};
+LLVM_YAML_IS_SEQUENCE_VECTOR(RegisterSet)
+
+template <> struct llvm::yaml::MappingTraits<Thread> {
+ static void mapping(IO &io, Thread &thread) {
+ io.mapRequired("regsets", thread.regsets);
+ }
+};
+LLVM_YAML_IS_SEQUENCE_VECTOR(Thread)
+
+template <> struct llvm::yaml::MappingTraits<MemoryRegion> {
+ static void mapping(IO &io, MemoryRegion &memory) {
+ io.mapRequired("addr", memory.addr);
+ io.mapOptional("UInt8", memory.bytes);
+ io.mapOptional("UInt32", memory.words);
+ io.mapOptional("UInt64", memory.doublewords);
+
+ if (memory.bytes.size()) {
+ memory.type = MemoryType::UInt8;
+ memory.size = memory.bytes.size();
+ } else if (memory.words.size()) {
+ memory.type = MemoryType::UInt32;
+ memory.size = memory.words.size() * 4;
+ } else if (memory.doublewords.size()) {
+ memory.type = MemoryType::UInt64;
+ memory.size = memory.doublewords.size() * 8;
+ }
+ }
+};
+LLVM_YAML_IS_SEQUENCE_VECTOR(MemoryRegion)
+
+template <> struct llvm::yaml::MappingTraits<Binary> {
+ static void mapping(IO &io, Binary &binary) {
+ io.mapOptional("name", binary.name);
+ io.mapRequired("uuid", binary.uuid);
+ std::optional<uint64_t> va, slide;
+ io.mapOptional("virtual-address", va);
+ io.mapOptional("slide", slide);
+ if (va && *va != UINT64_MAX) {
+ binary.value_is_slide = false;
+ binary.value = *va;
+ } else if (slide && *slide != UINT64_MAX) {
+ binary.value_is_slide = true;
+ binary.value = *slide;
+ } else {
+ fprintf(stderr,
+ "No virtual-address or slide specified for binary %s, aborting\n",
+ binary.uuid.c_str());
+ exit(1);
+ }
+ }
+};
+LLVM_YAML_IS_SEQUENCE_VECTOR(Binary)
+
+template <> struct llvm::yaml::MappingTraits<AddressableBits> {
+ static void mapping(IO &io, AddressableBits &addr_bits) {
+ std::optional<int> addressable_bits;
+ io.mapOptional("num-bits", addressable_bits);
+ if (addressable_bits) {
+ addr_bits.lowmem_bits = *addressable_bits;
+ addr_bits.highmem_bits = *addressable_bits;
+ } else {
+ io.mapOptional("lowmem-num-bits", addr_bits.lowmem_bits);
+ io.mapOptional("highmem-num-bits", addr_bits.highmem_bits);
+ }
+ }
+};
+
+template <> struct llvm::yaml::MappingTraits<CoreSpec> {
+ static void mapping(IO &io, CoreSpec &corespec) {
+ std::string cpuname;
+ io.mapRequired("cpu", cpuname);
+ if (cpuname == "armv7m") {
+ corespec.cputype = llvm::MachO::CPU_TYPE_ARM;
+ corespec.cpusubtype = llvm::MachO::CPU_SUBTYPE_ARM_V7M;
+ } else if (cpuname == "armv7") {
+ corespec.cputype = llvm::MachO::CPU_TYPE_ARM;
+ corespec.cpusubtype = llvm::MachO::CPU_SUBTYPE_ARM_ALL;
+ } else if (cpuname == "riscv") {
+ corespec.cputype = llvm::MachO::CPU_TYPE_RISCV;
+ corespec.cpusubtype = llvm::MachO::CPU_SUBTYPE_RISCV_ALL;
+ } else if (cpuname == "arm64") {
+ corespec.cputype = llvm::MachO::CPU_TYPE_ARM64;
+ corespec.cpusubtype = llvm::MachO::CPU_SUBTYPE_ARM64_ALL;
+ } else {
+ fprintf(stderr, "Unrecognized cpu name %s, exiting.\n", cpuname.c_str());
+ exit(1);
+ }
+ io.mapOptional("threads", corespec.threads);
+ io.mapOptional("memory-regions", corespec.memory_regions);
+ if (corespec.cputype == llvm::MachO::CPU_TYPE_ARM ||
+ corespec.cputype == llvm::MachO::CPU_TYPE_RISCV)
+ corespec.wordsize = 4;
+ else if (corespec.cputype == llvm::MachO::CPU_TYPE_ARM64)
+ corespec.wordsize = 8;
+ else {
+ fprintf(stderr,
+ "Unrecognized cputype, could not set wordsize, exiting.\n");
+ exit(1);
+ }
+ io.mapOptional("addressable-bits", corespec.addressable_bits);
+ io.mapOptional("binaries", corespec.binaries);
+ if (corespec.addressable_bits) {
+ if (!corespec.addressable_bits->lowmem_bits)
+ corespec.addressable_bits->lowmem_bits = corespec.wordsize * 8;
+ if (!corespec.addressable_bits->highmem_bits)
+ corespec.addressable_bits->highmem_bits = corespec.wordsize * 8;
+ }
+ }
+};
+
+CoreSpec from_yaml(char *buf, size_t len) {
+ llvm::StringRef file_corespec_strref(buf, len);
+
+ Input yin(file_corespec_strref);
+
+ CoreSpec v;
+ yin >> v;
+
+ if (yin.error()) {
+ fprintf(stderr, "Unable to parse YAML, exiting\n");
+ exit(1);
+ }
+
+ return v;
+}
diff --git a/lldb/tools/yaml2macho-core/CoreSpec.h b/lldb/tools/yaml2macho-core/CoreSpec.h
new file mode 100644
index 000000000000..5c27cc96bdaa
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/CoreSpec.h
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// CoreSpec holds the internal representation of the data that will be
+/// written into the corefile. Theads, register sets within threads, registers
+/// within register sets. Block of memory. Metadata about the CPU or binaries
+/// that were present.
+//===----------------------------------------------------------------------===//
+
+#ifndef YAML2MACHOCOREFILE_CORESPEC_H
+#define YAML2MACHOCOREFILE_CORESPEC_H
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <vector>
+
+struct RegisterNameAndValue {
+ std::string name;
+ uint64_t value;
+};
+
+enum RegisterFlavor { GPR = 0, FPR, EXC };
+
+struct RegisterSet {
+ RegisterFlavor flavor;
+ std::vector<RegisterNameAndValue> registers;
+};
+
+struct Thread {
+ std::vector<RegisterSet> regsets;
+};
+
+enum MemoryType { UInt8 = 0, UInt32, UInt64 };
+
+struct MemoryRegion {
+ uint64_t addr;
+ MemoryType type;
+ uint32_t size;
+ // One of the following formats.
+ std::vector<uint8_t> bytes;
+ std::vector<uint32_t> words;
+ std::vector<uint64_t> doublewords;
+};
+
+struct AddressableBits {
+ std::optional<int> lowmem_bits;
+ std::optional<int> highmem_bits;
+};
+
+struct Binary {
+ std::string name;
+ std::string uuid;
+ bool value_is_slide;
+ uint64_t value;
+};
+
+struct CoreSpec {
+ uint32_t cputype;
+ uint32_t cpusubtype;
+ int wordsize;
+
+ std::vector<Thread> threads;
+ std::vector<MemoryRegion> memory_regions;
+
+ std::optional<AddressableBits> addressable_bits;
+ std::vector<Binary> binaries;
+
+ CoreSpec() : cputype(0), cpusubtype(0), wordsize(0) {}
+};
+
+CoreSpec from_yaml(char *buf, size_t len);
+
+#endif
diff --git a/lldb/tools/yaml2macho-core/LCNoteWriter.cpp b/lldb/tools/yaml2macho-core/LCNoteWriter.cpp
new file mode 100644
index 000000000000..824a1de7ddfa
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/LCNoteWriter.cpp
@@ -0,0 +1,97 @@
+//===-- LCNoteWriter.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "LCNoteWriter.h"
+#include "Utility.h"
+#include "lldb/Utility/UUID.h"
+#include "llvm/BinaryFormat/MachO.h"
+#include <ctype.h>
+#include <stdlib.h>
+
+void create_lc_note_binary_load_cmd(const CoreSpec &spec,
+ std::vector<uint8_t> &cmds,
+ const Binary &binary,
+ std::vector<uint8_t> &payload_bytes,
+ off_t data_offset) {
+
+ // Add the payload bytes to payload_bytes.
+ size_t starting_payload_size = payload_bytes.size();
+ add_uint32(payload_bytes, 1); // version
+ lldb_private::UUID uuid;
+ uuid.SetFromStringRef(binary.uuid);
+ for (size_t i = 0; i < uuid.GetBytes().size(); i++)
+ payload_bytes.push_back(uuid.GetBytes().data()[i]);
+ if (binary.value_is_slide) {
+ add_uint64(payload_bytes, UINT64_MAX); // address
+ add_uint64(payload_bytes, binary.value); // slide
+ } else {
+ add_uint64(payload_bytes, binary.value); // address
+ add_uint64(payload_bytes, UINT64_MAX); // slide
+ }
+ if (binary.name.empty()) {
+ payload_bytes.push_back(0); // name_cstring
+ } else {
+ size_t len = binary.name.size();
+ for (size_t i = 0; i < len; i++)
+ payload_bytes.push_back(binary.name[i]);
+ payload_bytes.push_back(0); // name_cstring
+ }
+
+ size_t payload_size = payload_bytes.size() - starting_payload_size;
+ // Pad out the entry to a 4-byte aligned size.
+ if (payload_bytes.size() % 4 != 0) {
+ size_t pad_bytes =
+ ((payload_bytes.size() + 4 - 1) & ~(4 - 1)) - payload_bytes.size();
+ for (size_t i = 0; i < pad_bytes; i++)
+ payload_bytes.push_back(0);
+ }
+
+ // Add the load command bytes to cmds.
+ add_uint32(cmds, llvm::MachO::LC_NOTE);
+ add_uint32(cmds, sizeof(struct llvm::MachO::note_command));
+ char cmdname[16];
+ memset(cmdname, '\0', sizeof(cmdname));
+ strcpy(cmdname, "load binary");
+ for (int i = 0; i < 16; i++)
+ cmds.push_back(cmdname[i]);
+ add_uint64(cmds, data_offset);
+ add_uint64(cmds, payload_size);
+}
+
+void create_lc_note_addressable_bits(const CoreSpec &spec,
+ std::vector<uint8_t> &cmds,
+ const AddressableBits &addr_bits,
+ std::vector<uint8_t> &payload_bytes,
+ off_t data_offset) {
+ // Add the payload bytes to payload_bytes.
+ size_t starting_payload_size = payload_bytes.size();
+ add_uint32(payload_bytes, 4); // version
+
+ add_uint32(payload_bytes, *addr_bits.lowmem_bits); // low memory
+ add_uint32(payload_bytes, *addr_bits.highmem_bits); // high memory
+ add_uint32(payload_bytes, 0); // reserved
+ size_t payload_size = payload_bytes.size() - starting_payload_size;
+ // Pad out the entry to a 4-byte aligned size.
+ if (payload_bytes.size() % 4 != 0) {
+ size_t pad_bytes =
+ ((payload_bytes.size() + 4 - 1) & ~(4 - 1)) - payload_bytes.size();
+ for (size_t i = 0; i < pad_bytes; i++)
+ payload_bytes.push_back(0);
+ }
+
+ // Add the load command bytes to cmds.
+ add_uint32(cmds, llvm::MachO::LC_NOTE);
+ add_uint32(cmds, sizeof(struct llvm::MachO::note_command));
+ char cmdname[16];
+ memset(cmdname, '\0', sizeof(cmdname));
+ strcpy(cmdname, "addrable bits");
+ for (int i = 0; i < 16; i++)
+ cmds.push_back(cmdname[i]);
+ add_uint64(cmds, data_offset);
+ add_uint64(cmds, payload_size);
+}
diff --git a/lldb/tools/yaml2macho-core/LCNoteWriter.h b/lldb/tools/yaml2macho-core/LCNoteWriter.h
new file mode 100644
index 000000000000..ae04faabe828
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/LCNoteWriter.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// Functions to add an LC_NOTE load command to the corefile's load commands,
+/// and supply the payload of that LC_NOTE separately.
+//===----------------------------------------------------------------------===//
+
+#ifndef YAML2MACHOCOREFILE_LCNOTEWRITER_H
+#define YAML2MACHOCOREFILE_LCNOTEWRITER_H
+
+#include "CoreSpec.h"
+
+#include <stdio.h>
+#include <vector>
+
+void create_lc_note_binary_load_cmd(const CoreSpec &spec,
+ std::vector<uint8_t> &cmds,
+ const Binary &binary,
+ std::vector<uint8_t> &payload_bytes,
+ off_t data_offset);
+
+void create_lc_note_addressable_bits(const CoreSpec &spec,
+ std::vector<uint8_t> &cmds,
+ const AddressableBits &addr_bits,
+ std::vector<uint8_t> &payload_bytes,
+ off_t data_offset);
+
+#endif
diff --git a/lldb/tools/yaml2macho-core/MemoryWriter.cpp b/lldb/tools/yaml2macho-core/MemoryWriter.cpp
new file mode 100644
index 000000000000..4ace2894b6be
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/MemoryWriter.cpp
@@ -0,0 +1,56 @@
+//===-- MemoryWriter.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MemoryWriter.h"
+#include "CoreSpec.h"
+#include "Utility.h"
+#include "llvm/BinaryFormat/MachO.h"
+
+void create_lc_segment_cmd(const CoreSpec &spec, std::vector<uint8_t> &cmds,
+ const MemoryRegion &memory, off_t data_offset) {
+ if (spec.wordsize == 8) {
+ // Add the bytes for a segment_command_64 from <mach-o/loader.h>
+ add_uint32(cmds, llvm::MachO::LC_SEGMENT_64);
+ add_uint32(cmds, sizeof(struct llvm::MachO::segment_command_64));
+ for (int i = 0; i < 16; i++)
+ cmds.push_back(0);
+ add_uint64(cmds, memory.addr); // segment_command_64.vmaddr
+ add_uint64(cmds, memory.size); // segment_command_64.vmsize
+ add_uint64(cmds, data_offset); // segment_command_64.fileoff
+ add_uint64(cmds, memory.size); // segment_command_64.filesize
+ } else {
+ // Add the bytes for a segment_command from <mach-o/loader.h>
+ add_uint32(cmds, llvm::MachO::LC_SEGMENT);
+ add_uint32(cmds, sizeof(struct llvm::MachO::segment_command));
+ for (int i = 0; i < 16; i++)
+ cmds.push_back(0);
+ add_uint32(cmds, memory.addr); // segment_command_64.vmaddr
+ add_uint32(cmds, memory.size); // segment_command_64.vmsize
+ add_uint32(cmds, data_offset); // segment_command_64.fileoff
+ add_uint32(cmds, memory.size); // segment_command_64.filesize
+ }
+ add_uint32(cmds, 3); // segment_command_64.maxprot
+ add_uint32(cmds, 3); // segment_command_64.initprot
+ add_uint32(cmds, 0); // segment_command_64.nsects
+ add_uint32(cmds, 0); // segment_command_64.flags
+}
+
+void create_memory_bytes(const CoreSpec &spec, const MemoryRegion &memory,
+ std::vector<uint8_t> &buf) {
+ if (memory.type == MemoryType::UInt8)
+ for (uint8_t byte : memory.bytes)
+ buf.push_back(byte);
+
+ if (memory.type == MemoryType::UInt32)
+ for (uint32_t word : memory.words)
+ add_uint32(buf, word);
+
+ if (memory.type == MemoryType::UInt64)
+ for (uint64_t word : memory.doublewords)
+ add_uint64(buf, word);
+}
diff --git a/lldb/tools/yaml2macho-core/MemoryWriter.h b/lldb/tools/yaml2macho-core/MemoryWriter.h
new file mode 100644
index 000000000000..2bef00cdb764
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/MemoryWriter.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// Functions to emit the LC_SEGMENT load command, and to provide the bytes
+/// that appear later in the corefile.
+//===----------------------------------------------------------------------===//
+
+#ifndef YAML2MACHOCOREFILE_MEMORYWRITER_H
+#define YAML2MACHOCOREFILE_MEMORYWRITER_H
+
+#include "CoreSpec.h"
+
+#include <vector>
+
+void create_lc_segment_cmd(const CoreSpec &spec, std::vector<uint8_t> &cmds,
+ const MemoryRegion &memory, off_t data_offset);
+
+void create_memory_bytes(const CoreSpec &spec, const MemoryRegion &memory,
+ std::vector<uint8_t> &buf);
+
+#endif
diff --git a/lldb/tools/yaml2macho-core/ThreadWriter.cpp b/lldb/tools/yaml2macho-core/ThreadWriter.cpp
new file mode 100644
index 000000000000..40c70d92970c
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/ThreadWriter.cpp
@@ -0,0 +1,200 @@
+//===-- ThreadWriter.cpp --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ThreadWriter.h"
+#include "CoreSpec.h"
+#include "Utility.h"
+#include "llvm/BinaryFormat/MachO.h"
+#include <algorithm>
+#include <stdio.h>
+
+#define ARM_THREAD_STATE 1
+#define ARM_THREAD_STATE_COUNT 17
+#define ARM_EXCEPTION_STATE 3
+#define ARM_EXCEPTION_STATE_COUNT 3
+
+std::vector<RegisterNameAndValue>::const_iterator
+find_by_name(std::vector<RegisterNameAndValue>::const_iterator first,
+ std::vector<RegisterNameAndValue>::const_iterator last,
+ const char *name) {
+ for (; first != last; ++first)
+ if (first->name == name)
+ return first;
+ return last;
+}
+
+void add_reg_value(CoreSpec &spec, std::vector<uint8_t> &buf,
+ const std::vector<RegisterNameAndValue> &registers,
+ const char *regname, int regsize) {
+ const auto it = find_by_name(registers.begin(), registers.end(), regname);
+ if (it != registers.end()) {
+ if (regsize == 8)
+ add_uint64(buf, it->value);
+ else
+ add_uint32(buf, it->value);
+ } else {
+ if (regsize == 8)
+ add_uint64(buf, 0);
+ else
+ add_uint32(buf, 0);
+ }
+}
+
+void add_reg_value_32(CoreSpec &spec, std::vector<uint8_t> &buf,
+ const std::vector<RegisterNameAndValue> &registers,
+ const char *regname) {
+ add_reg_value(spec, buf, registers, regname, 4);
+}
+
+void add_reg_value_64(CoreSpec &spec, std::vector<uint8_t> &buf,
+ const std::vector<RegisterNameAndValue> &registers,
+ const char *regname) {
+ add_reg_value(spec, buf, registers, regname, 8);
+}
+
+void add_lc_threads_armv7(CoreSpec &spec,
+ std::vector<std::vector<uint8_t>> &load_commands) {
+ for (const Thread &th : spec.threads) {
+ std::vector<uint8_t> lc;
+ int size_of_all_flavors = 0;
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR)
+ size_of_all_flavors += (ARM_THREAD_STATE_COUNT * 4);
+ if (rs.flavor == RegisterFlavor::EXC)
+ size_of_all_flavors += (ARM_EXCEPTION_STATE_COUNT * 4);
+ }
+ int cmdsize = 4 * 2; // cmd, cmdsize
+ cmdsize += 4 * 2 * th.regsets.size(); // flavor, count (per register flavor)
+ cmdsize += size_of_all_flavors; // size of all the register set data
+
+ add_uint32(lc, llvm::MachO::LC_THREAD); // thread_command.cmd
+ add_uint32(lc, cmdsize); // thread_command.cmdsize
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR) {
+ add_uint32(lc, ARM_THREAD_STATE); // thread_command.flavor
+ add_uint32(lc, ARM_THREAD_STATE_COUNT); // thread_command.count
+ const char *names[] = {"r0", "r1", "r2", "r3", "r4", "r5",
+ "r6", "r7", "r8", "r9", "r10", "r11",
+ "r12", "sp", "lr", "pc", "cpsr", nullptr};
+ for (int i = 0; names[i]; i++)
+ add_reg_value_32(spec, lc, rs.registers, names[i]);
+ }
+ if (rs.flavor == RegisterFlavor::EXC) {
+ add_uint32(lc, ARM_EXCEPTION_STATE); // thread_command.flavor
+ add_uint32(lc, ARM_EXCEPTION_STATE_COUNT); // thread_command.count
+ const char *names[] = {"far", "esr", "exception", nullptr};
+ for (int i = 0; names[i]; i++)
+ add_reg_value_32(spec, lc, rs.registers, names[i]);
+ }
+ }
+ load_commands.push_back(lc);
+ }
+}
+
+#define ARM_THREAD_STATE64 6
+#define ARM_THREAD_STATE64_COUNT 68
+#define ARM_EXCEPTION_STATE64 7
+#define ARM_EXCEPTION_STATE64_COUNT 4
+
+void add_lc_threads_arm64(CoreSpec &spec,
+ std::vector<std::vector<uint8_t>> &load_commands) {
+ for (const Thread &th : spec.threads) {
+ std::vector<uint8_t> lc;
+ int size_of_all_flavors = 0;
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR)
+ size_of_all_flavors += (ARM_THREAD_STATE64_COUNT * 4);
+ if (rs.flavor == RegisterFlavor::EXC)
+ size_of_all_flavors += (ARM_EXCEPTION_STATE64_COUNT * 4);
+ }
+ int cmdsize = 4 * 2; // cmd, cmdsize
+ cmdsize += 4 * 2 * th.regsets.size(); // flavor, count (per register flavor)
+ cmdsize += size_of_all_flavors; // size of all the register set data
+
+ add_uint32(lc, llvm::MachO::LC_THREAD); // thread_command.cmd
+ add_uint32(lc, cmdsize); // thread_command.cmdsize
+
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR) {
+ add_uint32(lc, ARM_THREAD_STATE64); // thread_command.flavor
+ add_uint32(lc, ARM_THREAD_STATE64_COUNT); // thread_command.count
+ const char *names[] = {"x0", "x1", "x2", "x3", "x4", "x5", "x6",
+ "x7", "x8", "x9", "x10", "x11", "x12", "x13",
+ "x14", "x15", "x16", "x17", "x18", "x19", "x20",
+ "x21", "x22", "x23", "x24", "x25", "x26", "x27",
+ "x28", "fp", "lr", "sp", "pc", nullptr};
+ for (int i = 0; names[i]; i++)
+ add_reg_value_64(spec, lc, rs.registers, names[i]);
+
+ // cpsr is a 4-byte reg
+ add_reg_value_32(spec, lc, rs.registers, "cpsr");
+ // the 4 bytes of zeroes
+ add_uint32(lc, 0);
+ }
+ if (rs.flavor == RegisterFlavor::EXC) {
+ add_uint32(lc, ARM_EXCEPTION_STATE64); // thread_command.flavor
+ add_uint32(lc,
+ ARM_EXCEPTION_STATE64_COUNT); // thread_command.count
+ add_reg_value_64(spec, lc, rs.registers, "far");
+ add_reg_value_32(spec, lc, rs.registers, "esr");
+ add_reg_value_32(spec, lc, rs.registers, "exception");
+ }
+ }
+ load_commands.push_back(lc);
+ }
+}
+
+#define RV32_THREAD_STATE 2
+#define RV32_THREAD_STATE_COUNT 33
+
+void add_lc_threads_riscv(CoreSpec &spec,
+ std::vector<std::vector<uint8_t>> &load_commands) {
+ for (const Thread &th : spec.threads) {
+ std::vector<uint8_t> lc;
+ int size_of_all_flavors = 0;
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR)
+ size_of_all_flavors += (RV32_THREAD_STATE_COUNT * 4);
+ }
+ int cmdsize = 4 * 2; // cmd, cmdsize
+ cmdsize += 4 * 2 * th.regsets.size(); // flavor, count (per register flavor)
+ cmdsize += size_of_all_flavors; // size of all the register set data
+
+ add_uint32(lc, llvm::MachO::LC_THREAD); // thread_command.cmd
+ add_uint32(lc, cmdsize); // thread_command.cmdsize
+ for (const RegisterSet &rs : th.regsets) {
+ if (rs.flavor == RegisterFlavor::GPR) {
+ add_uint32(lc, RV32_THREAD_STATE); // thread_command.flavor
+ add_uint32(lc, RV32_THREAD_STATE_COUNT); // thread_command.count
+ const char *names[] = {"zero", "ra", "sp", "gp", "tp", "t0", "t1",
+ "t2", "fp", "s1", "a0", "a1", "a2", "a3",
+ "a4", "a5", "a6", "a7", "s2", "s3", "s4",
+ "s5", "s6", "s7", "s8", "s9", "s10", "s11",
+ "t3", "t4", "t5", "t6", "pc", nullptr};
+ for (int i = 0; names[i]; i++)
+ add_reg_value_32(spec, lc, rs.registers, names[i]);
+ }
+ }
+ load_commands.push_back(lc);
+ }
+}
+
+void add_lc_threads(CoreSpec &spec,
+ std::vector<std::vector<uint8_t>> &load_commands) {
+ if (spec.cputype == llvm::MachO::CPU_TYPE_ARM)
+ add_lc_threads_armv7(spec, load_commands);
+ else if (spec.cputype == llvm::MachO::CPU_TYPE_ARM64)
+ add_lc_threads_arm64(spec, load_commands);
+ else if (spec.cputype == llvm::MachO::CPU_TYPE_RISCV)
+ add_lc_threads_riscv(spec, load_commands);
+ else {
+ fprintf(stderr,
+ "Unrecognized cputype, could not write LC_THREAD. Exiting.\n");
+ exit(1);
+ }
+}
diff --git a/lldb/tools/yaml2macho-core/ThreadWriter.h b/lldb/tools/yaml2macho-core/ThreadWriter.h
new file mode 100644
index 000000000000..58ce4abdf0d1
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/ThreadWriter.h
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// Functions to emit LC_THREAD bytes to the corefile's Mach-O load commands,
+/// specifying the threads, the register sets ("flavors") within those threads,
+/// and all of the registers within those register sets.
+//===----------------------------------------------------------------------===//
+
+#ifndef YAML2MACHOCOREFILE_THREADWRITER_H
+#define YAML2MACHOCOREFILE_THREADWRITER_H
+
+#include "CoreSpec.h"
+
+#include <vector>
+
+void add_lc_threads(CoreSpec &spec,
+ std::vector<std::vector<uint8_t>> &load_commands);
+
+#endif
diff --git a/lldb/tools/yaml2macho-core/Utility.cpp b/lldb/tools/yaml2macho-core/Utility.cpp
new file mode 100644
index 000000000000..2414c2f03932
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/Utility.cpp
@@ -0,0 +1,22 @@
+//===-- Utility.cpp -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Utility.h"
+#include "CoreSpec.h"
+
+void add_uint64(std::vector<uint8_t> &buf, uint64_t val) {
+ uint8_t *p = reinterpret_cast<uint8_t *>(&val);
+ for (int i = 0; i < 8; i++)
+ buf.push_back(*p++);
+}
+
+void add_uint32(std::vector<uint8_t> &buf, uint32_t val) {
+ uint8_t *p = reinterpret_cast<uint8_t *>(&val);
+ for (int i = 0; i < 4; i++)
+ buf.push_back(*p++);
+}
diff --git a/lldb/tools/yaml2macho-core/Utility.h b/lldb/tools/yaml2macho-core/Utility.h
new file mode 100644
index 000000000000..ff05858c4dee
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/Utility.h
@@ -0,0 +1,18 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef YAML2MACHOCOREFILE_UTILITY_H
+#define YAML2MACHOCOREFILE_UTILITY_H
+
+#include "CoreSpec.h"
+#include <vector>
+
+void add_uint64(std::vector<uint8_t> &buf, uint64_t val);
+void add_uint32(std::vector<uint8_t> &buf, uint32_t val);
+
+#endif
diff --git a/lldb/tools/yaml2macho-core/yaml2macho.cpp b/lldb/tools/yaml2macho-core/yaml2macho.cpp
new file mode 100644
index 000000000000..85979a37d167
--- /dev/null
+++ b/lldb/tools/yaml2macho-core/yaml2macho.cpp
@@ -0,0 +1,250 @@
+//===-- main.cppp ---------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoreSpec.h"
+#include "LCNoteWriter.h"
+#include "MemoryWriter.h"
+#include "ThreadWriter.h"
+#include "Utility.h"
+#include "llvm/BinaryFormat/MachO.h"
+#include "llvm/Support/CommandLine.h"
+#include <stdio.h>
+#include <string>
+#include <sys/stat.h>
+
+std::vector<std::string> get_fields_from_delimited_string(std::string str,
+ const char delim) {
+ std::vector<std::string> result;
+ std::string::size_type prev = std::string::npos;
+ std::string::size_type next = str.find(delim);
+ if (str.empty()) {
+ return result;
+ }
+ if (next == std::string::npos) {
+ result.push_back(str);
+ } else {
+ result.push_back(std::string(str, 0, next));
+ prev = next;
+ while ((next = str.find(delim, prev + 1)) != std::string::npos) {
+ result.push_back(std::string(str, prev + 1, next - prev - 1));
+ prev = next;
+ }
+ result.push_back(std::string(str, prev + 1));
+ }
+ return result;
+}
+
+llvm::cl::opt<std::string> InputFilename("i", llvm::cl::Required,
+ llvm::cl::desc("input yaml filename"),
+ llvm::cl::value_desc("input"));
+llvm::cl::opt<std::string>
+ OutputFilename("o", llvm::cl::Required,
+ llvm::cl::desc("output core filenames"),
+ llvm::cl::value_desc("output"));
+llvm::cl::list<std::string>
+ UUIDs("u", llvm::cl::desc("uuid of binary loaded at slide 0"),
+ llvm::cl::value_desc("uuid"));
+llvm::cl::list<std::string>
+ UUIDAndVAs("L", llvm::cl::desc("UUID,virtual-address-loaded-at"),
+ llvm::cl::value_desc("--uuid-and-load-addr"));
+llvm::cl::opt<int>
+ AddressableBitsOverride("A",
+ llvm::cl::desc("number of bits used in addressing"),
+ llvm::cl::value_desc("--address-bits"));
+
+int main(int argc, char **argv) {
+ llvm::cl::ParseCommandLineOptions(argc, argv);
+
+ if (InputFilename.empty() || OutputFilename.empty()) {
+ fprintf(stderr, "Missing input or outpur file.\n");
+ exit(1);
+ }
+
+ struct stat sb;
+
+ if (stat(InputFilename.c_str(), &sb) == -1) {
+ fprintf(stderr, "Unable to stat %s, exiting\n", InputFilename.c_str());
+ exit(1);
+ }
+
+ FILE *input = fopen(InputFilename.c_str(), "r");
+ if (!input) {
+ fprintf(stderr, "Unable to open %s, exiting\n", InputFilename.c_str());
+ exit(1);
+ }
+ auto file_corespec = std::make_unique<char[]>(sb.st_size);
+ if (fread(file_corespec.get(), sb.st_size, 1, input) != 1) {
+ fprintf(stderr, "Unable to read all of %s, exiting\n",
+ InputFilename.c_str());
+ exit(1);
+ }
+ CoreSpec spec = from_yaml(file_corespec.get(), sb.st_size);
+ fclose(input);
+
+ for (const std::string &uuid : UUIDs) {
+ Binary binary;
+ binary.uuid = uuid;
+ binary.value = 0;
+ binary.value_is_slide = true;
+ spec.binaries.push_back(binary);
+ }
+
+ for (const std::string &uuid_and_va : UUIDAndVAs) {
+ std::vector<std::string> parts =
+ get_fields_from_delimited_string(uuid_and_va, ',');
+
+ std::string uuid = parts[0];
+ uint64_t va = std::strtoull(parts[1].c_str(), nullptr, 16);
+ Binary binary;
+ binary.uuid = uuid;
+ binary.value = va;
+ binary.value_is_slide = false;
+ spec.binaries.push_back(binary);
+ }
+
+ if (AddressableBitsOverride) {
+ AddressableBits bits;
+ bits.lowmem_bits = bits.highmem_bits = AddressableBitsOverride;
+ spec.addressable_bits = bits;
+ }
+
+ // An array of load commands
+ std::vector<std::vector<uint8_t>> load_commands;
+
+ // An array of corefile contents (memory regions)
+ std::vector<uint8_t> payload;
+
+ // First add all the load commands / payload so we can figure out how large
+ // the load commands will be.
+
+ add_lc_threads(spec, load_commands);
+ for (size_t i = 0; i < spec.memory_regions.size(); i++) {
+ std::vector<uint8_t> segment_command_bytes;
+ create_lc_segment_cmd(spec, segment_command_bytes, spec.memory_regions[i],
+ 0);
+ load_commands.push_back(segment_command_bytes);
+ }
+
+ if (spec.binaries.size() > 0)
+ for (const Binary &binary : spec.binaries) {
+ std::vector<uint8_t> segment_command_bytes;
+ std::vector<uint8_t> payload_bytes;
+ create_lc_note_binary_load_cmd(spec, segment_command_bytes, binary,
+ payload_bytes, 0);
+ load_commands.push_back(segment_command_bytes);
+ }
+ if (spec.addressable_bits) {
+ std::vector<uint8_t> segment_command_bytes;
+ std::vector<uint8_t> payload_bytes;
+ create_lc_note_addressable_bits(spec, segment_command_bytes,
+ *spec.addressable_bits, payload_bytes, 0);
+ load_commands.push_back(segment_command_bytes);
+ }
+
+ off_t size_of_load_commands = 0;
+ for (const auto &lc : load_commands)
+ size_of_load_commands += lc.size();
+
+ off_t header_and_load_cmd_room =
+ sizeof(llvm::MachO::mach_header_64) + size_of_load_commands;
+ off_t initial_payload_fileoff = header_and_load_cmd_room;
+ initial_payload_fileoff = (initial_payload_fileoff + 4096 - 1) & ~(4096 - 1);
+ off_t payload_fileoff = initial_payload_fileoff;
+
+ // Erase the load commands / payload now that we know how much space is
+ // needed, redo it with real values.
+ load_commands.clear();
+ payload.clear();
+
+ add_lc_threads(spec, load_commands);
+ for (size_t i = 0; i < spec.memory_regions.size(); i++) {
+ std::vector<uint8_t> segment_command_bytes;
+ create_lc_segment_cmd(spec, segment_command_bytes, spec.memory_regions[i],
+ payload_fileoff);
+ load_commands.push_back(segment_command_bytes);
+ payload_fileoff += spec.memory_regions[i].size;
+ payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
+ }
+
+ off_t payload_fileoff_before_lcnotes = payload_fileoff;
+ std::vector<uint8_t> lc_note_payload_bytes;
+ if (spec.binaries.size() > 0)
+ for (const Binary &binary : spec.binaries) {
+ std::vector<uint8_t> segment_command_bytes;
+ std::vector<uint8_t> payload_bytes;
+ create_lc_note_binary_load_cmd(spec, segment_command_bytes, binary,
+ lc_note_payload_bytes, payload_fileoff);
+ payload_fileoff =
+ payload_fileoff_before_lcnotes + lc_note_payload_bytes.size();
+ load_commands.push_back(segment_command_bytes);
+ }
+ if (spec.addressable_bits) {
+ std::vector<uint8_t> segment_command_bytes;
+ std::vector<uint8_t> payload_bytes;
+ create_lc_note_addressable_bits(spec, segment_command_bytes,
+ *spec.addressable_bits,
+ lc_note_payload_bytes, payload_fileoff);
+ payload_fileoff =
+ payload_fileoff_before_lcnotes + lc_note_payload_bytes.size();
+ load_commands.push_back(segment_command_bytes);
+ }
+
+ // Realign our payload offset if we added any LC_NOTEs.
+ if (lc_note_payload_bytes.size() > 0)
+ payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
+
+ FILE *f = fopen(OutputFilename.c_str(), "wb");
+ if (f == nullptr) {
+ fprintf(stderr, "Unable to open file %s for writing\n",
+ OutputFilename.c_str());
+ exit(1);
+ }
+
+ std::vector<uint8_t> mh;
+ // Write the fields of a mach_header_64 struct
+ if (spec.wordsize == 8)
+ add_uint32(mh, llvm::MachO::MH_MAGIC_64); // magic
+ else
+ add_uint32(mh, llvm::MachO::MH_MAGIC); // magic
+ add_uint32(mh, spec.cputype); // cputype
+ add_uint32(mh, spec.cpusubtype); // cpusubtype
+ add_uint32(mh, llvm::MachO::MH_CORE); // filetype
+ add_uint32(mh, load_commands.size()); // ncmds
+ add_uint32(mh, size_of_load_commands); // sizeofcmds
+ add_uint32(mh, 0); // flags
+ if (spec.wordsize == 8)
+ add_uint32(mh, 0); // reserved
+
+ fwrite(mh.data(), mh.size(), 1, f);
+
+ for (const auto &lc : load_commands)
+ fwrite(lc.data(), lc.size(), 1, f);
+
+ // Reset the payload offset back to the first one.
+ payload_fileoff = initial_payload_fileoff;
+ if (spec.memory_regions.size() > 0) {
+ for (size_t i = 0; i < spec.memory_regions.size(); i++) {
+ std::vector<uint8_t> bytes;
+ create_memory_bytes(spec, spec.memory_regions[i], bytes);
+ fseek(f, payload_fileoff, SEEK_SET);
+ fwrite(bytes.data(), bytes.size(), 1, f);
+
+ payload_fileoff += bytes.size();
+ payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
+ }
+ }
+
+ if (lc_note_payload_bytes.size() > 0) {
+ fseek(f, payload_fileoff, SEEK_SET);
+ fwrite(lc_note_payload_bytes.data(), lc_note_payload_bytes.size(), 1, f);
+ payload_fileoff += lc_note_payload_bytes.size();
+ payload_fileoff = (payload_fileoff + 4096 - 1) & ~(4096 - 1);
+ }
+
+ fclose(f);
+}