421 lines · cpp
1//===-- SourceBreakpoint.cpp ------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "SourceBreakpoint.h"10#include "BreakpointBase.h"11#include "DAP.h"12#include "JSONUtils.h"13#include "ProtocolUtils.h"14#include "lldb/API/SBBreakpoint.h"15#include "lldb/API/SBFileSpec.h"16#include "lldb/API/SBFileSpecList.h"17#include "lldb/API/SBFrame.h"18#include "lldb/API/SBInstruction.h"19#include "lldb/API/SBMutex.h"20#include "lldb/API/SBSymbol.h"21#include "lldb/API/SBTarget.h"22#include "lldb/API/SBThread.h"23#include "lldb/API/SBValue.h"24#include "lldb/lldb-enumerations.h"25#include "llvm/Support/Error.h"26#include <cassert>27#include <cctype>28#include <cstdlib>29#include <mutex>30#include <utility>31 32namespace lldb_dap {33 34SourceBreakpoint::SourceBreakpoint(DAP &dap,35 const protocol::SourceBreakpoint &breakpoint)36 : Breakpoint(dap, breakpoint.condition, breakpoint.hitCondition),37 m_log_message(breakpoint.logMessage.value_or("")),38 m_line(breakpoint.line),39 m_column(breakpoint.column.value_or(LLDB_INVALID_COLUMN_NUMBER)) {}40 41llvm::Error SourceBreakpoint::SetBreakpoint(const protocol::Source &source) {42 lldb::SBMutex lock = m_dap.GetAPIMutex();43 std::lock_guard<lldb::SBMutex> guard(lock);44 45 if (m_line == 0)46 return llvm::createStringError(llvm::inconvertibleErrorCode(),47 "Invalid line number.");48 49 if (source.sourceReference) {50 // Breakpoint set by assembly source.51 if (source.adapterData && source.adapterData->persistenceData) {52 // Prefer use the adapter persitence data, because this could be a53 // breakpoint from a previous session where the `sourceReference` is not54 // valid anymore.55 if (llvm::Error error = CreateAssemblyBreakpointWithPersistenceData(56 *source.adapterData->persistenceData))57 return error;58 } else {59 if (llvm::Error error = CreateAssemblyBreakpointWithSourceReference(60 *source.sourceReference))61 return error;62 }63 } else {64 CreatePathBreakpoint(source);65 }66 67 if (!m_log_message.empty())68 SetLogMessage();69 Breakpoint::SetBreakpoint();70 return llvm::Error::success();71}72 73void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) {74 if (m_log_message != request_bp.m_log_message) {75 m_log_message = request_bp.m_log_message;76 SetLogMessage();77 }78 BreakpointBase::UpdateBreakpoint(request_bp);79}80 81void SourceBreakpoint::CreatePathBreakpoint(const protocol::Source &source) {82 const auto source_path = source.path.value_or("");83 lldb::SBFileSpecList module_list;84 m_bp = m_dap.target.BreakpointCreateByLocation(source_path.c_str(), m_line,85 m_column, 0, module_list);86}87 88llvm::Error SourceBreakpoint::CreateAssemblyBreakpointWithSourceReference(89 int64_t source_reference) {90 std::optional<lldb::addr_t> raw_addr =91 m_dap.GetSourceReferenceAddress(source_reference);92 if (!raw_addr)93 return llvm::createStringError(llvm::inconvertibleErrorCode(),94 "Invalid sourceReference.");95 96 lldb::SBAddress source_address(*raw_addr, m_dap.target);97 if (!source_address.IsValid())98 return llvm::createStringError(llvm::inconvertibleErrorCode(),99 "Invalid sourceReference.");100 101 lldb::SBSymbol symbol = source_address.GetSymbol();102 if (!symbol.IsValid()) {103 // FIXME: Support assembly breakpoints without a valid symbol.104 return llvm::createStringError(llvm::inconvertibleErrorCode(),105 "Breakpoints in assembly without a valid "106 "symbol are not supported yet.");107 }108 109 lldb::SBInstructionList inst_list =110 m_dap.target.ReadInstructions(symbol.GetStartAddress(), m_line);111 if (inst_list.GetSize() < m_line)112 return llvm::createStringError(llvm::inconvertibleErrorCode(),113 "Invalid instruction list size.");114 115 lldb::SBAddress address =116 inst_list.GetInstructionAtIndex(m_line - 1).GetAddress();117 118 m_bp = m_dap.target.BreakpointCreateBySBAddress(address);119 return llvm::Error::success();120}121 122llvm::Error SourceBreakpoint::CreateAssemblyBreakpointWithPersistenceData(123 const protocol::PersistenceData &persistence_data) {124 lldb::SBFileSpec file_spec(persistence_data.module_path.c_str());125 lldb::SBFileSpecList comp_unit_list;126 lldb::SBFileSpecList file_spec_list;127 file_spec_list.Append(file_spec);128 m_bp = m_dap.target.BreakpointCreateByName(129 persistence_data.symbol_name.c_str(), lldb::eFunctionNameTypeFull,130 lldb::eLanguageTypeUnknown, m_line - 1, true, file_spec_list,131 comp_unit_list);132 return llvm::Error::success();133}134 135lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part,136 bool is_expr) {137 if (is_expr) {138 m_log_message_parts.emplace_back(part, is_expr);139 } else {140 std::string formatted;141 lldb::SBError error = FormatLogText(part, formatted);142 if (error.Fail())143 return error;144 m_log_message_parts.emplace_back(formatted, is_expr);145 }146 return lldb::SBError();147}148 149// TODO: consolidate this code with the implementation in150// FormatEntity::ParseInternal().151lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text,152 std::string &formatted) {153 lldb::SBError error;154 while (!text.empty()) {155 size_t backslash_pos = text.find_first_of('\\');156 if (backslash_pos == std::string::npos) {157 formatted += text.str();158 return error;159 }160 161 formatted += text.substr(0, backslash_pos).str();162 // Skip the characters before and including '\'.163 text = text.drop_front(backslash_pos + 1);164 165 if (text.empty()) {166 error.SetErrorString(167 "'\\' character was not followed by another character");168 return error;169 }170 171 const char desens_char = text[0];172 text = text.drop_front(); // Skip the desensitized char character173 switch (desens_char) {174 case 'a':175 formatted.push_back('\a');176 break;177 case 'b':178 formatted.push_back('\b');179 break;180 case 'f':181 formatted.push_back('\f');182 break;183 case 'n':184 formatted.push_back('\n');185 break;186 case 'r':187 formatted.push_back('\r');188 break;189 case 't':190 formatted.push_back('\t');191 break;192 case 'v':193 formatted.push_back('\v');194 break;195 case '\'':196 formatted.push_back('\'');197 break;198 case '\\':199 formatted.push_back('\\');200 break;201 case '0':202 // 1 to 3 octal chars203 {204 if (text.empty()) {205 error.SetErrorString("missing octal number following '\\0'");206 return error;207 }208 209 // Make a string that can hold onto the initial zero char, up to 3210 // octal digits, and a terminating NULL.211 char oct_str[5] = {0, 0, 0, 0, 0};212 213 size_t i;214 for (i = 0;215 i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7');216 ++i) {217 oct_str[i] = text[i];218 }219 220 text = text.drop_front(i);221 unsigned long octal_value = ::strtoul(oct_str, nullptr, 8);222 if (octal_value <= UINT8_MAX) {223 formatted.push_back((char)octal_value);224 } else {225 error.SetErrorString("octal number is larger than a single byte");226 return error;227 }228 }229 break;230 231 case 'x': {232 if (text.empty()) {233 error.SetErrorString("missing hex number following '\\x'");234 return error;235 }236 // hex number in the text237 if (std::isxdigit(text[0])) {238 // Make a string that can hold onto two hex chars plus a239 // NULL terminator240 char hex_str[3] = {0, 0, 0};241 hex_str[0] = text[0];242 243 text = text.drop_front();244 245 if (!text.empty() && std::isxdigit(text[0])) {246 hex_str[1] = text[0];247 text = text.drop_front();248 }249 250 unsigned long hex_value = strtoul(hex_str, nullptr, 16);251 if (hex_value <= UINT8_MAX) {252 formatted.push_back((char)hex_value);253 } else {254 error.SetErrorString("hex number is larger than a single byte");255 return error;256 }257 } else {258 formatted.push_back(desens_char);259 }260 break;261 }262 263 default:264 // Just desensitize any other character by just printing what came265 // after the '\'266 formatted.push_back(desens_char);267 break;268 }269 }270 return error;271}272 273// logMessage will be divided into array of LogMessagePart as two kinds:274// 1. raw print text message, and275// 2. interpolated expression for evaluation which is inside matching curly276// braces.277//278// The function tries to parse logMessage into a list of LogMessageParts279// for easy later access in BreakpointHitCallback.280void SourceBreakpoint::SetLogMessage() {281 m_log_message_parts.clear();282 283 // Contains unmatched open curly braces indices.284 std::vector<int> unmatched_curly_braces;285 286 // Contains all matched curly braces in logMessage.287 // Loop invariant: matched_curly_braces_ranges are sorted by start index in288 // ascending order without any overlap between them.289 std::vector<std::pair<int, int>> matched_curly_braces_ranges;290 291 lldb::SBError error;292 // Part1 - parse matched_curly_braces_ranges.293 // locating all curly braced expression ranges in logMessage.294 // The algorithm takes care of nested and imbalanced curly braces.295 for (size_t i = 0; i < m_log_message.size(); ++i) {296 if (m_log_message[i] == '{') {297 unmatched_curly_braces.push_back(i);298 } else if (m_log_message[i] == '}') {299 if (unmatched_curly_braces.empty())300 // Nothing to match.301 continue;302 303 int last_unmatched_index = unmatched_curly_braces.back();304 unmatched_curly_braces.pop_back();305 306 // Erase any matched ranges included in the new match.307 while (!matched_curly_braces_ranges.empty()) {308 assert(matched_curly_braces_ranges.back().first !=309 last_unmatched_index &&310 "How can a curley brace be matched twice?");311 if (matched_curly_braces_ranges.back().first < last_unmatched_index)312 break;313 314 // This is a nested range let's earse it.315 assert((size_t)matched_curly_braces_ranges.back().second < i);316 matched_curly_braces_ranges.pop_back();317 }318 319 // Assert invariant.320 assert(matched_curly_braces_ranges.empty() ||321 matched_curly_braces_ranges.back().first < last_unmatched_index);322 matched_curly_braces_ranges.emplace_back(last_unmatched_index, i);323 }324 }325 326 // Part2 - parse raw text and expresions parts.327 // All expression ranges have been parsed in matched_curly_braces_ranges.328 // The code below uses matched_curly_braces_ranges to divide logMessage329 // into raw text parts and expression parts.330 int last_raw_text_start = 0;331 for (const std::pair<int, int> &curly_braces_range :332 matched_curly_braces_ranges) {333 // Raw text before open curly brace.334 assert(curly_braces_range.first >= last_raw_text_start);335 size_t raw_text_len = curly_braces_range.first - last_raw_text_start;336 if (raw_text_len > 0) {337 error = AppendLogMessagePart(338 llvm::StringRef(m_log_message.c_str() + last_raw_text_start,339 raw_text_len),340 /*is_expr=*/false);341 if (error.Fail()) {342 NotifyLogMessageError(error.GetCString());343 return;344 }345 }346 347 // Expression between curly braces.348 assert(curly_braces_range.second > curly_braces_range.first);349 size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1;350 error = AppendLogMessagePart(351 llvm::StringRef(m_log_message.c_str() + curly_braces_range.first + 1,352 expr_len),353 /*is_expr=*/true);354 if (error.Fail()) {355 NotifyLogMessageError(error.GetCString());356 return;357 }358 359 last_raw_text_start = curly_braces_range.second + 1;360 }361 // Trailing raw text after close curly brace.362 assert(last_raw_text_start >= 0);363 if (m_log_message.size() > (size_t)last_raw_text_start) {364 error = AppendLogMessagePart(365 llvm::StringRef(m_log_message.c_str() + last_raw_text_start,366 m_log_message.size() - last_raw_text_start),367 /*is_expr=*/false);368 if (error.Fail()) {369 NotifyLogMessageError(error.GetCString());370 return;371 }372 }373 374 m_bp.SetCallback(BreakpointHitCallback, this);375}376 377void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {378 std::string message = "Log message has error: ";379 message += error;380 m_dap.SendOutput(OutputType::Console, message);381}382 383/*static*/384bool SourceBreakpoint::BreakpointHitCallback(385 void *baton, lldb::SBProcess &process, lldb::SBThread &thread,386 lldb::SBBreakpointLocation &location) {387 if (!baton)388 return true;389 390 SourceBreakpoint *bp = (SourceBreakpoint *)baton;391 lldb::SBFrame frame = thread.GetSelectedFrame();392 393 std::string output;394 for (const SourceBreakpoint::LogMessagePart &messagePart :395 bp->m_log_message_parts) {396 if (messagePart.is_expr) {397 // Try local frame variables first before fall back to expression398 // evaluation399 const std::string &expr_str = messagePart.text;400 const char *expr = expr_str.c_str();401 lldb::SBValue value =402 frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);403 if (value.GetError().Fail())404 value = frame.EvaluateExpression(expr);405 output += VariableDescription(406 value, bp->m_dap.configuration.enableAutoVariableSummaries)407 .display_value;408 } else {409 output += messagePart.text;410 }411 }412 if (!output.empty() && output.back() != '\n')413 output.push_back('\n'); // Ensure log message has line break.414 bp->m_dap.SendOutput(OutputType::Console, output.c_str());415 416 // Do not stop.417 return false;418}419 420} // namespace lldb_dap421