diff options
author | 2020-08-03 14:33:06 +0000 | |
---|---|---|
committer | 2020-08-03 14:33:06 +0000 | |
commit | 061da546b983eb767bad15e67af1174fb0bcf31c (patch) | |
tree | 83c78b820819d70aa40c36d90447978b300078c5 /gnu/llvm/lldb/source/Host/common | |
parent | Import LLVM 10.0.0 release including clang, lld and lldb. (diff) | |
download | wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.tar.xz wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.zip |
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom
tested by plenty
Diffstat (limited to 'gnu/llvm/lldb/source/Host/common')
33 files changed, 9653 insertions, 0 deletions
diff --git a/gnu/llvm/lldb/source/Host/common/Editline.cpp b/gnu/llvm/lldb/source/Host/common/Editline.cpp new file mode 100644 index 00000000000..5fd5a0cfc7f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/Editline.cpp @@ -0,0 +1,1503 @@ +//===-- Editline.cpp --------------------------------------------*- C++ -*-===// +// +// 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 <iomanip> +#include <iostream> +#include <limits.h> + +#include "lldb/Host/ConnectionFileDescriptor.h" +#include "lldb/Host/Editline.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Utility/CompletionRequest.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/SelectHelper.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/Utility/Timeout.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Threading.h" + +using namespace lldb_private; +using namespace lldb_private::line_editor; + +// Workaround for what looks like an OS X-specific issue, but other platforms +// may benefit from something similar if issues arise. The libedit library +// doesn't explicitly initialize the curses termcap library, which it gets away +// with until TERM is set to VT100 where it stumbles over an implementation +// assumption that may not exist on other platforms. The setupterm() function +// would normally require headers that don't work gracefully in this context, +// so the function declaraction has been hoisted here. +#if defined(__APPLE__) +extern "C" { +int setupterm(char *term, int fildes, int *errret); +} +#define USE_SETUPTERM_WORKAROUND +#endif + +// Editline uses careful cursor management to achieve the illusion of editing a +// multi-line block of text with a single line editor. Preserving this +// illusion requires fairly careful management of cursor state. Read and +// understand the relationship between DisplayInput(), MoveCursor(), +// SetCurrentLine(), and SaveEditedLine() before making changes. + +#define ESCAPE "\x1b" +#define ANSI_FAINT ESCAPE "[2m" +#define ANSI_UNFAINT ESCAPE "[22m" +#define ANSI_CLEAR_BELOW ESCAPE "[J" +#define ANSI_CLEAR_RIGHT ESCAPE "[K" +#define ANSI_SET_COLUMN_N ESCAPE "[%dG" +#define ANSI_UP_N_ROWS ESCAPE "[%dA" +#define ANSI_DOWN_N_ROWS ESCAPE "[%dB" + +#if LLDB_EDITLINE_USE_WCHAR + +#define EditLineConstString(str) L##str +#define EditLineStringFormatSpec "%ls" + +#else + +#define EditLineConstString(str) str +#define EditLineStringFormatSpec "%s" + +// use #defines so wide version functions and structs will resolve to old +// versions for case of libedit not built with wide char support +#define history_w history +#define history_winit history_init +#define history_wend history_end +#define HistoryW History +#define HistEventW HistEvent +#define LineInfoW LineInfo + +#define el_wgets el_gets +#define el_wgetc el_getc +#define el_wpush el_push +#define el_wparse el_parse +#define el_wset el_set +#define el_wget el_get +#define el_wline el_line +#define el_winsertstr el_insertstr +#define el_wdeletestr el_deletestr + +#endif // #if LLDB_EDITLINE_USE_WCHAR + +bool IsOnlySpaces(const EditLineStringType &content) { + for (wchar_t ch : content) { + if (ch != EditLineCharType(' ')) + return false; + } + return true; +} + +static int GetOperation(HistoryOperation op) { + // The naming used by editline for the history operations is counter + // intuitive to how it's used here. + // + // - The H_PREV operation returns the previous element in the history, which + // is newer than the current one. + // + // - The H_NEXT operation returns the next element in the history, which is + // older than the current one. + // + // The naming of the enum entries match the semantic meaning. + switch(op) { + case HistoryOperation::Oldest: + return H_FIRST; + case HistoryOperation::Older: + return H_NEXT; + case HistoryOperation::Current: + return H_CURR; + case HistoryOperation::Newer: + return H_PREV; + case HistoryOperation::Newest: + return H_LAST; + } + llvm_unreachable("Fully covered switch!"); +} + + +EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) { + EditLineStringStreamType combined_stream; + for (EditLineStringType line : lines) { + combined_stream << line.c_str() << "\n"; + } + return combined_stream.str(); +} + +std::vector<EditLineStringType> SplitLines(const EditLineStringType &input) { + std::vector<EditLineStringType> result; + size_t start = 0; + while (start < input.length()) { + size_t end = input.find('\n', start); + if (end == std::string::npos) { + result.insert(result.end(), input.substr(start)); + break; + } + result.insert(result.end(), input.substr(start, end - start)); + start = end + 1; + } + return result; +} + +EditLineStringType FixIndentation(const EditLineStringType &line, + int indent_correction) { + if (indent_correction == 0) + return line; + if (indent_correction < 0) + return line.substr(-indent_correction); + return EditLineStringType(indent_correction, EditLineCharType(' ')) + line; +} + +int GetIndentation(const EditLineStringType &line) { + int space_count = 0; + for (EditLineCharType ch : line) { + if (ch != EditLineCharType(' ')) + break; + ++space_count; + } + return space_count; +} + +bool IsInputPending(FILE *file) { + // FIXME: This will be broken on Windows if we ever re-enable Editline. You + // can't use select + // on something that isn't a socket. This will have to be re-written to not + // use a FILE*, but instead use some kind of yet-to-be-created abstraction + // that select-like functionality on non-socket objects. + const int fd = fileno(file); + SelectHelper select_helper; + select_helper.SetTimeout(std::chrono::microseconds(0)); + select_helper.FDSetRead(fd); + return select_helper.Select().Success(); +} + +namespace lldb_private { +namespace line_editor { +typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP; + +// EditlineHistory objects are sometimes shared between multiple Editline +// instances with the same program name. + +class EditlineHistory { +private: + // Use static GetHistory() function to get a EditlineHistorySP to one of + // these objects + EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries) + : m_history(nullptr), m_event(), m_prefix(prefix), m_path() { + m_history = history_winit(); + history_w(m_history, &m_event, H_SETSIZE, size); + if (unique_entries) + history_w(m_history, &m_event, H_SETUNIQUE, 1); + } + + const char *GetHistoryFilePath() { + // Compute the history path lazily. + if (m_path.empty() && m_history && !m_prefix.empty()) { + llvm::SmallString<128> lldb_history_file; + llvm::sys::path::home_directory(lldb_history_file); + llvm::sys::path::append(lldb_history_file, ".lldb"); + + // LLDB stores its history in ~/.lldb/. If for some reason this directory + // isn't writable or cannot be created, history won't be available. + if (!llvm::sys::fs::create_directory(lldb_history_file)) { +#if LLDB_EDITLINE_USE_WCHAR + std::string filename = m_prefix + "-widehistory"; +#else + std::string filename = m_prefix + "-history"; +#endif + llvm::sys::path::append(lldb_history_file, filename); + m_path = lldb_history_file.str(); + } + } + + if (m_path.empty()) + return nullptr; + + return m_path.c_str(); + } + +public: + ~EditlineHistory() { + Save(); + + if (m_history) { + history_wend(m_history); + m_history = nullptr; + } + } + + static EditlineHistorySP GetHistory(const std::string &prefix) { + typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; + static std::recursive_mutex g_mutex; + static WeakHistoryMap g_weak_map; + std::lock_guard<std::recursive_mutex> guard(g_mutex); + WeakHistoryMap::const_iterator pos = g_weak_map.find(prefix); + EditlineHistorySP history_sp; + if (pos != g_weak_map.end()) { + history_sp = pos->second.lock(); + if (history_sp) + return history_sp; + g_weak_map.erase(pos); + } + history_sp.reset(new EditlineHistory(prefix, 800, true)); + g_weak_map[prefix] = history_sp; + return history_sp; + } + + bool IsValid() const { return m_history != nullptr; } + + HistoryW *GetHistoryPtr() { return m_history; } + + void Enter(const EditLineCharType *line_cstr) { + if (m_history) + history_w(m_history, &m_event, H_ENTER, line_cstr); + } + + bool Load() { + if (m_history) { + const char *path = GetHistoryFilePath(); + if (path) { + history_w(m_history, &m_event, H_LOAD, path); + return true; + } + } + return false; + } + + bool Save() { + if (m_history) { + const char *path = GetHistoryFilePath(); + if (path) { + history_w(m_history, &m_event, H_SAVE, path); + return true; + } + } + return false; + } + +protected: + HistoryW *m_history; // The history object + HistEventW m_event; // The history event needed to contain all history events + std::string m_prefix; // The prefix name (usually the editline program name) + // to use when loading/saving history + std::string m_path; // Path to the history file +}; +} +} + +// Editline private methods + +void Editline::SetBaseLineNumber(int line_number) { + std::stringstream line_number_stream; + line_number_stream << line_number; + m_base_line_number = line_number; + m_line_number_digits = + std::max(3, (int)line_number_stream.str().length() + 1); +} + +std::string Editline::PromptForIndex(int line_index) { + bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0; + std::string prompt = m_set_prompt; + if (use_line_numbers && prompt.length() == 0) { + prompt = ": "; + } + std::string continuation_prompt = prompt; + if (m_set_continuation_prompt.length() > 0) { + continuation_prompt = m_set_continuation_prompt; + + // Ensure that both prompts are the same length through space padding + while (continuation_prompt.length() < prompt.length()) { + continuation_prompt += ' '; + } + while (prompt.length() < continuation_prompt.length()) { + prompt += ' '; + } + } + + if (use_line_numbers) { + StreamString prompt_stream; + prompt_stream.Printf( + "%*d%s", m_line_number_digits, m_base_line_number + line_index, + (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str()); + return std::move(prompt_stream.GetString()); + } + return (line_index == 0) ? prompt : continuation_prompt; +} + +void Editline::SetCurrentLine(int line_index) { + m_current_line_index = line_index; + m_current_prompt = PromptForIndex(line_index); +} + +int Editline::GetPromptWidth() { return (int)PromptForIndex(0).length(); } + +bool Editline::IsEmacs() { + const char *editor; + el_get(m_editline, EL_EDITOR, &editor); + return editor[0] == 'e'; +} + +bool Editline::IsOnlySpaces() { + const LineInfoW *info = el_wline(m_editline); + for (const EditLineCharType *character = info->buffer; + character < info->lastchar; character++) { + if (*character != ' ') + return false; + } + return true; +} + +int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) { + int line = 0; + if (location == CursorLocation::EditingPrompt || + location == CursorLocation::BlockEnd || + location == CursorLocation::EditingCursor) { + for (unsigned index = 0; index < m_current_line_index; index++) { + line += CountRowsForLine(m_input_lines[index]); + } + if (location == CursorLocation::EditingCursor) { + line += cursor_row; + } else if (location == CursorLocation::BlockEnd) { + for (unsigned index = m_current_line_index; index < m_input_lines.size(); + index++) { + line += CountRowsForLine(m_input_lines[index]); + } + --line; + } + } + return line; +} + +void Editline::MoveCursor(CursorLocation from, CursorLocation to) { + const LineInfoW *info = el_wline(m_editline); + int editline_cursor_position = + (int)((info->cursor - info->buffer) + GetPromptWidth()); + int editline_cursor_row = editline_cursor_position / m_terminal_width; + + // Determine relative starting and ending lines + int fromLine = GetLineIndexForLocation(from, editline_cursor_row); + int toLine = GetLineIndexForLocation(to, editline_cursor_row); + if (toLine != fromLine) { + fprintf(m_output_file, + (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, + std::abs(toLine - fromLine)); + } + + // Determine target column + int toColumn = 1; + if (to == CursorLocation::EditingCursor) { + toColumn = + editline_cursor_position - (editline_cursor_row * m_terminal_width) + 1; + } else if (to == CursorLocation::BlockEnd && !m_input_lines.empty()) { + toColumn = + ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % + 80) + + 1; + } + fprintf(m_output_file, ANSI_SET_COLUMN_N, toColumn); +} + +void Editline::DisplayInput(int firstIndex) { + fprintf(m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1); + int line_count = (int)m_input_lines.size(); + const char *faint = m_color_prompts ? ANSI_FAINT : ""; + const char *unfaint = m_color_prompts ? ANSI_UNFAINT : ""; + + for (int index = firstIndex; index < line_count; index++) { + fprintf(m_output_file, "%s" + "%s" + "%s" EditLineStringFormatSpec " ", + faint, PromptForIndex(index).c_str(), unfaint, + m_input_lines[index].c_str()); + if (index < line_count - 1) + fprintf(m_output_file, "\n"); + } +} + +int Editline::CountRowsForLine(const EditLineStringType &content) { + auto prompt = + PromptForIndex(0); // Prompt width is constant during an edit session + int line_length = (int)(content.length() + prompt.length()); + return (line_length / m_terminal_width) + 1; +} + +void Editline::SaveEditedLine() { + const LineInfoW *info = el_wline(m_editline); + m_input_lines[m_current_line_index] = + EditLineStringType(info->buffer, info->lastchar - info->buffer); +} + +StringList Editline::GetInputAsStringList(int line_count) { + StringList lines; + for (EditLineStringType line : m_input_lines) { + if (line_count == 0) + break; +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString(m_utf8conv.to_bytes(line)); +#else + lines.AppendString(line); +#endif + --line_count; + } + return lines; +} + +unsigned char Editline::RecallHistory(HistoryOperation op) { + assert(op == HistoryOperation::Older || op == HistoryOperation::Newer); + if (!m_history_sp || !m_history_sp->IsValid()) + return CC_ERROR; + + HistoryW *pHistory = m_history_sp->GetHistoryPtr(); + HistEventW history_event; + std::vector<EditLineStringType> new_input_lines; + + // Treat moving from the "live" entry differently + if (!m_in_history) { + switch (op) { + case HistoryOperation::Newer: + return CC_ERROR; // Can't go newer than the "live" entry + case HistoryOperation::Older: { + if (history_w(pHistory, &history_event, + GetOperation(HistoryOperation::Newest)) == -1) + return CC_ERROR; + // Save any edits to the "live" entry in case we return by moving forward + // in history (it would be more bash-like to save over any current entry, + // but libedit doesn't offer the ability to add entries anywhere except + // the end.) + SaveEditedLine(); + m_live_history_lines = m_input_lines; + m_in_history = true; + } break; + default: + llvm_unreachable("unsupported history direction"); + } + } else { + if (history_w(pHistory, &history_event, GetOperation(op)) == -1) { + switch (op) { + case HistoryOperation::Older: + // Can't move earlier than the earliest entry. + return CC_ERROR; + case HistoryOperation::Newer: + // Moving to newer-than-the-newest entry yields the "live" entry. + new_input_lines = m_live_history_lines; + m_in_history = false; + break; + default: + llvm_unreachable("unsupported history direction"); + } + } + } + + // If we're pulling the lines from history, split them apart + if (m_in_history) + new_input_lines = SplitLines(history_event.str); + + // Erase the current edit session and replace it with a new one + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + m_input_lines = new_input_lines; + DisplayInput(); + + // Prepare to edit the last line when moving to previous entry, or the first + // line when moving to next entry + switch (op) { + case HistoryOperation::Older: + m_current_line_index = (int)m_input_lines.size() - 1; + break; + case HistoryOperation::Newer: + m_current_line_index = 0; + break; + default: + llvm_unreachable("unsupported history direction"); + } + SetCurrentLine(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +int Editline::GetCharacter(EditLineGetCharType *c) { + const LineInfoW *info = el_wline(m_editline); + + // Paint a faint version of the desired prompt over the version libedit draws + // (will only be requested if colors are supported) + if (m_needs_prompt_repaint) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + fprintf(m_output_file, "%s" + "%s" + "%s", + ANSI_FAINT, Prompt(), ANSI_UNFAINT); + MoveCursor(CursorLocation::EditingPrompt, CursorLocation::EditingCursor); + m_needs_prompt_repaint = false; + } + + if (m_multiline_enabled) { + // Detect when the number of rows used for this input line changes due to + // an edit + int lineLength = (int)((info->lastchar - info->buffer) + GetPromptWidth()); + int new_line_rows = (lineLength / m_terminal_width) + 1; + if (m_current_line_rows != -1 && new_line_rows != m_current_line_rows) { + // Respond by repainting the current state from this line on + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + SaveEditedLine(); + DisplayInput(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + } + m_current_line_rows = new_line_rows; + } + + // Read an actual character + while (true) { + lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; + char ch = 0; + + // This mutex is locked by our caller (GetLine). Unlock it while we read a + // character (blocking operation), so we do not hold the mutex + // indefinitely. This gives a chance for someone to interrupt us. After + // Read returns, immediately lock the mutex again and check if we were + // interrupted. + m_output_mutex.unlock(); + int read_count = + m_input_connection.Read(&ch, 1, llvm::None, status, nullptr); + m_output_mutex.lock(); + if (m_editor_status == EditorStatus::Interrupted) { + while (read_count > 0 && status == lldb::eConnectionStatusSuccess) + read_count = + m_input_connection.Read(&ch, 1, llvm::None, status, nullptr); + lldbassert(status == lldb::eConnectionStatusInterrupted); + return 0; + } + + if (read_count) { + if (CompleteCharacter(ch, *c)) + return 1; + } else { + switch (status) { + case lldb::eConnectionStatusSuccess: // Success + break; + + case lldb::eConnectionStatusInterrupted: + llvm_unreachable("Interrupts should have been handled above."); + + case lldb::eConnectionStatusError: // Check GetError() for details + case lldb::eConnectionStatusTimedOut: // Request timed out + case lldb::eConnectionStatusEndOfFile: // End-of-file encountered + case lldb::eConnectionStatusNoConnection: // No connection + case lldb::eConnectionStatusLostConnection: // Lost connection while + // connected to a valid + // connection + m_editor_status = EditorStatus::EndOfInput; + return 0; + } + } + } +} + +const char *Editline::Prompt() { + if (m_color_prompts) + m_needs_prompt_repaint = true; + return m_current_prompt.c_str(); +} + +unsigned char Editline::BreakLineCommand(int ch) { + // Preserve any content beyond the cursor, truncate and save the current line + const LineInfoW *info = el_wline(m_editline); + auto current_line = + EditLineStringType(info->buffer, info->cursor - info->buffer); + auto new_line_fragment = + EditLineStringType(info->cursor, info->lastchar - info->cursor); + m_input_lines[m_current_line_index] = current_line; + + // Ignore whitespace-only extra fragments when breaking a line + if (::IsOnlySpaces(new_line_fragment)) + new_line_fragment = EditLineConstString(""); + + // Establish the new cursor position at the start of a line when inserting a + // line break + m_revert_cursor_index = 0; + + // Don't perform automatic formatting when pasting + if (!IsInputPending(m_input_file)) { + // Apply smart indentation + if (m_fix_indentation_callback) { + StringList lines = GetInputAsStringList(m_current_line_index + 1); +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString(m_utf8conv.to_bytes(new_line_fragment)); +#else + lines.AppendString(new_line_fragment); +#endif + + int indent_correction = m_fix_indentation_callback( + this, lines, 0, m_fix_indentation_callback_baton); + new_line_fragment = FixIndentation(new_line_fragment, indent_correction); + m_revert_cursor_index = GetIndentation(new_line_fragment); + } + } + + // Insert the new line and repaint everything from the split line on down + m_input_lines.insert(m_input_lines.begin() + m_current_line_index + 1, + new_line_fragment); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + DisplayInput(m_current_line_index); + + // Reposition the cursor to the right line and prepare to edit the new line + SetCurrentLine(m_current_line_index + 1); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +unsigned char Editline::EndOrAddLineCommand(int ch) { + // Don't perform end of input detection when pasting, always treat this as a + // line break + if (IsInputPending(m_input_file)) { + return BreakLineCommand(ch); + } + + // Save any edits to this line + SaveEditedLine(); + + // If this is the end of the last line, consider whether to add a line + // instead + const LineInfoW *info = el_wline(m_editline); + if (m_current_line_index == m_input_lines.size() - 1 && + info->cursor == info->lastchar) { + if (m_is_input_complete_callback) { + auto lines = GetInputAsStringList(); + if (!m_is_input_complete_callback(this, lines, + m_is_input_complete_callback_baton)) { + return BreakLineCommand(ch); + } + + // The completion test is allowed to change the input lines when complete + m_input_lines.clear(); + for (unsigned index = 0; index < lines.GetSize(); index++) { +#if LLDB_EDITLINE_USE_WCHAR + m_input_lines.insert(m_input_lines.end(), + m_utf8conv.from_bytes(lines[index])); +#else + m_input_lines.insert(m_input_lines.end(), lines[index]); +#endif + } + } + } + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd); + fprintf(m_output_file, "\n"); + m_editor_status = EditorStatus::Complete; + return CC_NEWLINE; +} + +unsigned char Editline::DeleteNextCharCommand(int ch) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + + // Just delete the next character normally if possible + if (info->cursor < info->lastchar) { + info->cursor++; + el_deletestr(m_editline, 1); + return CC_REFRESH; + } + + // Fail when at the end of the last line, except when ^D is pressed on the + // line is empty, in which case it is treated as EOF + if (m_current_line_index == m_input_lines.size() - 1) { + if (ch == 4 && info->buffer == info->lastchar) { + fprintf(m_output_file, "^D\n"); + m_editor_status = EditorStatus::EndOfInput; + return CC_EOF; + } + return CC_ERROR; + } + + // Prepare to combine this line with the one below + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + + // Insert the next line of text at the cursor and restore the cursor position + const EditLineCharType *cursor = info->cursor; + el_winsertstr(m_editline, m_input_lines[m_current_line_index + 1].c_str()); + info->cursor = cursor; + SaveEditedLine(); + + // Delete the extra line + m_input_lines.erase(m_input_lines.begin() + m_current_line_index + 1); + + // Clear and repaint from this line on down + DisplayInput(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + return CC_REFRESH; +} + +unsigned char Editline::DeletePreviousCharCommand(int ch) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + + // Just delete the previous character normally when not at the start of a + // line + if (info->cursor > info->buffer) { + el_deletestr(m_editline, 1); + return CC_REFRESH; + } + + // No prior line and no prior character? Let the user know + if (m_current_line_index == 0) + return CC_ERROR; + + // No prior character, but prior line? Combine with the line above + SaveEditedLine(); + SetCurrentLine(m_current_line_index - 1); + auto priorLine = m_input_lines[m_current_line_index]; + m_input_lines.erase(m_input_lines.begin() + m_current_line_index); + m_input_lines[m_current_line_index] = + priorLine + m_input_lines[m_current_line_index]; + + // Repaint from the new line down + fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, + CountRowsForLine(priorLine), 1); + DisplayInput(m_current_line_index); + + // Put the cursor back where libedit expects it to be before returning to + // editing by telling libedit about the newly inserted text + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + el_winsertstr(m_editline, priorLine.c_str()); + return CC_REDISPLAY; +} + +unsigned char Editline::PreviousLineCommand(int ch) { + SaveEditedLine(); + + if (m_current_line_index == 0) { + return RecallHistory(HistoryOperation::Older); + } + + // Start from a known location + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + + // Treat moving up from a blank last line as a deletion of that line + if (m_current_line_index == m_input_lines.size() - 1 && IsOnlySpaces()) { + m_input_lines.erase(m_input_lines.begin() + m_current_line_index); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + } + + SetCurrentLine(m_current_line_index - 1); + fprintf(m_output_file, ANSI_UP_N_ROWS ANSI_SET_COLUMN_N, + CountRowsForLine(m_input_lines[m_current_line_index]), 1); + return CC_NEWLINE; +} + +unsigned char Editline::NextLineCommand(int ch) { + SaveEditedLine(); + + // Handle attempts to move down from the last line + if (m_current_line_index == m_input_lines.size() - 1) { + // Don't add an extra line if the existing last line is blank, move through + // history instead + if (IsOnlySpaces()) { + return RecallHistory(HistoryOperation::Newer); + } + + // Determine indentation for the new line + int indentation = 0; + if (m_fix_indentation_callback) { + StringList lines = GetInputAsStringList(); + lines.AppendString(""); + indentation = m_fix_indentation_callback( + this, lines, 0, m_fix_indentation_callback_baton); + } + m_input_lines.insert( + m_input_lines.end(), + EditLineStringType(indentation, EditLineCharType(' '))); + } + + // Move down past the current line using newlines to force scrolling if + // needed + SetCurrentLine(m_current_line_index + 1); + const LineInfoW *info = el_wline(m_editline); + int cursor_position = (int)((info->cursor - info->buffer) + GetPromptWidth()); + int cursor_row = cursor_position / m_terminal_width; + for (int line_count = 0; line_count < m_current_line_rows - cursor_row; + line_count++) { + fprintf(m_output_file, "\n"); + } + return CC_NEWLINE; +} + +unsigned char Editline::PreviousHistoryCommand(int ch) { + SaveEditedLine(); + + return RecallHistory(HistoryOperation::Older); +} + +unsigned char Editline::NextHistoryCommand(int ch) { + SaveEditedLine(); + + return RecallHistory(HistoryOperation::Newer); +} + +unsigned char Editline::FixIndentationCommand(int ch) { + if (!m_fix_indentation_callback) + return CC_NORM; + + // Insert the character typed before proceeding + EditLineCharType inserted[] = {(EditLineCharType)ch, 0}; + el_winsertstr(m_editline, inserted); + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + int cursor_position = info->cursor - info->buffer; + + // Save the edits and determine the correct indentation level + SaveEditedLine(); + StringList lines = GetInputAsStringList(m_current_line_index + 1); + int indent_correction = m_fix_indentation_callback( + this, lines, cursor_position, m_fix_indentation_callback_baton); + + // If it is already correct no special work is needed + if (indent_correction == 0) + return CC_REFRESH; + + // Change the indentation level of the line + std::string currentLine = lines.GetStringAtIndex(m_current_line_index); + if (indent_correction > 0) { + currentLine = currentLine.insert(0, indent_correction, ' '); + } else { + currentLine = currentLine.erase(0, -indent_correction); + } +#if LLDB_EDITLINE_USE_WCHAR + m_input_lines[m_current_line_index] = m_utf8conv.from_bytes(currentLine); +#else + m_input_lines[m_current_line_index] = currentLine; +#endif + + // Update the display to reflect the change + MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + DisplayInput(m_current_line_index); + + // Reposition the cursor back on the original line and prepare to restart + // editing with a new cursor position + SetCurrentLine(m_current_line_index); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + m_revert_cursor_index = cursor_position + indent_correction; + return CC_NEWLINE; +} + +unsigned char Editline::RevertLineCommand(int ch) { + el_winsertstr(m_editline, m_input_lines[m_current_line_index].c_str()); + if (m_revert_cursor_index >= 0) { + LineInfoW *info = const_cast<LineInfoW *>(el_wline(m_editline)); + info->cursor = info->buffer + m_revert_cursor_index; + if (info->cursor > info->lastchar) { + info->cursor = info->lastchar; + } + m_revert_cursor_index = -1; + } + return CC_REFRESH; +} + +unsigned char Editline::BufferStartCommand(int ch) { + SaveEditedLine(); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + SetCurrentLine(0); + m_revert_cursor_index = 0; + return CC_NEWLINE; +} + +unsigned char Editline::BufferEndCommand(int ch) { + SaveEditedLine(); + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockEnd); + SetCurrentLine((int)m_input_lines.size() - 1); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +/// Prints completions and their descriptions to the given file. Only the +/// completions in the interval [start, end) are printed. +static void +PrintCompletion(FILE *output_file, + llvm::ArrayRef<CompletionResult::Completion> results, + size_t max_len) { + for (const CompletionResult::Completion &c : results) { + fprintf(output_file, "\t%-*s", (int)max_len, c.GetCompletion().c_str()); + if (!c.GetDescription().empty()) + fprintf(output_file, " -- %s", c.GetDescription().c_str()); + fprintf(output_file, "\n"); + } +} + +static void +DisplayCompletions(::EditLine *editline, FILE *output_file, + llvm::ArrayRef<CompletionResult::Completion> results) { + assert(!results.empty()); + + fprintf(output_file, "\n" ANSI_CLEAR_BELOW "Available completions:\n"); + const size_t page_size = 40; + bool all = false; + + auto longest = + std::max_element(results.begin(), results.end(), [](auto &c1, auto &c2) { + return c1.GetCompletion().size() < c2.GetCompletion().size(); + }); + + const size_t max_len = longest->GetCompletion().size(); + + if (results.size() < page_size) { + PrintCompletion(output_file, results, max_len); + return; + } + + size_t cur_pos = 0; + while (cur_pos < results.size()) { + size_t remaining = results.size() - cur_pos; + size_t next_size = all ? remaining : std::min(page_size, remaining); + + PrintCompletion(output_file, results.slice(cur_pos, next_size), max_len); + + cur_pos += next_size; + + if (cur_pos >= results.size()) + break; + + fprintf(output_file, "More (Y/n/a): "); + char reply = 'n'; + int got_char = el_getc(editline, &reply); + fprintf(output_file, "\n"); + if (got_char == -1 || reply == 'n') + break; + if (reply == 'a') + all = true; + } +} + +unsigned char Editline::TabCommand(int ch) { + if (m_completion_callback == nullptr) + return CC_ERROR; + + const LineInfo *line_info = el_line(m_editline); + + llvm::StringRef line(line_info->buffer, + line_info->lastchar - line_info->buffer); + unsigned cursor_index = line_info->cursor - line_info->buffer; + CompletionResult result; + CompletionRequest request(line, cursor_index, result); + + m_completion_callback(request, m_completion_callback_baton); + + llvm::ArrayRef<CompletionResult::Completion> results = result.GetResults(); + + StringList completions; + result.GetMatches(completions); + + if (results.size() == 0) + return CC_ERROR; + + if (results.size() == 1) { + CompletionResult::Completion completion = results.front(); + switch (completion.GetMode()) { + case CompletionMode::Normal: { + std::string to_add = completion.GetCompletion(); + to_add = to_add.substr(request.GetCursorArgumentPrefix().size()); + if (request.GetParsedArg().IsQuoted()) + to_add.push_back(request.GetParsedArg().GetQuoteChar()); + to_add.push_back(' '); + el_insertstr(m_editline, to_add.c_str()); + break; + } + case CompletionMode::Partial: { + std::string to_add = completion.GetCompletion(); + to_add = to_add.substr(request.GetCursorArgumentPrefix().size()); + el_insertstr(m_editline, to_add.c_str()); + break; + } + case CompletionMode::RewriteLine: { + el_deletestr(m_editline, line_info->cursor - line_info->buffer); + el_insertstr(m_editline, completion.GetCompletion().c_str()); + break; + } + } + return CC_REDISPLAY; + } + + // If we get a longer match display that first. + std::string longest_prefix = completions.LongestCommonPrefix(); + if (!longest_prefix.empty()) + longest_prefix = + longest_prefix.substr(request.GetCursorArgumentPrefix().size()); + if (!longest_prefix.empty()) { + el_insertstr(m_editline, longest_prefix.c_str()); + return CC_REDISPLAY; + } + + DisplayCompletions(m_editline, m_output_file, results); + + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + return CC_REDISPLAY; +} + +void Editline::ConfigureEditor(bool multiline) { + if (m_editline && m_multiline_enabled == multiline) + return; + m_multiline_enabled = multiline; + + if (m_editline) { + // Disable edit mode to stop the terminal from flushing all input during + // the call to el_end() since we expect to have multiple editline instances + // in this program. + el_set(m_editline, EL_EDITMODE, 0); + el_end(m_editline); + } + + m_editline = + el_init(m_editor_name.c_str(), m_input_file, m_output_file, m_error_file); + TerminalSizeChanged(); + + if (m_history_sp && m_history_sp->IsValid()) { + if (!m_history_sp->Load()) { + fputs("Could not load history file\n.", m_output_file); + } + el_wset(m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); + } + el_set(m_editline, EL_CLIENTDATA, this); + el_set(m_editline, EL_SIGNAL, 0); + el_set(m_editline, EL_EDITOR, "emacs"); + el_set(m_editline, EL_PROMPT, + (EditlinePromptCallbackType)([](EditLine *editline) { + return Editline::InstanceFor(editline)->Prompt(); + })); + + el_wset(m_editline, EL_GETCFN, (EditlineGetCharCallbackType)([]( + EditLine *editline, EditLineGetCharType *c) { + return Editline::InstanceFor(editline)->GetCharacter(c); + })); + + // Commands used for multiline support, registered whether or not they're + // used + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-break-line"), + EditLineConstString("Insert a line break"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BreakLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-end-or-add-line"), + EditLineConstString("End editing or continue when incomplete"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-delete-next-char"), + EditLineConstString("Delete next character"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch); + })); + el_wset( + m_editline, EL_ADDFN, EditLineConstString("lldb-delete-previous-char"), + EditLineConstString("Delete previous character"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-previous-line"), + EditLineConstString("Move to previous line"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-line"), + EditLineConstString("Move to next line"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextLineCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-previous-history"), + EditLineConstString("Move to previous history"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-next-history"), + EditLineConstString("Move to next history"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextHistoryCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-buffer-start"), + EditLineConstString("Move to start of buffer"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferStartCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-buffer-end"), + EditLineConstString("Move to end of buffer"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferEndCommand(ch); + })); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-fix-indentation"), + EditLineConstString("Fix line indentation"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->FixIndentationCommand(ch); + })); + + // Register the complete callback under two names for compatibility with + // older clients using custom .editrc files (largely because libedit has a + // bad bug where if you have a bind command that tries to bind to a function + // name that doesn't exist, it can corrupt the heap and crash your process + // later.) + EditlineCommandCallbackType complete_callback = [](EditLine *editline, + int ch) { + return Editline::InstanceFor(editline)->TabCommand(ch); + }; + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-complete"), + EditLineConstString("Invoke completion"), complete_callback); + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb_complete"), + EditLineConstString("Invoke completion"), complete_callback); + + // General bindings we don't mind being overridden + if (!multiline) { + el_set(m_editline, EL_BIND, "^r", "em-inc-search-prev", + NULL); // Cycle through backwards search, entering string + } + el_set(m_editline, EL_BIND, "^w", "ed-delete-prev-word", + NULL); // Delete previous word, behave like bash in emacs mode + el_set(m_editline, EL_BIND, "\t", "lldb-complete", + NULL); // Bind TAB to auto complete + + // Allow ctrl-left-arrow and ctrl-right-arrow for navigation, behave like + // bash in emacs mode. + el_set(m_editline, EL_BIND, ESCAPE "[1;5C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;5D", "ed-prev-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[5C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[5D", "ed-prev-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[C", "em-next-word", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[D", "ed-prev-word", NULL); + + // Allow user-specific customization prior to registering bindings we + // absolutely require + el_source(m_editline, nullptr); + + // Register an internal binding that external developers shouldn't use + el_wset(m_editline, EL_ADDFN, EditLineConstString("lldb-revert-line"), + EditLineConstString("Revert line to saved state"), + (EditlineCommandCallbackType)([](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->RevertLineCommand(ch); + })); + + // Register keys that perform auto-indent correction + if (m_fix_indentation_callback && m_fix_indentation_callback_chars) { + char bind_key[2] = {0, 0}; + const char *indent_chars = m_fix_indentation_callback_chars; + while (*indent_chars) { + bind_key[0] = *indent_chars; + el_set(m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL); + ++indent_chars; + } + } + + // Multi-line editor bindings + if (multiline) { + el_set(m_editline, EL_BIND, "\n", "lldb-end-or-add-line", NULL); + el_set(m_editline, EL_BIND, "\r", "lldb-end-or-add-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "\n", "lldb-break-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "\r", "lldb-break-line", NULL); + el_set(m_editline, EL_BIND, "^p", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, "^n", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "^?", "lldb-delete-previous-char", NULL); + el_set(m_editline, EL_BIND, "^d", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[3~", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[\\^", "lldb-revert-line", NULL); + + // Editor-specific bindings + if (IsEmacs()) { + el_set(m_editline, EL_BIND, ESCAPE "<", "lldb-buffer-start", NULL); + el_set(m_editline, EL_BIND, ESCAPE ">", "lldb-buffer-end", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[A", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[A", "lldb-previous-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE ESCAPE "[B", "lldb-next-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;3A", "lldb-previous-history", + NULL); + el_set(m_editline, EL_BIND, ESCAPE "[1;3B", "lldb-next-history", NULL); + } else { + el_set(m_editline, EL_BIND, "^H", "lldb-delete-previous-char", NULL); + + el_set(m_editline, EL_BIND, "-a", ESCAPE "[A", "lldb-previous-line", + NULL); + el_set(m_editline, EL_BIND, "-a", ESCAPE "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "-a", "x", "lldb-delete-next-char", NULL); + el_set(m_editline, EL_BIND, "-a", "^H", "lldb-delete-previous-char", + NULL); + el_set(m_editline, EL_BIND, "-a", "^?", "lldb-delete-previous-char", + NULL); + + // Escape is absorbed exiting edit mode, so re-register important + // sequences without the prefix + el_set(m_editline, EL_BIND, "-a", "[A", "lldb-previous-line", NULL); + el_set(m_editline, EL_BIND, "-a", "[B", "lldb-next-line", NULL); + el_set(m_editline, EL_BIND, "-a", "[\\^", "lldb-revert-line", NULL); + } + } +} + +// Editline public methods + +Editline *Editline::InstanceFor(EditLine *editline) { + Editline *editor; + el_get(editline, EL_CLIENTDATA, &editor); + return editor; +} + +Editline::Editline(const char *editline_name, FILE *input_file, + FILE *output_file, FILE *error_file, bool color_prompts) + : m_editor_status(EditorStatus::Complete), m_color_prompts(color_prompts), + m_input_file(input_file), m_output_file(output_file), + m_error_file(error_file), m_input_connection(fileno(input_file), false) { + // Get a shared history instance + m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; + m_history_sp = EditlineHistory::GetHistory(m_editor_name); + +#ifdef USE_SETUPTERM_WORKAROUND + if (m_output_file) { + const int term_fd = fileno(m_output_file); + if (term_fd != -1) { + static std::mutex *g_init_terminal_fds_mutex_ptr = nullptr; + static std::set<int> *g_init_terminal_fds_ptr = nullptr; + static llvm::once_flag g_once_flag; + llvm::call_once(g_once_flag, [&]() { + g_init_terminal_fds_mutex_ptr = + new std::mutex(); // NOTE: Leak to avoid C++ destructor chain issues + g_init_terminal_fds_ptr = new std::set<int>(); // NOTE: Leak to avoid + // C++ destructor chain + // issues + }); + + // We must make sure to initialize the terminal a given file descriptor + // only once. If we do this multiple times, we start leaking memory. + std::lock_guard<std::mutex> guard(*g_init_terminal_fds_mutex_ptr); + if (g_init_terminal_fds_ptr->find(term_fd) == + g_init_terminal_fds_ptr->end()) { + g_init_terminal_fds_ptr->insert(term_fd); + setupterm((char *)0, term_fd, (int *)0); + } + } + } +#endif +} + +Editline::~Editline() { + if (m_editline) { + // Disable edit mode to stop the terminal from flushing all input during + // the call to el_end() since we expect to have multiple editline instances + // in this program. + el_set(m_editline, EL_EDITMODE, 0); + el_end(m_editline); + m_editline = nullptr; + } + + // EditlineHistory objects are sometimes shared between multiple Editline + // instances with the same program name. So just release our shared pointer + // and if we are the last owner, it will save the history to the history save + // file automatically. + m_history_sp.reset(); +} + +void Editline::SetPrompt(const char *prompt) { + m_set_prompt = prompt == nullptr ? "" : prompt; +} + +void Editline::SetContinuationPrompt(const char *continuation_prompt) { + m_set_continuation_prompt = + continuation_prompt == nullptr ? "" : continuation_prompt; +} + +void Editline::TerminalSizeChanged() { + if (m_editline != nullptr) { + el_resize(m_editline); + int columns; + // This function is documenting as taking (const char *, void *) for the + // vararg part, but in reality in was consuming arguments until the first + // null pointer. This was fixed in libedit in April 2019 + // <http://mail-index.netbsd.org/source-changes/2019/04/26/msg105454.html>, + // but we're keeping the workaround until a version with that fix is more + // widely available. + if (el_get(m_editline, EL_GETTC, "co", &columns, nullptr) == 0) { + m_terminal_width = columns; + if (m_current_line_rows != -1) { + const LineInfoW *info = el_wline(m_editline); + int lineLength = + (int)((info->lastchar - info->buffer) + GetPromptWidth()); + m_current_line_rows = (lineLength / columns) + 1; + } + } else { + m_terminal_width = INT_MAX; + m_current_line_rows = 1; + } + } +} + +const char *Editline::GetPrompt() { return m_set_prompt.c_str(); } + +uint32_t Editline::GetCurrentLine() { return m_current_line_index; } + +bool Editline::Interrupt() { + bool result = true; + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + fprintf(m_output_file, "^C\n"); + result = m_input_connection.InterruptRead(); + } + m_editor_status = EditorStatus::Interrupted; + return result; +} + +bool Editline::Cancel() { + bool result = true; + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + result = m_input_connection.InterruptRead(); + } + m_editor_status = EditorStatus::Interrupted; + return result; +} + +void Editline::SetAutoCompleteCallback(CompleteCallbackType callback, + void *baton) { + m_completion_callback = callback; + m_completion_callback_baton = baton; +} + +void Editline::SetIsInputCompleteCallback(IsInputCompleteCallbackType callback, + void *baton) { + m_is_input_complete_callback = callback; + m_is_input_complete_callback_baton = baton; +} + +bool Editline::SetFixIndentationCallback(FixIndentationCallbackType callback, + void *baton, + const char *indent_chars) { + m_fix_indentation_callback = callback; + m_fix_indentation_callback_baton = baton; + m_fix_indentation_callback_chars = indent_chars; + return false; +} + +bool Editline::GetLine(std::string &line, bool &interrupted) { + ConfigureEditor(false); + m_input_lines = std::vector<EditLineStringType>(); + m_input_lines.insert(m_input_lines.begin(), EditLineConstString("")); + + std::lock_guard<std::mutex> guard(m_output_mutex); + + lldbassert(m_editor_status != EditorStatus::Editing); + if (m_editor_status == EditorStatus::Interrupted) { + m_editor_status = EditorStatus::Complete; + interrupted = true; + return true; + } + + SetCurrentLine(0); + m_in_history = false; + m_editor_status = EditorStatus::Editing; + m_revert_cursor_index = -1; + + int count; + auto input = el_wgets(m_editline, &count); + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) { + if (input == nullptr) { + fprintf(m_output_file, "\n"); + m_editor_status = EditorStatus::EndOfInput; + } else { + m_history_sp->Enter(input); +#if LLDB_EDITLINE_USE_WCHAR + line = m_utf8conv.to_bytes(SplitLines(input)[0]); +#else + line = SplitLines(input)[0]; +#endif + m_editor_status = EditorStatus::Complete; + } + } + return m_editor_status != EditorStatus::EndOfInput; +} + +bool Editline::GetLines(int first_line_number, StringList &lines, + bool &interrupted) { + ConfigureEditor(true); + + // Print the initial input lines, then move the cursor back up to the start + // of input + SetBaseLineNumber(first_line_number); + m_input_lines = std::vector<EditLineStringType>(); + m_input_lines.insert(m_input_lines.begin(), EditLineConstString("")); + + std::lock_guard<std::mutex> guard(m_output_mutex); + // Begin the line editing loop + DisplayInput(); + SetCurrentLine(0); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::BlockStart); + m_editor_status = EditorStatus::Editing; + m_in_history = false; + + m_revert_cursor_index = -1; + while (m_editor_status == EditorStatus::Editing) { + int count; + m_current_line_rows = -1; + el_wpush(m_editline, EditLineConstString( + "\x1b[^")); // Revert to the existing line content + el_wgets(m_editline, &count); + } + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) { + // Save the completed entry in history before returning + m_history_sp->Enter(CombineLines(m_input_lines).c_str()); + + lines = GetInputAsStringList(); + } + return m_editor_status != EditorStatus::EndOfInput; +} + +void Editline::PrintAsync(Stream *stream, const char *s, size_t len) { + std::lock_guard<std::mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + } + stream->Write(s, len); + stream->Flush(); + if (m_editor_status == EditorStatus::Editing) { + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + } +} + +bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) { +#if !LLDB_EDITLINE_USE_WCHAR + if (ch == (char)EOF) + return false; + + out = (unsigned char)ch; + return true; +#else + std::codecvt_utf8<wchar_t> cvt; + llvm::SmallString<4> input; + for (;;) { + const char *from_next; + wchar_t *to_next; + std::mbstate_t state = std::mbstate_t(); + input.push_back(ch); + switch (cvt.in(state, input.begin(), input.end(), from_next, &out, &out + 1, + to_next)) { + case std::codecvt_base::ok: + return out != (int)WEOF; + + case std::codecvt_base::error: + case std::codecvt_base::noconv: + return false; + + case std::codecvt_base::partial: + lldb::ConnectionStatus status; + size_t read_count = m_input_connection.Read( + &ch, 1, std::chrono::seconds(0), status, nullptr); + if (read_count == 0) + return false; + break; + } + } +#endif +} diff --git a/gnu/llvm/lldb/source/Host/common/File.cpp b/gnu/llvm/lldb/source/Host/common/File.cpp new file mode 100644 index 00000000000..7850222376f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/File.cpp @@ -0,0 +1,758 @@ +//===-- File.cpp ------------------------------------------------*- C++ -*-===// +// +// 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/File.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> + +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#else +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <termios.h> +#include <unistd.h> +#endif + +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Process.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; +using llvm::Expected; + +Expected<const char *> +File::GetStreamOpenModeFromOptions(File::OpenOptions options) { + if (options & File::eOpenOptionAppend) { + if (options & File::eOpenOptionRead) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "a+x"; + else + return "a+"; + } else if (options & File::eOpenOptionWrite) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "ax"; + else + return "a"; + } + } else if (options & File::eOpenOptionRead && + options & File::eOpenOptionWrite) { + if (options & File::eOpenOptionCanCreate) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "w+x"; + else + return "w+"; + } else + return "r+"; + } else if (options & File::eOpenOptionRead) { + return "r"; + } else if (options & File::eOpenOptionWrite) { + return "w"; + } + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "invalid options, cannot convert to mode string"); +} + +Expected<File::OpenOptions> File::GetOptionsFromMode(llvm::StringRef mode) { + OpenOptions opts = + llvm::StringSwitch<OpenOptions>(mode) + .Cases("r", "rb", eOpenOptionRead) + .Cases("w", "wb", eOpenOptionWrite) + .Cases("a", "ab", + eOpenOptionWrite | eOpenOptionAppend | eOpenOptionCanCreate) + .Cases("r+", "rb+", "r+b", eOpenOptionRead | eOpenOptionWrite) + .Cases("w+", "wb+", "w+b", + eOpenOptionRead | eOpenOptionWrite | eOpenOptionCanCreate | + eOpenOptionTruncate) + .Cases("a+", "ab+", "a+b", + eOpenOptionRead | eOpenOptionWrite | eOpenOptionAppend | + eOpenOptionCanCreate) + .Default(OpenOptions()); + if (opts) + return opts; + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "invalid mode, cannot convert to File::OpenOptions"); +} + +int File::kInvalidDescriptor = -1; +FILE *File::kInvalidStream = nullptr; + +Status File::Read(void *buf, size_t &num_bytes) { + return std::error_code(ENOTSUP, std::system_category()); +} +Status File::Write(const void *buf, size_t &num_bytes) { + return std::error_code(ENOTSUP, std::system_category()); +} + +bool File::IsValid() const { return false; } + +Status File::Close() { return Flush(); } + +IOObject::WaitableHandle File::GetWaitableHandle() { + return IOObject::kInvalidHandleValue; +} + +Status File::GetFileSpec(FileSpec &file_spec) const { + file_spec.Clear(); + return std::error_code(ENOTSUP, std::system_category()); +} + +int File::GetDescriptor() const { return kInvalidDescriptor; } + +FILE *File::GetStream() { return nullptr; } + +off_t File::SeekFromStart(off_t offset, Status *error_ptr) { + if (error_ptr) + *error_ptr = std::error_code(ENOTSUP, std::system_category()); + return -1; +} + +off_t File::SeekFromCurrent(off_t offset, Status *error_ptr) { + if (error_ptr) + *error_ptr = std::error_code(ENOTSUP, std::system_category()); + return -1; +} + +off_t File::SeekFromEnd(off_t offset, Status *error_ptr) { + if (error_ptr) + *error_ptr = std::error_code(ENOTSUP, std::system_category()); + return -1; +} + +Status File::Read(void *dst, size_t &num_bytes, off_t &offset) { + return std::error_code(ENOTSUP, std::system_category()); +} + +Status File::Write(const void *src, size_t &num_bytes, off_t &offset) { + return std::error_code(ENOTSUP, std::system_category()); +} + +Status File::Flush() { return Status(); } + +Status File::Sync() { return Flush(); } + +void File::CalculateInteractiveAndTerminal() { + const int fd = GetDescriptor(); + if (!DescriptorIsValid(fd)) { + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; + m_supports_colors = eLazyBoolNo; + return; + } + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; +#if defined(_WIN32) + if (_isatty(fd)) { + m_is_interactive = eLazyBoolYes; + m_is_real_terminal = eLazyBoolYes; +#if defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) + m_supports_colors = eLazyBoolYes; +#endif + } +#else + if (isatty(fd)) { + m_is_interactive = eLazyBoolYes; + struct winsize window_size; + if (::ioctl(fd, TIOCGWINSZ, &window_size) == 0) { + if (window_size.ws_col > 0) { + m_is_real_terminal = eLazyBoolYes; + if (llvm::sys::Process::FileDescriptorHasColors(fd)) + m_supports_colors = eLazyBoolYes; + } + } + } +#endif +} + +bool File::GetIsInteractive() { + if (m_is_interactive == eLazyBoolCalculate) + CalculateInteractiveAndTerminal(); + return m_is_interactive == eLazyBoolYes; +} + +bool File::GetIsRealTerminal() { + if (m_is_real_terminal == eLazyBoolCalculate) + CalculateInteractiveAndTerminal(); + return m_is_real_terminal == eLazyBoolYes; +} + +bool File::GetIsTerminalWithColors() { + if (m_supports_colors == eLazyBoolCalculate) + CalculateInteractiveAndTerminal(); + return m_supports_colors == eLazyBoolYes; +} + +size_t File::Printf(const char *format, ...) { + va_list args; + va_start(args, format); + size_t result = PrintfVarArg(format, args); + va_end(args); + return result; +} + +size_t File::PrintfVarArg(const char *format, va_list args) { + size_t result = 0; + char *s = nullptr; + result = vasprintf(&s, format, args); + if (s != nullptr) { + if (result > 0) { + size_t s_len = result; + Write(s, s_len); + result = s_len; + } + free(s); + } + return result; +} + +Expected<File::OpenOptions> File::GetOptions() const { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "GetOptions() not implemented for this File class"); +} + +uint32_t File::GetPermissions(Status &error) const { + int fd = GetDescriptor(); + if (!DescriptorIsValid(fd)) { + error = std::error_code(ENOTSUP, std::system_category()); + return 0; + } + struct stat file_stats; + if (::fstat(fd, &file_stats) == -1) { + error.SetErrorToErrno(); + return 0; + } + error.Clear(); + return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); +} + +Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; } + +int NativeFile::GetDescriptor() const { + if (DescriptorIsValid()) + return m_descriptor; + + // Don't open the file descriptor if we don't need to, just get it from the + // stream if we have one. + if (StreamIsValid()) { +#if defined(_WIN32) + return _fileno(m_stream); +#else + return fileno(m_stream); +#endif + } + + // Invalid descriptor and invalid stream, return invalid descriptor. + return kInvalidDescriptor; +} + +IOObject::WaitableHandle NativeFile::GetWaitableHandle() { + return GetDescriptor(); +} + +FILE *NativeFile::GetStream() { + if (!StreamIsValid()) { + if (DescriptorIsValid()) { + auto mode = GetStreamOpenModeFromOptions(m_options); + if (!mode) + llvm::consumeError(mode.takeError()); + else { + if (!m_own_descriptor) { +// We must duplicate the file descriptor if we don't own it because when you +// call fdopen, the stream will own the fd +#ifdef _WIN32 + m_descriptor = ::_dup(GetDescriptor()); +#else + m_descriptor = dup(GetDescriptor()); +#endif + m_own_descriptor = true; + } + + m_stream = llvm::sys::RetryAfterSignal(nullptr, ::fdopen, m_descriptor, + mode.get()); + + // If we got a stream, then we own the stream and should no longer own + // the descriptor because fclose() will close it for us + + if (m_stream) { + m_own_stream = true; + m_own_descriptor = false; + } + } + } + } + return m_stream; +} + +Status NativeFile::Close() { + Status error; + if (StreamIsValid()) { + if (m_own_stream) { + if (::fclose(m_stream) == EOF) + error.SetErrorToErrno(); + } else if (m_options & eOpenOptionWrite) { + if (::fflush(m_stream) == EOF) + error.SetErrorToErrno(); + } + } + if (DescriptorIsValid() && m_own_descriptor) { + if (::close(m_descriptor) != 0) + error.SetErrorToErrno(); + } + m_descriptor = kInvalidDescriptor; + m_stream = kInvalidStream; + m_options = OpenOptions(0); + m_own_stream = false; + m_own_descriptor = false; + m_is_interactive = eLazyBoolCalculate; + m_is_real_terminal = eLazyBoolCalculate; + return error; +} + +Status NativeFile::GetFileSpec(FileSpec &file_spec) const { + Status error; +#ifdef F_GETPATH + if (IsValid()) { + char path[PATH_MAX]; + if (::fcntl(GetDescriptor(), F_GETPATH, path) == -1) + error.SetErrorToErrno(); + else + file_spec.SetFile(path, FileSpec::Style::native); + } else { + error.SetErrorString("invalid file handle"); + } +#elif defined(__linux__) + char proc[64]; + char path[PATH_MAX]; + if (::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", GetDescriptor()) < 0) + error.SetErrorString("cannot resolve file descriptor"); + else { + ssize_t len; + if ((len = ::readlink(proc, path, sizeof(path) - 1)) == -1) + error.SetErrorToErrno(); + else { + path[len] = '\0'; + file_spec.SetFile(path, FileSpec::Style::native); + } + } +#else + error.SetErrorString( + "NativeFile::GetFileSpec is not supported on this platform"); +#endif + + if (error.Fail()) + file_spec.Clear(); + return error; +} + +off_t NativeFile::SeekFromStart(off_t offset, Status *error_ptr) { + off_t result = 0; + if (DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_SET); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_SET); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (error_ptr) { + error_ptr->SetErrorString("invalid file handle"); + } + return result; +} + +off_t NativeFile::SeekFromCurrent(off_t offset, Status *error_ptr) { + off_t result = -1; + if (DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_CUR); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_CUR); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (error_ptr) { + error_ptr->SetErrorString("invalid file handle"); + } + return result; +} + +off_t NativeFile::SeekFromEnd(off_t offset, Status *error_ptr) { + off_t result = -1; + if (DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_END); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_END); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } else if (error_ptr) { + error_ptr->SetErrorString("invalid file handle"); + } + return result; +} + +Status NativeFile::Flush() { + Status error; + if (StreamIsValid()) { + if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) + error.SetErrorToErrno(); + } else if (!DescriptorIsValid()) { + error.SetErrorString("invalid file handle"); + } + return error; +} + +Status NativeFile::Sync() { + Status error; + if (DescriptorIsValid()) { +#ifdef _WIN32 + int err = FlushFileBuffers((HANDLE)_get_osfhandle(m_descriptor)); + if (err == 0) + error.SetErrorToGenericError(); +#else + if (llvm::sys::RetryAfterSignal(-1, ::fsync, m_descriptor) == -1) + error.SetErrorToErrno(); +#endif + } else { + error.SetErrorString("invalid file handle"); + } + return error; +} + +#if defined(__APPLE__) +// Darwin kernels only can read/write <= INT_MAX bytes +#define MAX_READ_SIZE INT_MAX +#define MAX_WRITE_SIZE INT_MAX +#endif + +Status NativeFile::Read(void *buf, size_t &num_bytes) { + Status error; + +#if defined(MAX_READ_SIZE) + if (num_bytes > MAX_READ_SIZE) { + uint8_t *p = (uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes read to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_READ_SIZE) + curr_num_bytes = MAX_READ_SIZE; + else + curr_num_bytes = bytes_left; + + error = Read(p + num_bytes, curr_num_bytes); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + + ssize_t bytes_read = -1; + if (DescriptorIsValid()) { + bytes_read = llvm::sys::RetryAfterSignal(-1, ::read, m_descriptor, buf, num_bytes); + if (bytes_read == -1) { + error.SetErrorToErrno(); + num_bytes = 0; + } else + num_bytes = bytes_read; + } else if (StreamIsValid()) { + bytes_read = ::fread(buf, 1, num_bytes, m_stream); + + if (bytes_read == 0) { + if (::feof(m_stream)) + error.SetErrorString("feof"); + else if (::ferror(m_stream)) + error.SetErrorString("ferror"); + num_bytes = 0; + } else + num_bytes = bytes_read; + } else { + num_bytes = 0; + error.SetErrorString("invalid file handle"); + } + return error; +} + +Status NativeFile::Write(const void *buf, size_t &num_bytes) { + Status error; + +#if defined(MAX_WRITE_SIZE) + if (num_bytes > MAX_WRITE_SIZE) { + const uint8_t *p = (const uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes written to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_WRITE_SIZE) + curr_num_bytes = MAX_WRITE_SIZE; + else + curr_num_bytes = bytes_left; + + error = Write(p + num_bytes, curr_num_bytes); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + + ssize_t bytes_written = -1; + if (DescriptorIsValid()) { + bytes_written = + llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, buf, num_bytes); + if (bytes_written == -1) { + error.SetErrorToErrno(); + num_bytes = 0; + } else + num_bytes = bytes_written; + } else if (StreamIsValid()) { + bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); + + if (bytes_written == 0) { + if (::feof(m_stream)) + error.SetErrorString("feof"); + else if (::ferror(m_stream)) + error.SetErrorString("ferror"); + num_bytes = 0; + } else + num_bytes = bytes_written; + + } else { + num_bytes = 0; + error.SetErrorString("invalid file handle"); + } + + return error; +} + +Status NativeFile::Read(void *buf, size_t &num_bytes, off_t &offset) { + Status error; + +#if defined(MAX_READ_SIZE) + if (num_bytes > MAX_READ_SIZE) { + uint8_t *p = (uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes read to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_READ_SIZE) + curr_num_bytes = MAX_READ_SIZE; + else + curr_num_bytes = bytes_left; + + error = Read(p + num_bytes, curr_num_bytes, offset); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + +#ifndef _WIN32 + int fd = GetDescriptor(); + if (fd != kInvalidDescriptor) { + ssize_t bytes_read = + llvm::sys::RetryAfterSignal(-1, ::pread, fd, buf, num_bytes, offset); + if (bytes_read < 0) { + num_bytes = 0; + error.SetErrorToErrno(); + } else { + offset += bytes_read; + num_bytes = bytes_read; + } + } else { + num_bytes = 0; + error.SetErrorString("invalid file handle"); + } +#else + std::lock_guard<std::mutex> guard(offset_access_mutex); + long cur = ::lseek(m_descriptor, 0, SEEK_CUR); + SeekFromStart(offset); + error = Read(buf, num_bytes); + if (!error.Fail()) + SeekFromStart(cur); +#endif + return error; +} + +Status NativeFile::Write(const void *buf, size_t &num_bytes, off_t &offset) { + Status error; + +#if defined(MAX_WRITE_SIZE) + if (num_bytes > MAX_WRITE_SIZE) { + const uint8_t *p = (const uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes written to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_WRITE_SIZE) + curr_num_bytes = MAX_WRITE_SIZE; + else + curr_num_bytes = bytes_left; + + error = Write(p + num_bytes, curr_num_bytes, offset); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + + int fd = GetDescriptor(); + if (fd != kInvalidDescriptor) { +#ifndef _WIN32 + ssize_t bytes_written = + llvm::sys::RetryAfterSignal(-1, ::pwrite, m_descriptor, buf, num_bytes, offset); + if (bytes_written < 0) { + num_bytes = 0; + error.SetErrorToErrno(); + } else { + offset += bytes_written; + num_bytes = bytes_written; + } +#else + std::lock_guard<std::mutex> guard(offset_access_mutex); + long cur = ::lseek(m_descriptor, 0, SEEK_CUR); + SeekFromStart(offset); + error = Write(buf, num_bytes); + long after = ::lseek(m_descriptor, 0, SEEK_CUR); + + if (!error.Fail()) + SeekFromStart(cur); + + offset = after; +#endif + } else { + num_bytes = 0; + error.SetErrorString("invalid file handle"); + } + return error; +} + +size_t NativeFile::PrintfVarArg(const char *format, va_list args) { + if (StreamIsValid()) { + return ::vfprintf(m_stream, format, args); + } else { + return File::PrintfVarArg(format, args); + } +} + +mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) { + mode_t mode = 0; + if (open_options & eOpenOptionRead && open_options & eOpenOptionWrite) + mode |= O_RDWR; + else if (open_options & eOpenOptionWrite) + mode |= O_WRONLY; + + if (open_options & eOpenOptionAppend) + mode |= O_APPEND; + + if (open_options & eOpenOptionTruncate) + mode |= O_TRUNC; + + if (open_options & eOpenOptionNonBlocking) + mode |= O_NONBLOCK; + + if (open_options & eOpenOptionCanCreateNewOnly) + mode |= O_CREAT | O_EXCL; + else if (open_options & eOpenOptionCanCreate) + mode |= O_CREAT; + + return mode; +} + +char File::ID = 0; +char NativeFile::ID = 0; diff --git a/gnu/llvm/lldb/source/Host/common/FileAction.cpp b/gnu/llvm/lldb/source/Host/common/FileAction.cpp new file mode 100644 index 00000000000..3268d952bcc --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/FileAction.cpp @@ -0,0 +1,88 @@ +//===-- FileAction.cpp ------------------------------------------*- C++ -*-===// +// +// 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 <fcntl.h> + +#include "lldb/Host/FileAction.h" +#include "lldb/Host/PosixApi.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb_private; + +// FileAction member functions + +FileAction::FileAction() + : m_action(eFileActionNone), m_fd(-1), m_arg(-1), m_file_spec() {} + +void FileAction::Clear() { + m_action = eFileActionNone; + m_fd = -1; + m_arg = -1; + m_file_spec.Clear(); +} + +llvm::StringRef FileAction::GetPath() const { return m_file_spec.GetCString(); } + +const FileSpec &FileAction::GetFileSpec() const { return m_file_spec; } + +bool FileAction::Open(int fd, const FileSpec &file_spec, bool read, + bool write) { + if ((read || write) && fd >= 0 && file_spec) { + m_action = eFileActionOpen; + m_fd = fd; + if (read && write) + m_arg = O_NOCTTY | O_CREAT | O_RDWR; + else if (read) + m_arg = O_NOCTTY | O_RDONLY; + else + m_arg = O_NOCTTY | O_CREAT | O_WRONLY; + m_file_spec = file_spec; + return true; + } else { + Clear(); + } + return false; +} + +bool FileAction::Close(int fd) { + Clear(); + if (fd >= 0) { + m_action = eFileActionClose; + m_fd = fd; + } + return m_fd >= 0; +} + +bool FileAction::Duplicate(int fd, int dup_fd) { + Clear(); + if (fd >= 0 && dup_fd >= 0) { + m_action = eFileActionDuplicate; + m_fd = fd; + m_arg = dup_fd; + } + return m_fd >= 0; +} + +void FileAction::Dump(Stream &stream) const { + stream.PutCString("file action: "); + switch (m_action) { + case eFileActionClose: + stream.Printf("close fd %d", m_fd); + break; + case eFileActionDuplicate: + stream.Printf("duplicate fd %d to %d", m_fd, m_arg); + break; + case eFileActionNone: + stream.PutCString("no action"); + break; + case eFileActionOpen: + stream.Printf("open fd %d with '%s', OFLAGS = 0x%x", m_fd, + m_file_spec.GetCString(), m_arg); + break; + } +} diff --git a/gnu/llvm/lldb/source/Host/common/FileCache.cpp b/gnu/llvm/lldb/source/Host/common/FileCache.cpp new file mode 100644 index 00000000000..d9dcad992c3 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/FileCache.cpp @@ -0,0 +1,114 @@ +//===-- FileCache.cpp -------------------------------------------*- C++ -*-===// +// +// 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/FileCache.h" + +#include "lldb/Host/File.h" +#include "lldb/Host/FileSystem.h" + +using namespace lldb; +using namespace lldb_private; + +FileCache *FileCache::m_instance = nullptr; + +FileCache &FileCache::GetInstance() { + if (m_instance == nullptr) + m_instance = new FileCache(); + + return *m_instance; +} + +lldb::user_id_t FileCache::OpenFile(const FileSpec &file_spec, + File::OpenOptions flags, uint32_t mode, + Status &error) { + if (!file_spec) { + error.SetErrorString("empty path"); + return UINT64_MAX; + } + auto file = FileSystem::Instance().Open(file_spec, flags, mode); + if (!file) { + error = file.takeError(); + return UINT64_MAX; + } + lldb::user_id_t fd = file.get()->GetDescriptor(); + m_cache[fd] = std::move(file.get()); + return fd; +} + +bool FileCache::CloseFile(lldb::user_id_t fd, Status &error) { + if (fd == UINT64_MAX) { + error.SetErrorString("invalid file descriptor"); + return false; + } + FDToFileMap::iterator pos = m_cache.find(fd); + if (pos == m_cache.end()) { + error.SetErrorStringWithFormat("invalid host file descriptor %" PRIu64, fd); + return false; + } + FileUP &file_up = pos->second; + if (!file_up) { + error.SetErrorString("invalid host backing file"); + return false; + } + error = file_up->Close(); + m_cache.erase(pos); + return error.Success(); +} + +uint64_t FileCache::WriteFile(lldb::user_id_t fd, uint64_t offset, + const void *src, uint64_t src_len, + Status &error) { + if (fd == UINT64_MAX) { + error.SetErrorString("invalid file descriptor"); + return UINT64_MAX; + } + FDToFileMap::iterator pos = m_cache.find(fd); + if (pos == m_cache.end()) { + error.SetErrorStringWithFormat("invalid host file descriptor %" PRIu64, fd); + return false; + } + FileUP &file_up = pos->second; + if (!file_up) { + error.SetErrorString("invalid host backing file"); + return UINT64_MAX; + } + if (static_cast<uint64_t>(file_up->SeekFromStart(offset, &error)) != offset || + error.Fail()) + return UINT64_MAX; + size_t bytes_written = src_len; + error = file_up->Write(src, bytes_written); + if (error.Fail()) + return UINT64_MAX; + return bytes_written; +} + +uint64_t FileCache::ReadFile(lldb::user_id_t fd, uint64_t offset, void *dst, + uint64_t dst_len, Status &error) { + if (fd == UINT64_MAX) { + error.SetErrorString("invalid file descriptor"); + return UINT64_MAX; + } + FDToFileMap::iterator pos = m_cache.find(fd); + if (pos == m_cache.end()) { + error.SetErrorStringWithFormat("invalid host file descriptor %" PRIu64, fd); + return false; + } + FileUP &file_up = pos->second; + if (!file_up) { + error.SetErrorString("invalid host backing file"); + return UINT64_MAX; + } + if (static_cast<uint64_t>(file_up->SeekFromStart(offset, &error)) != offset || + error.Fail()) + return UINT64_MAX; + size_t bytes_read = dst_len; + error = file_up->Read(dst, bytes_read); + if (error.Fail()) + return UINT64_MAX; + return bytes_read; +} diff --git a/gnu/llvm/lldb/source/Host/common/FileSystem.cpp b/gnu/llvm/lldb/source/Host/common/FileSystem.cpp new file mode 100644 index 00000000000..2db5bff3207 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/FileSystem.cpp @@ -0,0 +1,468 @@ +//===-- FileSystem.cpp ------------------------------------------*- C++ -*-===// +// +// 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/FileSystem.h" + +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/TildeExpressionResolver.h" + +#include "llvm/Support/Errc.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Threading.h" + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> + +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#else +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <termios.h> +#include <unistd.h> +#endif + +#include <algorithm> +#include <fstream> +#include <vector> + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +FileSystem &FileSystem::Instance() { return *InstanceImpl(); } + +void FileSystem::Initialize() { + lldbassert(!InstanceImpl() && "Already initialized."); + InstanceImpl().emplace(); +} + +void FileSystem::Initialize(std::shared_ptr<FileCollector> collector) { + lldbassert(!InstanceImpl() && "Already initialized."); + InstanceImpl().emplace(collector); +} + +llvm::Error FileSystem::Initialize(const FileSpec &mapping) { + lldbassert(!InstanceImpl() && "Already initialized."); + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer = + llvm::vfs::getRealFileSystem()->getBufferForFile(mapping.GetPath()); + + if (!buffer) + return llvm::errorCodeToError(buffer.getError()); + + InstanceImpl().emplace(llvm::vfs::getVFSFromYAML(std::move(buffer.get()), + nullptr, mapping.GetPath()), + true); + + return llvm::Error::success(); +} + +void FileSystem::Initialize(IntrusiveRefCntPtr<vfs::FileSystem> fs) { + lldbassert(!InstanceImpl() && "Already initialized."); + InstanceImpl().emplace(fs); +} + +void FileSystem::Terminate() { + lldbassert(InstanceImpl() && "Already terminated."); + InstanceImpl().reset(); +} + +Optional<FileSystem> &FileSystem::InstanceImpl() { + static Optional<FileSystem> g_fs; + return g_fs; +} + +vfs::directory_iterator FileSystem::DirBegin(const FileSpec &file_spec, + std::error_code &ec) { + return DirBegin(file_spec.GetPath(), ec); +} + +vfs::directory_iterator FileSystem::DirBegin(const Twine &dir, + std::error_code &ec) { + return m_fs->dir_begin(dir, ec); +} + +llvm::ErrorOr<vfs::Status> +FileSystem::GetStatus(const FileSpec &file_spec) const { + return GetStatus(file_spec.GetPath()); +} + +llvm::ErrorOr<vfs::Status> FileSystem::GetStatus(const Twine &path) const { + return m_fs->status(path); +} + +sys::TimePoint<> +FileSystem::GetModificationTime(const FileSpec &file_spec) const { + return GetModificationTime(file_spec.GetPath()); +} + +sys::TimePoint<> FileSystem::GetModificationTime(const Twine &path) const { + ErrorOr<vfs::Status> status = m_fs->status(path); + if (!status) + return sys::TimePoint<>(); + return status->getLastModificationTime(); +} + +uint64_t FileSystem::GetByteSize(const FileSpec &file_spec) const { + return GetByteSize(file_spec.GetPath()); +} + +uint64_t FileSystem::GetByteSize(const Twine &path) const { + ErrorOr<vfs::Status> status = m_fs->status(path); + if (!status) + return 0; + return status->getSize(); +} + +uint32_t FileSystem::GetPermissions(const FileSpec &file_spec) const { + return GetPermissions(file_spec.GetPath()); +} + +uint32_t FileSystem::GetPermissions(const FileSpec &file_spec, + std::error_code &ec) const { + return GetPermissions(file_spec.GetPath(), ec); +} + +uint32_t FileSystem::GetPermissions(const Twine &path) const { + std::error_code ec; + return GetPermissions(path, ec); +} + +uint32_t FileSystem::GetPermissions(const Twine &path, + std::error_code &ec) const { + ErrorOr<vfs::Status> status = m_fs->status(path); + if (!status) { + ec = status.getError(); + return sys::fs::perms::perms_not_known; + } + return status->getPermissions(); +} + +bool FileSystem::Exists(const Twine &path) const { return m_fs->exists(path); } + +bool FileSystem::Exists(const FileSpec &file_spec) const { + return Exists(file_spec.GetPath()); +} + +bool FileSystem::Readable(const Twine &path) const { + return GetPermissions(path) & sys::fs::perms::all_read; +} + +bool FileSystem::Readable(const FileSpec &file_spec) const { + return Readable(file_spec.GetPath()); +} + +bool FileSystem::IsDirectory(const Twine &path) const { + ErrorOr<vfs::Status> status = m_fs->status(path); + if (!status) + return false; + return status->isDirectory(); +} + +bool FileSystem::IsDirectory(const FileSpec &file_spec) const { + return IsDirectory(file_spec.GetPath()); +} + +bool FileSystem::IsLocal(const Twine &path) const { + bool b = false; + m_fs->isLocal(path, b); + return b; +} + +bool FileSystem::IsLocal(const FileSpec &file_spec) const { + return IsLocal(file_spec.GetPath()); +} + +void FileSystem::EnumerateDirectory(Twine path, bool find_directories, + bool find_files, bool find_other, + EnumerateDirectoryCallbackType callback, + void *callback_baton) { + std::error_code EC; + vfs::recursive_directory_iterator Iter(*m_fs, path, EC); + vfs::recursive_directory_iterator End; + for (; Iter != End && !EC; Iter.increment(EC)) { + const auto &Item = *Iter; + ErrorOr<vfs::Status> Status = m_fs->status(Item.path()); + if (!Status) + break; + if (!find_files && Status->isRegularFile()) + continue; + if (!find_directories && Status->isDirectory()) + continue; + if (!find_other && Status->isOther()) + continue; + + auto Result = callback(callback_baton, Status->getType(), Item.path()); + if (Result == eEnumerateDirectoryResultQuit) + return; + if (Result == eEnumerateDirectoryResultNext) { + // Default behavior is to recurse. Opt out if the callback doesn't want + // this behavior. + Iter.no_push(); + } + } +} + +std::error_code FileSystem::MakeAbsolute(SmallVectorImpl<char> &path) const { + return m_fs->makeAbsolute(path); +} + +std::error_code FileSystem::MakeAbsolute(FileSpec &file_spec) const { + SmallString<128> path; + file_spec.GetPath(path, false); + + auto EC = MakeAbsolute(path); + if (EC) + return EC; + + FileSpec new_file_spec(path, file_spec.GetPathStyle()); + file_spec = new_file_spec; + return {}; +} + +std::error_code FileSystem::GetRealPath(const Twine &path, + SmallVectorImpl<char> &output) const { + return m_fs->getRealPath(path, output); +} + +void FileSystem::Resolve(SmallVectorImpl<char> &path) { + if (path.empty()) + return; + + // Resolve tilde in path. + SmallString<128> resolved(path.begin(), path.end()); + StandardTildeExpressionResolver Resolver; + Resolver.ResolveFullPath(llvm::StringRef(path.begin(), path.size()), + resolved); + + // Try making the path absolute if it exists. + SmallString<128> absolute(resolved.begin(), resolved.end()); + MakeAbsolute(absolute); + + path.clear(); + if (Exists(absolute)) { + path.append(absolute.begin(), absolute.end()); + } else { + path.append(resolved.begin(), resolved.end()); + } +} + +void FileSystem::Resolve(FileSpec &file_spec) { + // Extract path from the FileSpec. + SmallString<128> path; + file_spec.GetPath(path); + + // Resolve the path. + Resolve(path); + + // Update the FileSpec with the resolved path. + if (file_spec.GetFilename().IsEmpty()) + file_spec.GetDirectory().SetString(path); + else + file_spec.SetPath(path); + file_spec.SetIsResolved(true); +} + +std::shared_ptr<DataBufferLLVM> +FileSystem::CreateDataBuffer(const llvm::Twine &path, uint64_t size, + uint64_t offset) { + if (m_collector) + m_collector->addFile(path); + + const bool is_volatile = !IsLocal(path); + const ErrorOr<std::string> external_path = GetExternalPath(path); + + if (!external_path) + return nullptr; + + std::unique_ptr<llvm::WritableMemoryBuffer> buffer; + if (size == 0) { + auto buffer_or_error = + llvm::WritableMemoryBuffer::getFile(*external_path, -1, is_volatile); + if (!buffer_or_error) + return nullptr; + buffer = std::move(*buffer_or_error); + } else { + auto buffer_or_error = llvm::WritableMemoryBuffer::getFileSlice( + *external_path, size, offset, is_volatile); + if (!buffer_or_error) + return nullptr; + buffer = std::move(*buffer_or_error); + } + return std::shared_ptr<DataBufferLLVM>(new DataBufferLLVM(std::move(buffer))); +} + +std::shared_ptr<DataBufferLLVM> +FileSystem::CreateDataBuffer(const FileSpec &file_spec, uint64_t size, + uint64_t offset) { + return CreateDataBuffer(file_spec.GetPath(), size, offset); +} + +bool FileSystem::ResolveExecutableLocation(FileSpec &file_spec) { + // If the directory is set there's nothing to do. + ConstString directory = file_spec.GetDirectory(); + if (directory) + return false; + + // We cannot look for a file if there's no file name. + ConstString filename = file_spec.GetFilename(); + if (!filename) + return false; + + // Search for the file on the host. + const std::string filename_str(filename.GetCString()); + llvm::ErrorOr<std::string> error_or_path = + llvm::sys::findProgramByName(filename_str); + if (!error_or_path) + return false; + + // findProgramByName returns "." if it can't find the file. + llvm::StringRef path = *error_or_path; + llvm::StringRef parent = llvm::sys::path::parent_path(path); + if (parent.empty() || parent == ".") + return false; + + // Make sure that the result exists. + FileSpec result(*error_or_path); + if (!Exists(result)) + return false; + + file_spec = result; + return true; +} + +static int OpenWithFS(const FileSystem &fs, const char *path, int flags, + int mode) { + return const_cast<FileSystem &>(fs).Open(path, flags, mode); +} + +static int GetOpenFlags(uint32_t options) { + const bool read = options & File::eOpenOptionRead; + const bool write = options & File::eOpenOptionWrite; + + int open_flags = 0; + if (write) { + if (read) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + + if (options & File::eOpenOptionAppend) + open_flags |= O_APPEND; + + if (options & File::eOpenOptionTruncate) + open_flags |= O_TRUNC; + + if (options & File::eOpenOptionCanCreate) + open_flags |= O_CREAT; + + if (options & File::eOpenOptionCanCreateNewOnly) + open_flags |= O_CREAT | O_EXCL; + } else if (read) { + open_flags |= O_RDONLY; + +#ifndef _WIN32 + if (options & File::eOpenOptionDontFollowSymlinks) + open_flags |= O_NOFOLLOW; +#endif + } + +#ifndef _WIN32 + if (options & File::eOpenOptionNonBlocking) + open_flags |= O_NONBLOCK; + if (options & File::eOpenOptionCloseOnExec) + open_flags |= O_CLOEXEC; +#else + open_flags |= O_BINARY; +#endif + + return open_flags; +} + +static mode_t GetOpenMode(uint32_t permissions) { + mode_t mode = 0; + if (permissions & lldb::eFilePermissionsUserRead) + mode |= S_IRUSR; + if (permissions & lldb::eFilePermissionsUserWrite) + mode |= S_IWUSR; + if (permissions & lldb::eFilePermissionsUserExecute) + mode |= S_IXUSR; + if (permissions & lldb::eFilePermissionsGroupRead) + mode |= S_IRGRP; + if (permissions & lldb::eFilePermissionsGroupWrite) + mode |= S_IWGRP; + if (permissions & lldb::eFilePermissionsGroupExecute) + mode |= S_IXGRP; + if (permissions & lldb::eFilePermissionsWorldRead) + mode |= S_IROTH; + if (permissions & lldb::eFilePermissionsWorldWrite) + mode |= S_IWOTH; + if (permissions & lldb::eFilePermissionsWorldExecute) + mode |= S_IXOTH; + return mode; +} + +Expected<FileUP> FileSystem::Open(const FileSpec &file_spec, + File::OpenOptions options, + uint32_t permissions, bool should_close_fd) { + if (m_collector) + m_collector->addFile(file_spec.GetPath()); + + const int open_flags = GetOpenFlags(options); + const mode_t open_mode = + (open_flags & O_CREAT) ? GetOpenMode(permissions) : 0; + + auto path = GetExternalPath(file_spec); + if (!path) + return errorCodeToError(path.getError()); + + int descriptor = llvm::sys::RetryAfterSignal( + -1, OpenWithFS, *this, path->c_str(), open_flags, open_mode); + + if (!File::DescriptorIsValid(descriptor)) + return llvm::errorCodeToError( + std::error_code(errno, std::system_category())); + + auto file = std::unique_ptr<File>( + new NativeFile(descriptor, options, should_close_fd)); + assert(file->IsValid()); + return std::move(file); +} + +ErrorOr<std::string> FileSystem::GetExternalPath(const llvm::Twine &path) { + if (!m_mapped) + return path.str(); + + // If VFS mapped we know the underlying FS is a RedirectingFileSystem. + ErrorOr<vfs::RedirectingFileSystem::Entry *> E = + static_cast<vfs::RedirectingFileSystem &>(*m_fs).lookupPath(path); + if (!E) { + if (E.getError() == llvm::errc::no_such_file_or_directory) { + return path.str(); + } + return E.getError(); + } + + auto *F = dyn_cast<vfs::RedirectingFileSystem::RedirectingFileEntry>(*E); + if (!F) + return make_error_code(llvm::errc::not_supported); + + return F->getExternalContentsPath().str(); +} + +ErrorOr<std::string> FileSystem::GetExternalPath(const FileSpec &file_spec) { + return GetExternalPath(file_spec.GetPath()); +} diff --git a/gnu/llvm/lldb/source/Host/common/GetOptInc.cpp b/gnu/llvm/lldb/source/Host/common/GetOptInc.cpp new file mode 100644 index 00000000000..95a68c5d3c7 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/GetOptInc.cpp @@ -0,0 +1,451 @@ +//===-- GetOptInc.cpp -------------------------------------------*- C++ -*-===// +// +// 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/common/GetOptInc.h" + +#if defined(REPLACE_GETOPT) || defined(REPLACE_GETOPT_LONG) || \ + defined(REPLACE_GETOPT_LONG_ONLY) + +// getopt.cpp +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#if defined(REPLACE_GETOPT) +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal(int, char *const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char *const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char *const *); + +static const char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* +* Compute the greatest common divisor of a and b. +*/ +static int gcd(int a, int b) { + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +static void pass() {} +#define warnx(a, ...) pass(); + +/* +* Exchange the block from nonopt_start to nonopt_end with the block +* from nonopt_end to opt_end (keeping the same order of arguments +* in each block). +*/ +static void permute_args(int panonopt_start, int panonopt_end, int opt_end, + char *const *nargv) { + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end + i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + const_cast<char **>(nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + const_cast<char **>(nargv)[cstart] = swap; + } + } +} + +/* +* parse_long_options -- +* Parse long options in argc/argv argument vector. +* Returns -1 if short_too is set and the option does not match long_options. +*/ +static int parse_long_options(char *const *nargv, const char *options, + const struct option *long_options, int *idx, + int short_too) { + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = const_cast<char *>(place); + match = -1; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, current_argv); + optopt = 0; + return (BADCH); + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) && + (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* +* getopt_internal -- +* Parse argc/argv argument vector. Called by user level routines. +*/ +static int getopt_internal(int nargc, char *const *nargv, const char *options, + const struct option *long_options, int *idx, + int flags) { + const char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1 || optreset) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, optind, nargv); + optind -= nonopt_end - nonopt_start; + } else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, optind, nargv); + nonopt_start = optind - (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = const_cast<char *>(place); + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* +* getopt -- +* Parse argc/argv argument vector. +* +* [eventually this will replace the BSD getopt] +*/ +#if defined(REPLACE_GETOPT) +int getopt(int nargc, char *const *nargv, const char *options) { + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} +#endif + +/* +* getopt_long -- +* Parse argc/argv argument vector. +*/ +#if defined(REPLACE_GETOPT_LONG) +int getopt_long(int nargc, char *const *nargv, const char *options, + const struct option *long_options, int *idx) { + return ( + getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE)); +} +#endif + +/* +* getopt_long_only -- +* Parse argc/argv argument vector. +*/ +#if defined(REPLACE_GETOPT_LONG_ONLY) +int getopt_long_only(int nargc, char *const *nargv, const char *options, + const struct option *long_options, int *idx) { + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE | FLAG_LONGONLY)); +} +#endif + +#endif diff --git a/gnu/llvm/lldb/source/Host/common/Host.cpp b/gnu/llvm/lldb/source/Host/common/Host.cpp new file mode 100644 index 00000000000..5fbb655fc79 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/Host.cpp @@ -0,0 +1,680 @@ +//===-- Host.cpp ------------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// C includes +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <sys/types.h> +#ifndef _WIN32 +#include <dlfcn.h> +#include <grp.h> +#include <netdb.h> +#include <pwd.h> +#include <sys/stat.h> +#include <unistd.h> +#endif + +#if defined(__APPLE__) +#include <mach-o/dyld.h> +#include <mach/mach_init.h> +#include <mach/mach_port.h> +#endif + +#if defined(__linux__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__APPLE__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +#if !defined(__ANDROID__) +#include <spawn.h> +#endif +#include <sys/syscall.h> +#include <sys/wait.h> +#endif + +#if defined(__FreeBSD__) +#include <pthread_np.h> +#endif + +#if defined(__NetBSD__) +#include <lwp.h> +#endif + +#include <csignal> + +#include "lldb/Host/FileAction.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/MonitoringProcessLauncher.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Host/ProcessLauncher.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" +#include "lldb/Utility/DataBufferLLVM.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Predicate.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-private-forward.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/FileSystem.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/ConnectionGenericFileWindows.h" +#include "lldb/Host/windows/ProcessLauncherWindows.h" +#else +#include "lldb/Host/posix/ProcessLauncherPosixFork.h" +#endif + +#if defined(__APPLE__) +#ifndef _POSIX_SPAWN_DISABLE_ASLR +#define _POSIX_SPAWN_DISABLE_ASLR 0x0100 +#endif + +extern "C" { +int __pthread_chdir(const char *path); +int __pthread_fchdir(int fildes); +} + +#endif + +using namespace lldb; +using namespace lldb_private; + +#if !defined(__APPLE__) && !defined(_WIN32) +struct MonitorInfo { + lldb::pid_t pid; // The process ID to monitor + Host::MonitorChildProcessCallback + callback; // The callback function to call when "pid" exits or signals + bool monitor_signals; // If true, call the callback when "pid" gets signaled. +}; + +static thread_result_t MonitorChildProcessThreadFunction(void *arg); + +llvm::Expected<HostThread> Host::StartMonitoringChildProcess( + const Host::MonitorChildProcessCallback &callback, lldb::pid_t pid, + bool monitor_signals) { + MonitorInfo *info_ptr = new MonitorInfo(); + + info_ptr->pid = pid; + info_ptr->callback = callback; + info_ptr->monitor_signals = monitor_signals; + + char thread_name[256]; + ::snprintf(thread_name, sizeof(thread_name), + "<lldb.host.wait4(pid=%" PRIu64 ")>", pid); + return ThreadLauncher::LaunchThread( + thread_name, MonitorChildProcessThreadFunction, info_ptr, 0); +} + +#ifndef __linux__ +// Scoped class that will disable thread canceling when it is constructed, and +// exception safely restore the previous value it when it goes out of scope. +class ScopedPThreadCancelDisabler { +public: + ScopedPThreadCancelDisabler() { + // Disable the ability for this thread to be cancelled + int err = ::pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &m_old_state); + if (err != 0) + m_old_state = -1; + } + + ~ScopedPThreadCancelDisabler() { + // Restore the ability for this thread to be cancelled to what it + // previously was. + if (m_old_state != -1) + ::pthread_setcancelstate(m_old_state, 0); + } + +private: + int m_old_state; // Save the old cancelability state. +}; +#endif // __linux__ + +#ifdef __linux__ +#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) +static __thread volatile sig_atomic_t g_usr1_called; +#else +static thread_local volatile sig_atomic_t g_usr1_called; +#endif + +static void SigUsr1Handler(int) { g_usr1_called = 1; } +#endif // __linux__ + +static bool CheckForMonitorCancellation() { +#ifdef __linux__ + if (g_usr1_called) { + g_usr1_called = 0; + return true; + } +#else + ::pthread_testcancel(); +#endif + return false; +} + +static thread_result_t MonitorChildProcessThreadFunction(void *arg) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + const char *function = __FUNCTION__; + LLDB_LOGF(log, "%s (arg = %p) thread starting...", function, arg); + + MonitorInfo *info = (MonitorInfo *)arg; + + const Host::MonitorChildProcessCallback callback = info->callback; + const bool monitor_signals = info->monitor_signals; + + assert(info->pid <= UINT32_MAX); + const ::pid_t pid = monitor_signals ? -1 * getpgid(info->pid) : info->pid; + + delete info; + + int status = -1; +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) +#define __WALL 0 +#endif + const int options = __WALL; + +#ifdef __linux__ + // This signal is only used to interrupt the thread from waitpid + struct sigaction sigUsr1Action; + memset(&sigUsr1Action, 0, sizeof(sigUsr1Action)); + sigUsr1Action.sa_handler = SigUsr1Handler; + ::sigaction(SIGUSR1, &sigUsr1Action, nullptr); +#endif // __linux__ + + while (1) { + log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); + LLDB_LOGF(log, "%s ::waitpid (pid = %" PRIi32 ", &status, options = %i)...", + function, pid, options); + + if (CheckForMonitorCancellation()) + break; + + // Get signals from all children with same process group of pid + const ::pid_t wait_pid = ::waitpid(pid, &status, options); + + if (CheckForMonitorCancellation()) + break; + + if (wait_pid == -1) { + if (errno == EINTR) + continue; + else { + LLDB_LOG(log, + "arg = {0}, thread exiting because waitpid failed ({1})...", + arg, llvm::sys::StrError()); + break; + } + } else if (wait_pid > 0) { + bool exited = false; + int signal = 0; + int exit_status = 0; + const char *status_cstr = nullptr; + if (WIFSTOPPED(status)) { + signal = WSTOPSIG(status); + status_cstr = "STOPPED"; + } else if (WIFEXITED(status)) { + exit_status = WEXITSTATUS(status); + status_cstr = "EXITED"; + exited = true; + } else if (WIFSIGNALED(status)) { + signal = WTERMSIG(status); + status_cstr = "SIGNALED"; + if (wait_pid == abs(pid)) { + exited = true; + exit_status = -1; + } + } else { + status_cstr = "(\?\?\?)"; + } + + // Scope for pthread_cancel_disabler + { +#ifndef __linux__ + ScopedPThreadCancelDisabler pthread_cancel_disabler; +#endif + + log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); + LLDB_LOGF(log, + "%s ::waitpid (pid = %" PRIi32 + ", &status, options = %i) => pid = %" PRIi32 + ", status = 0x%8.8x (%s), signal = %i, exit_state = %i", + function, pid, options, wait_pid, status, status_cstr, signal, + exit_status); + + if (exited || (signal != 0 && monitor_signals)) { + bool callback_return = false; + if (callback) + callback_return = callback(wait_pid, exited, signal, exit_status); + + // If our process exited, then this thread should exit + if (exited && wait_pid == abs(pid)) { + LLDB_LOGF(log, + "%s (arg = %p) thread exiting because pid received " + "exit signal...", + __FUNCTION__, arg); + break; + } + // If the callback returns true, it means this process should exit + if (callback_return) { + LLDB_LOGF(log, + "%s (arg = %p) thread exiting because callback " + "returned true...", + __FUNCTION__, arg); + break; + } + } + } + } + } + + log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); + LLDB_LOGF(log, "%s (arg = %p) thread exiting...", __FUNCTION__, arg); + + return nullptr; +} + +#endif // #if !defined (__APPLE__) && !defined (_WIN32) + +#if !defined(__APPLE__) + +void Host::SystemLog(SystemLogType type, const char *format, va_list args) { + vfprintf(stderr, format, args); +} + +#endif + +void Host::SystemLog(SystemLogType type, const char *format, ...) { + { + va_list args; + va_start(args, format); + SystemLog(type, format, args); + va_end(args); + } + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST)); + if (log && log->GetVerbose()) { + // Log to log channel. This allows testcases to grep for log output. + va_list args; + va_start(args, format); + log->VAPrintf(format, args); + va_end(args); + } +} + +lldb::pid_t Host::GetCurrentProcessID() { return ::getpid(); } + +#ifndef _WIN32 + +lldb::thread_t Host::GetCurrentThread() { + return lldb::thread_t(pthread_self()); +} + +const char *Host::GetSignalAsCString(int signo) { + switch (signo) { + case SIGHUP: + return "SIGHUP"; // 1 hangup + case SIGINT: + return "SIGINT"; // 2 interrupt + case SIGQUIT: + return "SIGQUIT"; // 3 quit + case SIGILL: + return "SIGILL"; // 4 illegal instruction (not reset when caught) + case SIGTRAP: + return "SIGTRAP"; // 5 trace trap (not reset when caught) + case SIGABRT: + return "SIGABRT"; // 6 abort() +#if defined(SIGPOLL) +#if !defined(SIGIO) || (SIGPOLL != SIGIO) + // Under some GNU/Linux, SIGPOLL and SIGIO are the same. Causing the build to + // fail with 'multiple define cases with same value' + case SIGPOLL: + return "SIGPOLL"; // 7 pollable event ([XSR] generated, not supported) +#endif +#endif +#if defined(SIGEMT) + case SIGEMT: + return "SIGEMT"; // 7 EMT instruction +#endif + case SIGFPE: + return "SIGFPE"; // 8 floating point exception + case SIGKILL: + return "SIGKILL"; // 9 kill (cannot be caught or ignored) + case SIGBUS: + return "SIGBUS"; // 10 bus error + case SIGSEGV: + return "SIGSEGV"; // 11 segmentation violation + case SIGSYS: + return "SIGSYS"; // 12 bad argument to system call + case SIGPIPE: + return "SIGPIPE"; // 13 write on a pipe with no one to read it + case SIGALRM: + return "SIGALRM"; // 14 alarm clock + case SIGTERM: + return "SIGTERM"; // 15 software termination signal from kill + case SIGURG: + return "SIGURG"; // 16 urgent condition on IO channel + case SIGSTOP: + return "SIGSTOP"; // 17 sendable stop signal not from tty + case SIGTSTP: + return "SIGTSTP"; // 18 stop signal from tty + case SIGCONT: + return "SIGCONT"; // 19 continue a stopped process + case SIGCHLD: + return "SIGCHLD"; // 20 to parent on child stop or exit + case SIGTTIN: + return "SIGTTIN"; // 21 to readers pgrp upon background tty read + case SIGTTOU: + return "SIGTTOU"; // 22 like TTIN for output if (tp->t_local<OSTOP) +#if defined(SIGIO) + case SIGIO: + return "SIGIO"; // 23 input/output possible signal +#endif + case SIGXCPU: + return "SIGXCPU"; // 24 exceeded CPU time limit + case SIGXFSZ: + return "SIGXFSZ"; // 25 exceeded file size limit + case SIGVTALRM: + return "SIGVTALRM"; // 26 virtual time alarm + case SIGPROF: + return "SIGPROF"; // 27 profiling time alarm +#if defined(SIGWINCH) + case SIGWINCH: + return "SIGWINCH"; // 28 window size changes +#endif +#if defined(SIGINFO) + case SIGINFO: + return "SIGINFO"; // 29 information request +#endif + case SIGUSR1: + return "SIGUSR1"; // 30 user defined signal 1 + case SIGUSR2: + return "SIGUSR2"; // 31 user defined signal 2 + default: + break; + } + return nullptr; +} + +#endif + +#if !defined(__APPLE__) // see Host.mm + +bool Host::GetBundleDirectory(const FileSpec &file, FileSpec &bundle) { + bundle.Clear(); + return false; +} + +bool Host::ResolveExecutableInBundle(FileSpec &file) { return false; } +#endif + +#ifndef _WIN32 + +FileSpec Host::GetModuleFileSpecForHostAddress(const void *host_addr) { + FileSpec module_filespec; +#if !defined(__ANDROID__) + Dl_info info; + if (::dladdr(host_addr, &info)) { + if (info.dli_fname) { + module_filespec.SetFile(info.dli_fname, FileSpec::Style::native); + FileSystem::Instance().Resolve(module_filespec); + } + } +#endif + return module_filespec; +} + +#endif + +#if !defined(__linux__) +bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) { + return false; +} +#endif + +struct ShellInfo { + ShellInfo() + : process_reaped(false), pid(LLDB_INVALID_PROCESS_ID), signo(-1), + status(-1) {} + + lldb_private::Predicate<bool> process_reaped; + lldb::pid_t pid; + int signo; + int status; +}; + +static bool +MonitorShellCommand(std::shared_ptr<ShellInfo> shell_info, lldb::pid_t pid, + bool exited, // True if the process did exit + int signo, // Zero for no signal + int status) // Exit value of process if signal is zero +{ + shell_info->pid = pid; + shell_info->signo = signo; + shell_info->status = status; + // Let the thread running Host::RunShellCommand() know that the process + // exited and that ShellInfo has been filled in by broadcasting to it + shell_info->process_reaped.SetValue(true, eBroadcastAlways); + return true; +} + +Status Host::RunShellCommand(const char *command, const FileSpec &working_dir, + int *status_ptr, int *signo_ptr, + std::string *command_output_ptr, + const Timeout<std::micro> &timeout, + bool run_in_default_shell, + bool hide_stderr) { + return RunShellCommand(Args(command), working_dir, status_ptr, signo_ptr, + command_output_ptr, timeout, run_in_default_shell, + hide_stderr); +} + +Status Host::RunShellCommand(const Args &args, const FileSpec &working_dir, + int *status_ptr, int *signo_ptr, + std::string *command_output_ptr, + const Timeout<std::micro> &timeout, + bool run_in_default_shell, + bool hide_stderr) { + Status error; + ProcessLaunchInfo launch_info; + launch_info.SetArchitecture(HostInfo::GetArchitecture()); + if (run_in_default_shell) { + // Run the command in a shell + launch_info.SetShell(HostInfo::GetDefaultShell()); + launch_info.GetArguments().AppendArguments(args); + const bool localhost = true; + const bool will_debug = false; + const bool first_arg_is_full_shell_command = false; + launch_info.ConvertArgumentsForLaunchingInShell( + error, localhost, will_debug, first_arg_is_full_shell_command, 0); + } else { + // No shell, just run it + const bool first_arg_is_executable = true; + launch_info.SetArguments(args, first_arg_is_executable); + } + + if (working_dir) + launch_info.SetWorkingDirectory(working_dir); + llvm::SmallString<64> output_file_path; + + if (command_output_ptr) { + // Create a temporary file to get the stdout/stderr and redirect the output + // of the command into this file. We will later read this file if all goes + // well and fill the data into "command_output_ptr" + if (FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir()) { + tmpdir_file_spec.AppendPathComponent("lldb-shell-output.%%%%%%"); + llvm::sys::fs::createUniqueFile(tmpdir_file_spec.GetPath(), + output_file_path); + } else { + llvm::sys::fs::createTemporaryFile("lldb-shell-output.%%%%%%", "", + output_file_path); + } + } + + FileSpec output_file_spec(output_file_path.c_str()); + // Set up file descriptors. + launch_info.AppendSuppressFileAction(STDIN_FILENO, true, false); + if (output_file_spec) + launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_spec, false, + true); + else + launch_info.AppendSuppressFileAction(STDOUT_FILENO, false, true); + + if (output_file_spec && !hide_stderr) + launch_info.AppendDuplicateFileAction(STDOUT_FILENO, STDERR_FILENO); + else + launch_info.AppendSuppressFileAction(STDERR_FILENO, false, true); + + std::shared_ptr<ShellInfo> shell_info_sp(new ShellInfo()); + const bool monitor_signals = false; + launch_info.SetMonitorProcessCallback( + std::bind(MonitorShellCommand, shell_info_sp, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4), + monitor_signals); + + error = LaunchProcess(launch_info); + const lldb::pid_t pid = launch_info.GetProcessID(); + + if (error.Success() && pid == LLDB_INVALID_PROCESS_ID) + error.SetErrorString("failed to get process ID"); + + if (error.Success()) { + if (!shell_info_sp->process_reaped.WaitForValueEqualTo(true, timeout)) { + error.SetErrorString("timed out waiting for shell command to complete"); + + // Kill the process since it didn't complete within the timeout specified + Kill(pid, SIGKILL); + // Wait for the monitor callback to get the message + shell_info_sp->process_reaped.WaitForValueEqualTo( + true, std::chrono::seconds(1)); + } else { + if (status_ptr) + *status_ptr = shell_info_sp->status; + + if (signo_ptr) + *signo_ptr = shell_info_sp->signo; + + if (command_output_ptr) { + command_output_ptr->clear(); + uint64_t file_size = + FileSystem::Instance().GetByteSize(output_file_spec); + if (file_size > 0) { + if (file_size > command_output_ptr->max_size()) { + error.SetErrorStringWithFormat( + "shell command output is too large to fit into a std::string"); + } else { + auto Buffer = + FileSystem::Instance().CreateDataBuffer(output_file_spec); + if (error.Success()) + command_output_ptr->assign(Buffer->GetChars(), + Buffer->GetByteSize()); + } + } + } + } + } + + llvm::sys::fs::remove(output_file_spec.GetPath()); + return error; +} + +// The functions below implement process launching for non-Apple-based +// platforms +#if !defined(__APPLE__) +Status Host::LaunchProcess(ProcessLaunchInfo &launch_info) { + std::unique_ptr<ProcessLauncher> delegate_launcher; +#if defined(_WIN32) + delegate_launcher.reset(new ProcessLauncherWindows()); +#else + delegate_launcher.reset(new ProcessLauncherPosixFork()); +#endif + MonitoringProcessLauncher launcher(std::move(delegate_launcher)); + + Status error; + HostProcess process = launcher.LaunchProcess(launch_info, error); + + // TODO(zturner): It would be better if the entire HostProcess were returned + // instead of writing it into this structure. + launch_info.SetProcessID(process.GetProcessId()); + + return error; +} +#endif // !defined(__APPLE__) + +#ifndef _WIN32 +void Host::Kill(lldb::pid_t pid, int signo) { ::kill(pid, signo); } + +#endif + +#if !defined(__APPLE__) +bool Host::OpenFileInExternalEditor(const FileSpec &file_spec, + uint32_t line_no) { + return false; +} + +#endif + +std::unique_ptr<Connection> Host::CreateDefaultConnection(llvm::StringRef url) { +#if defined(_WIN32) + if (url.startswith("file://")) + return std::unique_ptr<Connection>(new ConnectionGenericFile()); +#endif + return std::unique_ptr<Connection>(new ConnectionFileDescriptor()); +} + +#if defined(LLVM_ON_UNIX) +WaitStatus WaitStatus::Decode(int wstatus) { + if (WIFEXITED(wstatus)) + return {Exit, uint8_t(WEXITSTATUS(wstatus))}; + else if (WIFSIGNALED(wstatus)) + return {Signal, uint8_t(WTERMSIG(wstatus))}; + else if (WIFSTOPPED(wstatus)) + return {Stop, uint8_t(WSTOPSIG(wstatus))}; + llvm_unreachable("Unknown wait status"); +} +#endif + +void llvm::format_provider<WaitStatus>::format(const WaitStatus &WS, + raw_ostream &OS, + StringRef Options) { + if (Options == "g") { + char type; + switch (WS.type) { + case WaitStatus::Exit: + type = 'W'; + break; + case WaitStatus::Signal: + type = 'X'; + break; + case WaitStatus::Stop: + type = 'S'; + break; + } + OS << formatv("{0}{1:x-2}", type, WS.status); + return; + } + + assert(Options.empty()); + const char *desc; + switch(WS.type) { + case WaitStatus::Exit: + desc = "Exited with status"; + break; + case WaitStatus::Signal: + desc = "Killed by signal"; + break; + case WaitStatus::Stop: + desc = "Stopped by signal"; + break; + } + OS << desc << " " << int(WS.status); +} diff --git a/gnu/llvm/lldb/source/Host/common/HostInfoBase.cpp b/gnu/llvm/lldb/source/Host/common/HostInfoBase.cpp new file mode 100644 index 00000000000..8f263e90d90 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/HostInfoBase.cpp @@ -0,0 +1,349 @@ +//===-- HostInfoBase.cpp ----------------------------------------*- C++ -*-===// +// +// 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/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/HostInfoBase.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" + +#include <mutex> +#include <thread> + +using namespace lldb; +using namespace lldb_private; + +namespace { +// The HostInfoBaseFields is a work around for windows not supporting static +// variables correctly in a thread safe way. Really each of the variables in +// HostInfoBaseFields should live in the functions in which they are used and +// each one should be static, but the work around is in place to avoid this +// restriction. Ick. + +struct HostInfoBaseFields { + ~HostInfoBaseFields() { + if (FileSystem::Instance().Exists(m_lldb_process_tmp_dir)) { + // Remove the LLDB temporary directory if we have one. Set "recurse" to + // true to all files that were created for the LLDB process can be + // cleaned up. + llvm::sys::fs::remove_directories(m_lldb_process_tmp_dir.GetPath()); + } + } + + llvm::once_flag m_host_triple_once; + llvm::Triple m_host_triple; + + llvm::once_flag m_host_arch_once; + ArchSpec m_host_arch_32; + ArchSpec m_host_arch_64; + + llvm::once_flag m_lldb_so_dir_once; + FileSpec m_lldb_so_dir; + llvm::once_flag m_lldb_support_exe_dir_once; + FileSpec m_lldb_support_exe_dir; + llvm::once_flag m_lldb_headers_dir_once; + FileSpec m_lldb_headers_dir; + llvm::once_flag m_lldb_clang_resource_dir_once; + FileSpec m_lldb_clang_resource_dir; + llvm::once_flag m_lldb_system_plugin_dir_once; + FileSpec m_lldb_system_plugin_dir; + llvm::once_flag m_lldb_user_plugin_dir_once; + FileSpec m_lldb_user_plugin_dir; + llvm::once_flag m_lldb_process_tmp_dir_once; + FileSpec m_lldb_process_tmp_dir; + llvm::once_flag m_lldb_global_tmp_dir_once; + FileSpec m_lldb_global_tmp_dir; +}; + +HostInfoBaseFields *g_fields = nullptr; +} + +void HostInfoBase::Initialize() { g_fields = new HostInfoBaseFields(); } + +void HostInfoBase::Terminate() { + delete g_fields; + g_fields = nullptr; +} + +llvm::Triple HostInfoBase::GetTargetTriple() { + llvm::call_once(g_fields->m_host_triple_once, []() { + g_fields->m_host_triple = + HostInfo::GetArchitecture().GetTriple(); + }); + return g_fields->m_host_triple; +} + +const ArchSpec &HostInfoBase::GetArchitecture(ArchitectureKind arch_kind) { + llvm::call_once(g_fields->m_host_arch_once, []() { + HostInfo::ComputeHostArchitectureSupport(g_fields->m_host_arch_32, + g_fields->m_host_arch_64); + }); + + // If an explicit 32 or 64-bit architecture was requested, return that. + if (arch_kind == eArchKind32) + return g_fields->m_host_arch_32; + if (arch_kind == eArchKind64) + return g_fields->m_host_arch_64; + + // Otherwise prefer the 64-bit architecture if it is valid. + return (g_fields->m_host_arch_64.IsValid()) ? g_fields->m_host_arch_64 + : g_fields->m_host_arch_32; +} + +llvm::Optional<HostInfoBase::ArchitectureKind> HostInfoBase::ParseArchitectureKind(llvm::StringRef kind) { + return llvm::StringSwitch<llvm::Optional<ArchitectureKind>>(kind) + .Case(LLDB_ARCH_DEFAULT, eArchKindDefault) + .Case(LLDB_ARCH_DEFAULT_32BIT, eArchKind32) + .Case(LLDB_ARCH_DEFAULT_64BIT, eArchKind64) + .Default(llvm::None); +} + +FileSpec HostInfoBase::GetShlibDir() { + llvm::call_once(g_fields->m_lldb_so_dir_once, []() { + if (!HostInfo::ComputeSharedLibraryDirectory(g_fields->m_lldb_so_dir)) + g_fields->m_lldb_so_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "shlib dir -> `{0}`", g_fields->m_lldb_so_dir); + }); + return g_fields->m_lldb_so_dir; +} + +FileSpec HostInfoBase::GetSupportExeDir() { + llvm::call_once(g_fields->m_lldb_support_exe_dir_once, []() { + if (!HostInfo::ComputeSupportExeDirectory(g_fields->m_lldb_support_exe_dir)) + g_fields->m_lldb_support_exe_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "support exe dir -> `{0}`", g_fields->m_lldb_support_exe_dir); + }); + return g_fields->m_lldb_support_exe_dir; +} + +FileSpec HostInfoBase::GetHeaderDir() { + llvm::call_once(g_fields->m_lldb_headers_dir_once, []() { + if (!HostInfo::ComputeHeaderDirectory(g_fields->m_lldb_headers_dir)) + g_fields->m_lldb_headers_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "header dir -> `{0}`", g_fields->m_lldb_headers_dir); + }); + return g_fields->m_lldb_headers_dir; +} + +FileSpec HostInfoBase::GetSystemPluginDir() { + llvm::call_once(g_fields->m_lldb_system_plugin_dir_once, []() { + if (!HostInfo::ComputeSystemPluginsDirectory(g_fields->m_lldb_system_plugin_dir)) + g_fields->m_lldb_system_plugin_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "system plugin dir -> `{0}`", + g_fields->m_lldb_system_plugin_dir); + }); + return g_fields->m_lldb_system_plugin_dir; +} + +FileSpec HostInfoBase::GetUserPluginDir() { + llvm::call_once(g_fields->m_lldb_user_plugin_dir_once, []() { + if (!HostInfo::ComputeUserPluginsDirectory(g_fields->m_lldb_user_plugin_dir)) + g_fields->m_lldb_user_plugin_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "user plugin dir -> `{0}`", g_fields->m_lldb_user_plugin_dir); + }); + return g_fields->m_lldb_user_plugin_dir; +} + +FileSpec HostInfoBase::GetProcessTempDir() { + llvm::call_once(g_fields->m_lldb_process_tmp_dir_once, []() { + if (!HostInfo::ComputeProcessTempFileDirectory( g_fields->m_lldb_process_tmp_dir)) + g_fields->m_lldb_process_tmp_dir = FileSpec(); + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "process temp dir -> `{0}`", + g_fields->m_lldb_process_tmp_dir); + }); + return g_fields->m_lldb_process_tmp_dir; +} + +FileSpec HostInfoBase::GetGlobalTempDir() { + llvm::call_once(g_fields->m_lldb_global_tmp_dir_once, []() { + if (!HostInfo::ComputeGlobalTempFileDirectory( g_fields->m_lldb_global_tmp_dir)) + g_fields->m_lldb_global_tmp_dir = FileSpec(); + + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + LLDB_LOG(log, "global temp dir -> `{0}`", g_fields->m_lldb_global_tmp_dir); + }); + return g_fields->m_lldb_global_tmp_dir; +} + +ArchSpec HostInfoBase::GetAugmentedArchSpec(llvm::StringRef triple) { + if (triple.empty()) + return ArchSpec(); + llvm::Triple normalized_triple(llvm::Triple::normalize(triple)); + if (!ArchSpec::ContainsOnlyArch(normalized_triple)) + return ArchSpec(triple); + + if (auto kind = HostInfo::ParseArchitectureKind(triple)) + return HostInfo::GetArchitecture(*kind); + + llvm::Triple host_triple(llvm::sys::getDefaultTargetTriple()); + + if (normalized_triple.getVendorName().empty()) + normalized_triple.setVendor(host_triple.getVendor()); + if (normalized_triple.getOSName().empty()) + normalized_triple.setOS(host_triple.getOS()); + if (normalized_triple.getEnvironmentName().empty()) + normalized_triple.setEnvironment(host_triple.getEnvironment()); + return ArchSpec(normalized_triple); +} + +bool HostInfoBase::ComputePathRelativeToLibrary(FileSpec &file_spec, + llvm::StringRef dir) { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST); + + FileSpec lldb_file_spec = GetShlibDir(); + if (!lldb_file_spec) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + LLDB_LOGF(log, + "HostInfo::%s() attempting to " + "derive the path %s relative to liblldb install path: %s", + __FUNCTION__, dir.data(), raw_path.c_str()); + + // Drop bin (windows) or lib + llvm::StringRef parent_path = llvm::sys::path::parent_path(raw_path); + if (parent_path.empty()) { + LLDB_LOGF(log, + "HostInfo::%s() failed to find liblldb within the shared " + "lib path", + __FUNCTION__); + return false; + } + + raw_path = (parent_path + dir).str(); + LLDB_LOGF(log, "HostInfo::%s() derived the path as: %s", __FUNCTION__, + raw_path.c_str()); + file_spec.GetDirectory().SetString(raw_path); + return (bool)file_spec.GetDirectory(); +} + +bool HostInfoBase::ComputeSharedLibraryDirectory(FileSpec &file_spec) { + // To get paths related to LLDB we get the path to the executable that + // contains this function. On MacOSX this will be "LLDB.framework/.../LLDB". + // On other posix systems, we will get .../lib(64|32)?/liblldb.so. + + FileSpec lldb_file_spec(Host::GetModuleFileSpecForHostAddress( + reinterpret_cast<void *>( + HostInfoBase::ComputeSharedLibraryDirectory))); + + // This is necessary because when running the testsuite the shlib might be a + // symbolic link inside the Python resource dir. + FileSystem::Instance().ResolveSymbolicLink(lldb_file_spec, lldb_file_spec); + + // Remove the filename so that this FileSpec only represents the directory. + file_spec.GetDirectory() = lldb_file_spec.GetDirectory(); + + return (bool)file_spec.GetDirectory(); +} + +bool HostInfoBase::ComputeSupportExeDirectory(FileSpec &file_spec) { + file_spec = GetShlibDir(); + return bool(file_spec); +} + +bool HostInfoBase::ComputeProcessTempFileDirectory(FileSpec &file_spec) { + FileSpec temp_file_spec; + if (!HostInfo::ComputeGlobalTempFileDirectory(temp_file_spec)) + return false; + + std::string pid_str{llvm::to_string(Host::GetCurrentProcessID())}; + temp_file_spec.AppendPathComponent(pid_str); + if (llvm::sys::fs::create_directory(temp_file_spec.GetPath())) + return false; + + file_spec.GetDirectory().SetCString(temp_file_spec.GetCString()); + return true; +} + +bool HostInfoBase::ComputeTempFileBaseDirectory(FileSpec &file_spec) { + llvm::SmallVector<char, 16> tmpdir; + llvm::sys::path::system_temp_directory(/*ErasedOnReboot*/ true, tmpdir); + file_spec = FileSpec(std::string(tmpdir.data(), tmpdir.size())); + FileSystem::Instance().Resolve(file_spec); + return true; +} + +bool HostInfoBase::ComputeGlobalTempFileDirectory(FileSpec &file_spec) { + file_spec.Clear(); + + FileSpec temp_file_spec; + if (!HostInfo::ComputeTempFileBaseDirectory(temp_file_spec)) + return false; + + temp_file_spec.AppendPathComponent("lldb"); + if (llvm::sys::fs::create_directory(temp_file_spec.GetPath())) + return false; + + file_spec.GetDirectory().SetCString(temp_file_spec.GetCString()); + return true; +} + +bool HostInfoBase::ComputeHeaderDirectory(FileSpec &file_spec) { + // TODO(zturner): Figure out how to compute the header directory for all + // platforms. + return false; +} + +bool HostInfoBase::ComputeSystemPluginsDirectory(FileSpec &file_spec) { + // TODO(zturner): Figure out how to compute the system plugins directory for + // all platforms. + return false; +} + +bool HostInfoBase::ComputeUserPluginsDirectory(FileSpec &file_spec) { + // TODO(zturner): Figure out how to compute the user plugins directory for + // all platforms. + return false; +} + +void HostInfoBase::ComputeHostArchitectureSupport(ArchSpec &arch_32, + ArchSpec &arch_64) { + llvm::Triple triple(llvm::sys::getProcessTriple()); + + arch_32.Clear(); + arch_64.Clear(); + + switch (triple.getArch()) { + default: + arch_32.SetTriple(triple); + break; + + case llvm::Triple::aarch64: + case llvm::Triple::ppc64: + case llvm::Triple::ppc64le: + case llvm::Triple::x86_64: + arch_64.SetTriple(triple); + arch_32.SetTriple(triple.get32BitArchVariant()); + break; + + case llvm::Triple::mips64: + case llvm::Triple::mips64el: + case llvm::Triple::sparcv9: + case llvm::Triple::systemz: + arch_64.SetTriple(triple); + break; + } +} diff --git a/gnu/llvm/lldb/source/Host/common/HostNativeThreadBase.cpp b/gnu/llvm/lldb/source/Host/common/HostNativeThreadBase.cpp new file mode 100644 index 00000000000..fe7d85acaf1 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/HostNativeThreadBase.cpp @@ -0,0 +1,69 @@ +//===-- HostNativeThreadBase.cpp --------------------------------*- C++ -*-===// +// +// 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/HostNativeThreadBase.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/Log.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Threading.h" + +using namespace lldb; +using namespace lldb_private; + +HostNativeThreadBase::HostNativeThreadBase() + : m_thread(LLDB_INVALID_HOST_THREAD), m_result(0) {} + +HostNativeThreadBase::HostNativeThreadBase(thread_t thread) + : m_thread(thread), m_result(0) {} + +lldb::thread_t HostNativeThreadBase::GetSystemHandle() const { + return m_thread; +} + +lldb::thread_result_t HostNativeThreadBase::GetResult() const { + return m_result; +} + +bool HostNativeThreadBase::IsJoinable() const { + return m_thread != LLDB_INVALID_HOST_THREAD; +} + +void HostNativeThreadBase::Reset() { + m_thread = LLDB_INVALID_HOST_THREAD; + m_result = 0; +} + +bool HostNativeThreadBase::EqualsThread(lldb::thread_t thread) const { + return m_thread == thread; +} + +lldb::thread_t HostNativeThreadBase::Release() { + lldb::thread_t result = m_thread; + m_thread = LLDB_INVALID_HOST_THREAD; + m_result = 0; + + return result; +} + +lldb::thread_result_t +HostNativeThreadBase::ThreadCreateTrampoline(lldb::thread_arg_t arg) { + ThreadLauncher::HostThreadCreateInfo *info = + (ThreadLauncher::HostThreadCreateInfo *)arg; + llvm::set_thread_name(info->thread_name); + + thread_func_t thread_fptr = info->thread_fptr; + thread_arg_t thread_arg = info->thread_arg; + + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + LLDB_LOGF(log, "thread created"); + + delete info; + return thread_fptr(thread_arg); +} diff --git a/gnu/llvm/lldb/source/Host/common/HostProcess.cpp b/gnu/llvm/lldb/source/Host/common/HostProcess.cpp new file mode 100644 index 00000000000..e180687551f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/HostProcess.cpp @@ -0,0 +1,47 @@ +//===-- HostProcess.cpp -----------------------------------------*- C++ -*-===// +// +// 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/HostProcess.h" +#include "lldb/Host/HostNativeProcess.h" +#include "lldb/Host/HostThread.h" + +using namespace lldb; +using namespace lldb_private; + +HostProcess::HostProcess() : m_native_process(new HostNativeProcess) {} + +HostProcess::HostProcess(lldb::process_t process) + : m_native_process(new HostNativeProcess(process)) {} + +HostProcess::~HostProcess() {} + +Status HostProcess::Terminate() { return m_native_process->Terminate(); } + +Status HostProcess::GetMainModule(FileSpec &file_spec) const { + return m_native_process->GetMainModule(file_spec); +} + +lldb::pid_t HostProcess::GetProcessId() const { + return m_native_process->GetProcessId(); +} + +bool HostProcess::IsRunning() const { return m_native_process->IsRunning(); } + +llvm::Expected<HostThread> +HostProcess::StartMonitoring(const Host::MonitorChildProcessCallback &callback, + bool monitor_signals) { + return m_native_process->StartMonitoring(callback, monitor_signals); +} + +HostNativeProcessBase &HostProcess::GetNativeProcess() { + return *m_native_process; +} + +const HostNativeProcessBase &HostProcess::GetNativeProcess() const { + return *m_native_process; +} diff --git a/gnu/llvm/lldb/source/Host/common/HostThread.cpp b/gnu/llvm/lldb/source/Host/common/HostThread.cpp new file mode 100644 index 00000000000..89cadce5b20 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/HostThread.cpp @@ -0,0 +1,46 @@ +//===-- HostThread.cpp ------------------------------------------*- C++ -*-===// +// +// 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/HostThread.h" +#include "lldb/Host/HostNativeThread.h" + +using namespace lldb; +using namespace lldb_private; + +HostThread::HostThread() : m_native_thread(new HostNativeThread) {} + +HostThread::HostThread(lldb::thread_t thread) + : m_native_thread(new HostNativeThread(thread)) {} + +Status HostThread::Join(lldb::thread_result_t *result) { + return m_native_thread->Join(result); +} + +Status HostThread::Cancel() { return m_native_thread->Cancel(); } + +void HostThread::Reset() { return m_native_thread->Reset(); } + +lldb::thread_t HostThread::Release() { return m_native_thread->Release(); } + +bool HostThread::IsJoinable() const { return m_native_thread->IsJoinable(); } + +HostNativeThread &HostThread::GetNativeThread() { + return static_cast<HostNativeThread &>(*m_native_thread); +} + +const HostNativeThread &HostThread::GetNativeThread() const { + return static_cast<const HostNativeThread &>(*m_native_thread); +} + +lldb::thread_result_t HostThread::GetResult() const { + return m_native_thread->GetResult(); +} + +bool HostThread::EqualsThread(lldb::thread_t thread) const { + return m_native_thread->EqualsThread(thread); +} diff --git a/gnu/llvm/lldb/source/Host/common/LZMA.cpp b/gnu/llvm/lldb/source/Host/common/LZMA.cpp new file mode 100644 index 00000000000..02be8a09df6 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/LZMA.cpp @@ -0,0 +1,146 @@ +//===-- LZMA.cpp ------------------------------------------------*- C++ -*-===// +// +// 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 "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#if LLDB_ENABLE_LZMA +#include <lzma.h> +#endif // LLDB_ENABLE_LZMA + +namespace lldb_private { + +namespace lzma { + +#if !LLDB_ENABLE_LZMA +bool isAvailable() { return false; } +llvm::Expected<uint64_t> +getUncompressedSize(llvm::ArrayRef<uint8_t> InputBuffer) { + llvm_unreachable("lzma::getUncompressedSize is unavailable"); +} + +llvm::Error uncompress(llvm::ArrayRef<uint8_t> InputBuffer, + llvm::SmallVectorImpl<uint8_t> &Uncompressed) { + llvm_unreachable("lzma::uncompress is unavailable"); +} + +#else // LLDB_ENABLE_LZMA + +bool isAvailable() { return true; } + +static const char *convertLZMACodeToString(lzma_ret Code) { + switch (Code) { + case LZMA_STREAM_END: + return "lzma error: LZMA_STREAM_END"; + case LZMA_NO_CHECK: + return "lzma error: LZMA_NO_CHECK"; + case LZMA_UNSUPPORTED_CHECK: + return "lzma error: LZMA_UNSUPPORTED_CHECK"; + case LZMA_GET_CHECK: + return "lzma error: LZMA_GET_CHECK"; + case LZMA_MEM_ERROR: + return "lzma error: LZMA_MEM_ERROR"; + case LZMA_MEMLIMIT_ERROR: + return "lzma error: LZMA_MEMLIMIT_ERROR"; + case LZMA_FORMAT_ERROR: + return "lzma error: LZMA_FORMAT_ERROR"; + case LZMA_OPTIONS_ERROR: + return "lzma error: LZMA_OPTIONS_ERROR"; + case LZMA_DATA_ERROR: + return "lzma error: LZMA_DATA_ERROR"; + case LZMA_BUF_ERROR: + return "lzma error: LZMA_BUF_ERROR"; + case LZMA_PROG_ERROR: + return "lzma error: LZMA_PROG_ERROR"; + default: + llvm_unreachable("unknown or unexpected lzma status code"); + } +} + +llvm::Expected<uint64_t> +getUncompressedSize(llvm::ArrayRef<uint8_t> InputBuffer) { + lzma_stream_flags opts{}; + if (InputBuffer.size() < LZMA_STREAM_HEADER_SIZE) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "size of xz-compressed blob (%lu bytes) is smaller than the " + "LZMA_STREAM_HEADER_SIZE (%lu bytes)", + InputBuffer.size(), LZMA_STREAM_HEADER_SIZE); + } + + // Decode xz footer. + lzma_ret xzerr = lzma_stream_footer_decode( + &opts, InputBuffer.take_back(LZMA_STREAM_HEADER_SIZE).data()); + if (xzerr != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_stream_footer_decode()=%s", + convertLZMACodeToString(xzerr)); + } + if (InputBuffer.size() < (opts.backward_size + LZMA_STREAM_HEADER_SIZE)) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "xz-compressed buffer size (%lu bytes) too small (required at " + "least %lu bytes) ", + InputBuffer.size(), (opts.backward_size + LZMA_STREAM_HEADER_SIZE)); + } + + // Decode xz index. + lzma_index *xzindex; + uint64_t memlimit(UINT64_MAX); + size_t inpos = 0; + xzerr = lzma_index_buffer_decode( + &xzindex, &memlimit, nullptr, + InputBuffer.take_back(LZMA_STREAM_HEADER_SIZE + opts.backward_size) + .data(), + &inpos, InputBuffer.size()); + if (xzerr != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_index_buffer_decode()=%s", + convertLZMACodeToString(xzerr)); + } + + // Get size of uncompressed file to construct an in-memory buffer of the + // same size on the calling end (if needed). + uint64_t uncompressedSize = lzma_index_uncompressed_size(xzindex); + + // Deallocate xz index as it is no longer needed. + lzma_index_end(xzindex, nullptr); + + return uncompressedSize; +} + +llvm::Error uncompress(llvm::ArrayRef<uint8_t> InputBuffer, + llvm::SmallVectorImpl<uint8_t> &Uncompressed) { + llvm::Expected<uint64_t> uncompressedSize = getUncompressedSize(InputBuffer); + + if (auto err = uncompressedSize.takeError()) + return err; + + Uncompressed.resize(*uncompressedSize); + + // Decompress xz buffer to buffer. + uint64_t memlimit = UINT64_MAX; + size_t inpos = 0; + size_t outpos = 0; + lzma_ret ret = lzma_stream_buffer_decode( + &memlimit, 0, nullptr, InputBuffer.data(), &inpos, InputBuffer.size(), + Uncompressed.data(), &outpos, Uncompressed.size()); + if (ret != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_stream_buffer_decode()=%s", + convertLZMACodeToString(ret)); + } + + return llvm::Error::success(); +} + +#endif // LLDB_ENABLE_LZMA + +} // end of namespace lzma +} // namespace lldb_private diff --git a/gnu/llvm/lldb/source/Host/common/LockFileBase.cpp b/gnu/llvm/lldb/source/Host/common/LockFileBase.cpp new file mode 100644 index 00000000000..744b1eaabb4 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/LockFileBase.cpp @@ -0,0 +1,81 @@ +//===-- LockFileBase.cpp ----------------------------------------*- C++ -*-===// +// +// 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/LockFileBase.h" + +using namespace lldb; +using namespace lldb_private; + +namespace { + +Status AlreadyLocked() { return Status("Already locked"); } + +Status NotLocked() { return Status("Not locked"); } +} + +LockFileBase::LockFileBase(int fd) + : m_fd(fd), m_locked(false), m_start(0), m_len(0) {} + +bool LockFileBase::IsLocked() const { return m_locked; } + +Status LockFileBase::WriteLock(const uint64_t start, const uint64_t len) { + return DoLock([&](const uint64_t start, + const uint64_t len) { return DoWriteLock(start, len); }, + start, len); +} + +Status LockFileBase::TryWriteLock(const uint64_t start, const uint64_t len) { + return DoLock([&](const uint64_t start, + const uint64_t len) { return DoTryWriteLock(start, len); }, + start, len); +} + +Status LockFileBase::ReadLock(const uint64_t start, const uint64_t len) { + return DoLock([&](const uint64_t start, + const uint64_t len) { return DoReadLock(start, len); }, + start, len); +} + +Status LockFileBase::TryReadLock(const uint64_t start, const uint64_t len) { + return DoLock([&](const uint64_t start, + const uint64_t len) { return DoTryReadLock(start, len); }, + start, len); +} + +Status LockFileBase::Unlock() { + if (!IsLocked()) + return NotLocked(); + + const auto error = DoUnlock(); + if (error.Success()) { + m_locked = false; + m_start = 0; + m_len = 0; + } + return error; +} + +bool LockFileBase::IsValidFile() const { return m_fd != -1; } + +Status LockFileBase::DoLock(const Locker &locker, const uint64_t start, + const uint64_t len) { + if (!IsValidFile()) + return Status("File is invalid"); + + if (IsLocked()) + return AlreadyLocked(); + + const auto error = locker(start, len); + if (error.Success()) { + m_locked = true; + m_start = start; + m_len = len; + } + + return error; +} diff --git a/gnu/llvm/lldb/source/Host/common/MainLoop.cpp b/gnu/llvm/lldb/source/Host/common/MainLoop.cpp new file mode 100644 index 00000000000..240320f8324 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/MainLoop.cpp @@ -0,0 +1,409 @@ +//===-- MainLoop.cpp --------------------------------------------*- C++ -*-===// +// +// 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 "llvm/Config/llvm-config.h" +#include "lldb/Host/Config.h" + +#include "lldb/Host/MainLoop.h" +#include "lldb/Host/PosixApi.h" +#include "lldb/Utility/Status.h" +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <csignal> +#include <time.h> +#include <vector> + +// Multiplexing is implemented using kqueue on systems that support it (BSD +// variants including OSX). On linux we use ppoll, while android uses pselect +// (ppoll is present but not implemented properly). On windows we use WSApoll +// (which does not support signals). + +#if HAVE_SYS_EVENT_H +#include <sys/event.h> +#elif defined(_WIN32) +#include <winsock2.h> +#elif defined(__ANDROID__) +#include <sys/syscall.h> +#else +#include <poll.h> +#endif + +#ifdef _WIN32 +#define POLL WSAPoll +#else +#define POLL poll +#endif + +#if SIGNAL_POLLING_UNSUPPORTED +#ifdef _WIN32 +typedef int sigset_t; +typedef int siginfo_t; +#endif + +int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout_ts, + const sigset_t *) { + int timeout = + (timeout_ts == nullptr) + ? -1 + : (timeout_ts->tv_sec * 1000 + timeout_ts->tv_nsec / 1000000); + return POLL(fds, nfds, timeout); +} + +#endif + +using namespace lldb; +using namespace lldb_private; + +static sig_atomic_t g_signal_flags[NSIG]; + +#ifndef SIGNAL_POLLING_UNSUPPORTED +static void SignalHandler(int signo, siginfo_t *info, void *) { + assert(signo < NSIG); + g_signal_flags[signo] = 1; +} +#endif + +class MainLoop::RunImpl { +public: + RunImpl(MainLoop &loop); + ~RunImpl() = default; + + Status Poll(); + void ProcessEvents(); + +private: + MainLoop &loop; + +#if HAVE_SYS_EVENT_H + std::vector<struct kevent> in_events; + struct kevent out_events[4]; + int num_events = -1; + +#else +#ifdef __ANDROID__ + fd_set read_fd_set; +#else + std::vector<struct pollfd> read_fds; +#endif + + sigset_t get_sigmask(); +#endif +}; + +#if HAVE_SYS_EVENT_H +MainLoop::RunImpl::RunImpl(MainLoop &loop) : loop(loop) { + in_events.reserve(loop.m_read_fds.size()); +} + +Status MainLoop::RunImpl::Poll() { + in_events.resize(loop.m_read_fds.size()); + unsigned i = 0; + for (auto &fd : loop.m_read_fds) + EV_SET(&in_events[i++], fd.first, EVFILT_READ, EV_ADD, 0, 0, 0); + + num_events = kevent(loop.m_kqueue, in_events.data(), in_events.size(), + out_events, llvm::array_lengthof(out_events), nullptr); + + if (num_events < 0) { + if (errno == EINTR) { + // in case of EINTR, let the main loop run one iteration + // we need to zero num_events to avoid assertions failing + num_events = 0; + } else + return Status(errno, eErrorTypePOSIX); + } + return Status(); +} + +void MainLoop::RunImpl::ProcessEvents() { + assert(num_events >= 0); + for (int i = 0; i < num_events; ++i) { + if (loop.m_terminate_request) + return; + switch (out_events[i].filter) { + case EVFILT_READ: + loop.ProcessReadObject(out_events[i].ident); + break; + case EVFILT_SIGNAL: + loop.ProcessSignal(out_events[i].ident); + break; + default: + llvm_unreachable("Unknown event"); + } + } +} +#else +MainLoop::RunImpl::RunImpl(MainLoop &loop) : loop(loop) { +#ifndef __ANDROID__ + read_fds.reserve(loop.m_read_fds.size()); +#endif +} + +sigset_t MainLoop::RunImpl::get_sigmask() { + sigset_t sigmask; +#if defined(_WIN32) + sigmask = 0; +#elif SIGNAL_POLLING_UNSUPPORTED + sigemptyset(&sigmask); +#else + int ret = pthread_sigmask(SIG_SETMASK, nullptr, &sigmask); + assert(ret == 0); + (void) ret; + + for (const auto &sig : loop.m_signals) + sigdelset(&sigmask, sig.first); +#endif + return sigmask; +} + +#ifdef __ANDROID__ +Status MainLoop::RunImpl::Poll() { + // ppoll(2) is not supported on older all android versions. Also, older + // versions android (API <= 19) implemented pselect in a non-atomic way, as a + // combination of pthread_sigmask and select. This is not sufficient for us, + // as we rely on the atomicity to correctly implement signal polling, so we + // call the underlying syscall ourselves. + + FD_ZERO(&read_fd_set); + int nfds = 0; + for (const auto &fd : loop.m_read_fds) { + FD_SET(fd.first, &read_fd_set); + nfds = std::max(nfds, fd.first + 1); + } + + union { + sigset_t set; + uint64_t pad; + } kernel_sigset; + memset(&kernel_sigset, 0, sizeof(kernel_sigset)); + kernel_sigset.set = get_sigmask(); + + struct { + void *sigset_ptr; + size_t sigset_len; + } extra_data = {&kernel_sigset, sizeof(kernel_sigset)}; + if (syscall(__NR_pselect6, nfds, &read_fd_set, nullptr, nullptr, nullptr, + &extra_data) == -1 && + errno != EINTR) + return Status(errno, eErrorTypePOSIX); + + return Status(); +} +#else +Status MainLoop::RunImpl::Poll() { + read_fds.clear(); + + sigset_t sigmask = get_sigmask(); + + for (const auto &fd : loop.m_read_fds) { + struct pollfd pfd; + pfd.fd = fd.first; + pfd.events = POLLIN; + pfd.revents = 0; + read_fds.push_back(pfd); + } + + if (ppoll(read_fds.data(), read_fds.size(), nullptr, &sigmask) == -1 && + errno != EINTR) + return Status(errno, eErrorTypePOSIX); + + return Status(); +} +#endif + +void MainLoop::RunImpl::ProcessEvents() { +#ifdef __ANDROID__ + // Collect first all readable file descriptors into a separate vector and + // then iterate over it to invoke callbacks. Iterating directly over + // loop.m_read_fds is not possible because the callbacks can modify the + // container which could invalidate the iterator. + std::vector<IOObject::WaitableHandle> fds; + for (const auto &fd : loop.m_read_fds) + if (FD_ISSET(fd.first, &read_fd_set)) + fds.push_back(fd.first); + + for (const auto &handle : fds) { +#else + for (const auto &fd : read_fds) { + if ((fd.revents & (POLLIN | POLLHUP)) == 0) + continue; + IOObject::WaitableHandle handle = fd.fd; +#endif + if (loop.m_terminate_request) + return; + + loop.ProcessReadObject(handle); + } + + std::vector<int> signals; + for (const auto &entry : loop.m_signals) + if (g_signal_flags[entry.first] != 0) + signals.push_back(entry.first); + + for (const auto &signal : signals) { + if (loop.m_terminate_request) + return; + g_signal_flags[signal] = 0; + loop.ProcessSignal(signal); + } +} +#endif + +MainLoop::MainLoop() { +#if HAVE_SYS_EVENT_H + m_kqueue = kqueue(); + assert(m_kqueue >= 0); +#endif +} +MainLoop::~MainLoop() { +#if HAVE_SYS_EVENT_H + close(m_kqueue); +#endif + assert(m_read_fds.size() == 0); + assert(m_signals.size() == 0); +} + +MainLoop::ReadHandleUP MainLoop::RegisterReadObject(const IOObjectSP &object_sp, + const Callback &callback, + Status &error) { +#ifdef _WIN32 + if (object_sp->GetFdType() != IOObject:: eFDTypeSocket) { + error.SetErrorString("MainLoop: non-socket types unsupported on Windows"); + return nullptr; + } +#endif + if (!object_sp || !object_sp->IsValid()) { + error.SetErrorString("IO object is not valid."); + return nullptr; + } + + const bool inserted = + m_read_fds.insert({object_sp->GetWaitableHandle(), callback}).second; + if (!inserted) { + error.SetErrorStringWithFormat("File descriptor %d already monitored.", + object_sp->GetWaitableHandle()); + return nullptr; + } + + return CreateReadHandle(object_sp); +} + +// We shall block the signal, then install the signal handler. The signal will +// be unblocked in the Run() function to check for signal delivery. +MainLoop::SignalHandleUP +MainLoop::RegisterSignal(int signo, const Callback &callback, Status &error) { +#ifdef SIGNAL_POLLING_UNSUPPORTED + error.SetErrorString("Signal polling is not supported on this platform."); + return nullptr; +#else + if (m_signals.find(signo) != m_signals.end()) { + error.SetErrorStringWithFormat("Signal %d already monitored.", signo); + return nullptr; + } + + SignalInfo info; + info.callback = callback; + struct sigaction new_action; + new_action.sa_sigaction = &SignalHandler; + new_action.sa_flags = SA_SIGINFO; + sigemptyset(&new_action.sa_mask); + sigaddset(&new_action.sa_mask, signo); + sigset_t old_set; + + g_signal_flags[signo] = 0; + + // Even if using kqueue, the signal handler will still be invoked, so it's + // important to replace it with our "benign" handler. + int ret = sigaction(signo, &new_action, &info.old_action); + (void)ret; + assert(ret == 0 && "sigaction failed"); + +#if HAVE_SYS_EVENT_H + struct kevent ev; + EV_SET(&ev, signo, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); + ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr); + assert(ret == 0); +#endif + + // If we're using kqueue, the signal needs to be unblocked in order to + // receive it. If using pselect/ppoll, we need to block it, and later unblock + // it as a part of the system call. + ret = pthread_sigmask(HAVE_SYS_EVENT_H ? SIG_UNBLOCK : SIG_BLOCK, + &new_action.sa_mask, &old_set); + assert(ret == 0 && "pthread_sigmask failed"); + info.was_blocked = sigismember(&old_set, signo); + m_signals.insert({signo, info}); + + return SignalHandleUP(new SignalHandle(*this, signo)); +#endif +} + +void MainLoop::UnregisterReadObject(IOObject::WaitableHandle handle) { + bool erased = m_read_fds.erase(handle); + UNUSED_IF_ASSERT_DISABLED(erased); + assert(erased); +} + +void MainLoop::UnregisterSignal(int signo) { +#if SIGNAL_POLLING_UNSUPPORTED + Status("Signal polling is not supported on this platform."); +#else + auto it = m_signals.find(signo); + assert(it != m_signals.end()); + + sigaction(signo, &it->second.old_action, nullptr); + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signo); + int ret = pthread_sigmask(it->second.was_blocked ? SIG_BLOCK : SIG_UNBLOCK, + &set, nullptr); + assert(ret == 0); + (void)ret; + +#if HAVE_SYS_EVENT_H + struct kevent ev; + EV_SET(&ev, signo, EVFILT_SIGNAL, EV_DELETE, 0, 0, 0); + ret = kevent(m_kqueue, &ev, 1, nullptr, 0, nullptr); + assert(ret == 0); +#endif + + m_signals.erase(it); +#endif +} + +Status MainLoop::Run() { + m_terminate_request = false; + + Status error; + RunImpl impl(*this); + + // run until termination or until we run out of things to listen to + while (!m_terminate_request && (!m_read_fds.empty() || !m_signals.empty())) { + + error = impl.Poll(); + if (error.Fail()) + return error; + + impl.ProcessEvents(); + } + return Status(); +} + +void MainLoop::ProcessSignal(int signo) { + auto it = m_signals.find(signo); + if (it != m_signals.end()) + it->second.callback(*this); // Do the work +} + +void MainLoop::ProcessReadObject(IOObject::WaitableHandle handle) { + auto it = m_read_fds.find(handle); + if (it != m_read_fds.end()) + it->second(*this); // Do the work +} diff --git a/gnu/llvm/lldb/source/Host/common/MonitoringProcessLauncher.cpp b/gnu/llvm/lldb/source/Host/common/MonitoringProcessLauncher.cpp new file mode 100644 index 00000000000..55e9f69a089 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/MonitoringProcessLauncher.cpp @@ -0,0 +1,70 @@ +//===-- MonitoringProcessLauncher.cpp ---------------------------*- C++ -*-===// +// +// 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/MonitoringProcessLauncher.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Utility/Log.h" + +#include "llvm/Support/FileSystem.h" + +using namespace lldb; +using namespace lldb_private; + +MonitoringProcessLauncher::MonitoringProcessLauncher( + std::unique_ptr<ProcessLauncher> delegate_launcher) + : m_delegate_launcher(std::move(delegate_launcher)) {} + +HostProcess +MonitoringProcessLauncher::LaunchProcess(const ProcessLaunchInfo &launch_info, + Status &error) { + ProcessLaunchInfo resolved_info(launch_info); + + error.Clear(); + + FileSystem &fs = FileSystem::Instance(); + FileSpec exe_spec(resolved_info.GetExecutableFile()); + + if (!fs.Exists(exe_spec)) + FileSystem::Instance().Resolve(exe_spec); + + if (!fs.Exists(exe_spec)) + FileSystem::Instance().ResolveExecutableLocation(exe_spec); + + if (!fs.Exists(exe_spec)) { + error.SetErrorStringWithFormatv("executable doesn't exist: '{0}'", + exe_spec); + return HostProcess(); + } + + resolved_info.SetExecutableFile(exe_spec, false); + assert(!resolved_info.GetFlags().Test(eLaunchFlagLaunchInTTY)); + + HostProcess process = + m_delegate_launcher->LaunchProcess(resolved_info, error); + + if (process.GetProcessId() != LLDB_INVALID_PROCESS_ID) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + assert(launch_info.GetMonitorProcessCallback()); + llvm::Expected<HostThread> maybe_thread = + process.StartMonitoring(launch_info.GetMonitorProcessCallback(), + launch_info.GetMonitorSignals()); + if (!maybe_thread) + error.SetErrorStringWithFormatv("failed to launch host thread: {}", + llvm::toString(maybe_thread.takeError())); + if (log) + log->PutCString("started monitoring child process."); + } else { + // Invalid process ID, something didn't go well + if (error.Success()) + error.SetErrorString("process launch failed for unknown reasons"); + } + return process; +} diff --git a/gnu/llvm/lldb/source/Host/common/NativeProcessProtocol.cpp b/gnu/llvm/lldb/source/Host/common/NativeProcessProtocol.cpp new file mode 100644 index 00000000000..712c448dc2c --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -0,0 +1,757 @@ +//===-- NativeProcessProtocol.cpp -------------------------------*- C++ -*-===// +// +// 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/common/NativeProcessProtocol.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/common/NativeBreakpointList.h" +#include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" +#include "lldb/lldb-enumerations.h" + +#include "llvm/Support/Process.h" + +using namespace lldb; +using namespace lldb_private; + +// NativeProcessProtocol Members + +NativeProcessProtocol::NativeProcessProtocol(lldb::pid_t pid, int terminal_fd, + NativeDelegate &delegate) + : m_pid(pid), m_terminal_fd(terminal_fd) { + bool registered = RegisterNativeDelegate(delegate); + assert(registered); + (void)registered; +} + +lldb_private::Status NativeProcessProtocol::Interrupt() { + Status error; +#if !defined(SIGSTOP) + error.SetErrorString("local host does not support signaling"); + return error; +#else + return Signal(SIGSTOP); +#endif +} + +Status NativeProcessProtocol::IgnoreSignals(llvm::ArrayRef<int> signals) { + m_signals_to_ignore.clear(); + m_signals_to_ignore.insert(signals.begin(), signals.end()); + return Status(); +} + +lldb_private::Status +NativeProcessProtocol::GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) { + // Default: not implemented. + return Status("not implemented"); +} + +llvm::Optional<WaitStatus> NativeProcessProtocol::GetExitStatus() { + if (m_state == lldb::eStateExited) + return m_exit_status; + + return llvm::None; +} + +bool NativeProcessProtocol::SetExitStatus(WaitStatus status, + bool bNotifyStateChange) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + LLDB_LOG(log, "status = {0}, notify = {1}", status, bNotifyStateChange); + + // Exit status already set + if (m_state == lldb::eStateExited) { + if (m_exit_status) + LLDB_LOG(log, "exit status already set to {0}", *m_exit_status); + else + LLDB_LOG(log, "state is exited, but status not set"); + return false; + } + + m_state = lldb::eStateExited; + m_exit_status = status; + + if (bNotifyStateChange) + SynchronouslyNotifyProcessStateChanged(lldb::eStateExited); + + return true; +} + +NativeThreadProtocol *NativeProcessProtocol::GetThreadAtIndex(uint32_t idx) { + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + if (idx < m_threads.size()) + return m_threads[idx].get(); + return nullptr; +} + +NativeThreadProtocol * +NativeProcessProtocol::GetThreadByIDUnlocked(lldb::tid_t tid) { + for (const auto &thread : m_threads) { + if (thread->GetID() == tid) + return thread.get(); + } + return nullptr; +} + +NativeThreadProtocol *NativeProcessProtocol::GetThreadByID(lldb::tid_t tid) { + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + return GetThreadByIDUnlocked(tid); +} + +bool NativeProcessProtocol::IsAlive() const { + return m_state != eStateDetached && m_state != eStateExited && + m_state != eStateInvalid && m_state != eStateUnloaded; +} + +const NativeWatchpointList::WatchpointMap & +NativeProcessProtocol::GetWatchpointMap() const { + return m_watchpoint_list.GetWatchpointMap(); +} + +llvm::Optional<std::pair<uint32_t, uint32_t>> +NativeProcessProtocol::GetHardwareDebugSupportInfo() const { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // get any thread + NativeThreadProtocol *thread( + const_cast<NativeProcessProtocol *>(this)->GetThreadAtIndex(0)); + if (!thread) { + LLDB_LOG(log, "failed to find a thread to grab a NativeRegisterContext!"); + return llvm::None; + } + + NativeRegisterContext ®_ctx = thread->GetRegisterContext(); + return std::make_pair(reg_ctx.NumSupportedHardwareBreakpoints(), + reg_ctx.NumSupportedHardwareWatchpoints()); +} + +Status NativeProcessProtocol::SetWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags, + bool hardware) { + // This default implementation assumes setting the watchpoint for the process + // will require setting the watchpoint for each of the threads. Furthermore, + // it will track watchpoints set for the process and will add them to each + // thread that is attached to via the (FIXME implement) OnThreadAttached () + // method. + + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Update the thread list + UpdateThreads(); + + // Keep track of the threads we successfully set the watchpoint for. If one + // of the thread watchpoint setting operations fails, back off and remove the + // watchpoint for all the threads that were successfully set so we get back + // to a consistent state. + std::vector<NativeThreadProtocol *> watchpoint_established_threads; + + // Tell each thread to set a watchpoint. In the event that hardware + // watchpoints are requested but the SetWatchpoint fails, try to set a + // software watchpoint as a fallback. It's conceivable that if there are + // more threads than hardware watchpoints available, some of the threads will + // fail to set hardware watchpoints while software ones may be available. + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + for (const auto &thread : m_threads) { + assert(thread && "thread list should not have a NULL thread!"); + + Status thread_error = + thread->SetWatchpoint(addr, size, watch_flags, hardware); + if (thread_error.Fail() && hardware) { + // Try software watchpoints since we failed on hardware watchpoint + // setting and we may have just run out of hardware watchpoints. + thread_error = thread->SetWatchpoint(addr, size, watch_flags, false); + if (thread_error.Success()) + LLDB_LOG(log, + "hardware watchpoint requested but software watchpoint set"); + } + + if (thread_error.Success()) { + // Remember that we set this watchpoint successfully in case we need to + // clear it later. + watchpoint_established_threads.push_back(thread.get()); + } else { + // Unset the watchpoint for each thread we successfully set so that we + // get back to a consistent state of "not set" for the watchpoint. + for (auto unwatch_thread_sp : watchpoint_established_threads) { + Status remove_error = unwatch_thread_sp->RemoveWatchpoint(addr); + if (remove_error.Fail()) + LLDB_LOG(log, "RemoveWatchpoint failed for pid={0}, tid={1}: {2}", + GetID(), unwatch_thread_sp->GetID(), remove_error); + } + + return thread_error; + } + } + return m_watchpoint_list.Add(addr, size, watch_flags, hardware); +} + +Status NativeProcessProtocol::RemoveWatchpoint(lldb::addr_t addr) { + // Update the thread list + UpdateThreads(); + + Status overall_error; + + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + for (const auto &thread : m_threads) { + assert(thread && "thread list should not have a NULL thread!"); + + const Status thread_error = thread->RemoveWatchpoint(addr); + if (thread_error.Fail()) { + // Keep track of the first thread error if any threads fail. We want to + // try to remove the watchpoint from every thread, though, even if one or + // more have errors. + if (!overall_error.Fail()) + overall_error = thread_error; + } + } + const Status error = m_watchpoint_list.Remove(addr); + return overall_error.Fail() ? overall_error : error; +} + +const HardwareBreakpointMap & +NativeProcessProtocol::GetHardwareBreakpointMap() const { + return m_hw_breakpoints_map; +} + +Status NativeProcessProtocol::SetHardwareBreakpoint(lldb::addr_t addr, + size_t size) { + // This default implementation assumes setting a hardware breakpoint for this + // process will require setting same hardware breakpoint for each of its + // existing threads. New thread will do the same once created. + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Update the thread list + UpdateThreads(); + + // Exit here if target does not have required hardware breakpoint capability. + auto hw_debug_cap = GetHardwareDebugSupportInfo(); + + if (hw_debug_cap == llvm::None || hw_debug_cap->first == 0 || + hw_debug_cap->first <= m_hw_breakpoints_map.size()) + return Status("Target does not have required no of hardware breakpoints"); + + // Vector below stores all thread pointer for which we have we successfully + // set this hardware breakpoint. If any of the current process threads fails + // to set this hardware breakpoint then roll back and remove this breakpoint + // for all the threads that had already set it successfully. + std::vector<NativeThreadProtocol *> breakpoint_established_threads; + + // Request to set a hardware breakpoint for each of current process threads. + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + for (const auto &thread : m_threads) { + assert(thread && "thread list should not have a NULL thread!"); + + Status thread_error = thread->SetHardwareBreakpoint(addr, size); + if (thread_error.Success()) { + // Remember that we set this breakpoint successfully in case we need to + // clear it later. + breakpoint_established_threads.push_back(thread.get()); + } else { + // Unset the breakpoint for each thread we successfully set so that we + // get back to a consistent state of "not set" for this hardware + // breakpoint. + for (auto rollback_thread_sp : breakpoint_established_threads) { + Status remove_error = + rollback_thread_sp->RemoveHardwareBreakpoint(addr); + if (remove_error.Fail()) + LLDB_LOG(log, + "RemoveHardwareBreakpoint failed for pid={0}, tid={1}: {2}", + GetID(), rollback_thread_sp->GetID(), remove_error); + } + + return thread_error; + } + } + + // Register new hardware breakpoint into hardware breakpoints map of current + // process. + m_hw_breakpoints_map[addr] = {addr, size}; + + return Status(); +} + +Status NativeProcessProtocol::RemoveHardwareBreakpoint(lldb::addr_t addr) { + // Update the thread list + UpdateThreads(); + + Status error; + + std::lock_guard<std::recursive_mutex> guard(m_threads_mutex); + for (const auto &thread : m_threads) { + assert(thread && "thread list should not have a NULL thread!"); + error = thread->RemoveHardwareBreakpoint(addr); + } + + // Also remove from hardware breakpoint map of current process. + m_hw_breakpoints_map.erase(addr); + + return error; +} + +bool NativeProcessProtocol::RegisterNativeDelegate( + NativeDelegate &native_delegate) { + std::lock_guard<std::recursive_mutex> guard(m_delegates_mutex); + if (std::find(m_delegates.begin(), m_delegates.end(), &native_delegate) != + m_delegates.end()) + return false; + + m_delegates.push_back(&native_delegate); + native_delegate.InitializeDelegate(this); + return true; +} + +bool NativeProcessProtocol::UnregisterNativeDelegate( + NativeDelegate &native_delegate) { + std::lock_guard<std::recursive_mutex> guard(m_delegates_mutex); + + const auto initial_size = m_delegates.size(); + m_delegates.erase( + remove(m_delegates.begin(), m_delegates.end(), &native_delegate), + m_delegates.end()); + + // We removed the delegate if the count of delegates shrank after removing + // all copies of the given native_delegate from the vector. + return m_delegates.size() < initial_size; +} + +void NativeProcessProtocol::SynchronouslyNotifyProcessStateChanged( + lldb::StateType state) { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + std::lock_guard<std::recursive_mutex> guard(m_delegates_mutex); + for (auto native_delegate : m_delegates) + native_delegate->ProcessStateChanged(this, state); + + if (log) { + if (!m_delegates.empty()) { + LLDB_LOGF(log, + "NativeProcessProtocol::%s: sent state notification [%s] " + "from process %" PRIu64, + __FUNCTION__, lldb_private::StateAsCString(state), GetID()); + } else { + LLDB_LOGF(log, + "NativeProcessProtocol::%s: would send state notification " + "[%s] from process %" PRIu64 ", but no delegates", + __FUNCTION__, lldb_private::StateAsCString(state), GetID()); + } + } +} + +void NativeProcessProtocol::NotifyDidExec() { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + LLDB_LOGF(log, "NativeProcessProtocol::%s - preparing to call delegates", + __FUNCTION__); + + { + std::lock_guard<std::recursive_mutex> guard(m_delegates_mutex); + for (auto native_delegate : m_delegates) + native_delegate->DidExec(this); + } +} + +Status NativeProcessProtocol::SetSoftwareBreakpoint(lldb::addr_t addr, + uint32_t size_hint) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + LLDB_LOG(log, "addr = {0:x}, size_hint = {1}", addr, size_hint); + + auto it = m_software_breakpoints.find(addr); + if (it != m_software_breakpoints.end()) { + ++it->second.ref_count; + return Status(); + } + auto expected_bkpt = EnableSoftwareBreakpoint(addr, size_hint); + if (!expected_bkpt) + return Status(expected_bkpt.takeError()); + + m_software_breakpoints.emplace(addr, std::move(*expected_bkpt)); + return Status(); +} + +Status NativeProcessProtocol::RemoveSoftwareBreakpoint(lldb::addr_t addr) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + LLDB_LOG(log, "addr = {0:x}", addr); + auto it = m_software_breakpoints.find(addr); + if (it == m_software_breakpoints.end()) + return Status("Breakpoint not found."); + assert(it->second.ref_count > 0); + if (--it->second.ref_count > 0) + return Status(); + + // This is the last reference. Let's remove the breakpoint. + Status error; + + // Clear a software breakpoint instruction + llvm::SmallVector<uint8_t, 4> curr_break_op( + it->second.breakpoint_opcodes.size(), 0); + + // Read the breakpoint opcode + size_t bytes_read = 0; + error = + ReadMemory(addr, curr_break_op.data(), curr_break_op.size(), bytes_read); + if (error.Fail() || bytes_read < curr_break_op.size()) { + return Status("addr=0x%" PRIx64 + ": tried to read %zu bytes but only read %zu", + addr, curr_break_op.size(), bytes_read); + } + const auto &saved = it->second.saved_opcodes; + // Make sure the breakpoint opcode exists at this address + if (makeArrayRef(curr_break_op) != it->second.breakpoint_opcodes) { + if (curr_break_op != it->second.saved_opcodes) + return Status("Original breakpoint trap is no longer in memory."); + LLDB_LOG(log, + "Saved opcodes ({0:@[x]}) have already been restored at {1:x}.", + llvm::make_range(saved.begin(), saved.end()), addr); + } else { + // We found a valid breakpoint opcode at this address, now restore the + // saved opcode. + size_t bytes_written = 0; + error = WriteMemory(addr, saved.data(), saved.size(), bytes_written); + if (error.Fail() || bytes_written < saved.size()) { + return Status("addr=0x%" PRIx64 + ": tried to write %zu bytes but only wrote %zu", + addr, saved.size(), bytes_written); + } + + // Verify that our original opcode made it back to the inferior + llvm::SmallVector<uint8_t, 4> verify_opcode(saved.size(), 0); + size_t verify_bytes_read = 0; + error = ReadMemory(addr, verify_opcode.data(), verify_opcode.size(), + verify_bytes_read); + if (error.Fail() || verify_bytes_read < verify_opcode.size()) { + return Status("addr=0x%" PRIx64 + ": tried to read %zu verification bytes but only read %zu", + addr, verify_opcode.size(), verify_bytes_read); + } + if (verify_opcode != saved) + LLDB_LOG(log, "Restoring bytes at {0:x}: {1:@[x]}", addr, + llvm::make_range(saved.begin(), saved.end())); + } + + m_software_breakpoints.erase(it); + return Status(); +} + +llvm::Expected<NativeProcessProtocol::SoftwareBreakpoint> +NativeProcessProtocol::EnableSoftwareBreakpoint(lldb::addr_t addr, + uint32_t size_hint) { + Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + + auto expected_trap = GetSoftwareBreakpointTrapOpcode(size_hint); + if (!expected_trap) + return expected_trap.takeError(); + + llvm::SmallVector<uint8_t, 4> saved_opcode_bytes(expected_trap->size(), 0); + // Save the original opcodes by reading them so we can restore later. + size_t bytes_read = 0; + Status error = ReadMemory(addr, saved_opcode_bytes.data(), + saved_opcode_bytes.size(), bytes_read); + if (error.Fail()) + return error.ToError(); + + // Ensure we read as many bytes as we expected. + if (bytes_read != saved_opcode_bytes.size()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Failed to read memory while attempting to set breakpoint: attempted " + "to read {0} bytes but only read {1}.", + saved_opcode_bytes.size(), bytes_read); + } + + LLDB_LOG( + log, "Overwriting bytes at {0:x}: {1:@[x]}", addr, + llvm::make_range(saved_opcode_bytes.begin(), saved_opcode_bytes.end())); + + // Write a software breakpoint in place of the original opcode. + size_t bytes_written = 0; + error = WriteMemory(addr, expected_trap->data(), expected_trap->size(), + bytes_written); + if (error.Fail()) + return error.ToError(); + + // Ensure we wrote as many bytes as we expected. + if (bytes_written != expected_trap->size()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Failed write memory while attempting to set " + "breakpoint: attempted to write {0} bytes but only wrote {1}", + expected_trap->size(), bytes_written); + } + + llvm::SmallVector<uint8_t, 4> verify_bp_opcode_bytes(expected_trap->size(), + 0); + size_t verify_bytes_read = 0; + error = ReadMemory(addr, verify_bp_opcode_bytes.data(), + verify_bp_opcode_bytes.size(), verify_bytes_read); + if (error.Fail()) + return error.ToError(); + + // Ensure we read as many verification bytes as we expected. + if (verify_bytes_read != verify_bp_opcode_bytes.size()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Failed to read memory while " + "attempting to verify breakpoint: attempted to read {0} bytes " + "but only read {1}", + verify_bp_opcode_bytes.size(), verify_bytes_read); + } + + if (llvm::makeArrayRef(verify_bp_opcode_bytes.data(), verify_bytes_read) != + *expected_trap) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Verification of software breakpoint " + "writing failed - trap opcodes not successfully read back " + "after writing when setting breakpoint at {0:x}", + addr); + } + + LLDB_LOG(log, "addr = {0:x}: SUCCESS", addr); + return SoftwareBreakpoint{1, saved_opcode_bytes, *expected_trap}; +} + +llvm::Expected<llvm::ArrayRef<uint8_t>> +NativeProcessProtocol::GetSoftwareBreakpointTrapOpcode(size_t size_hint) { + static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x20, 0xd4}; + static const uint8_t g_i386_opcode[] = {0xCC}; + static const uint8_t g_mips64_opcode[] = {0x00, 0x00, 0x00, 0x0d}; + static const uint8_t g_mips64el_opcode[] = {0x0d, 0x00, 0x00, 0x00}; + static const uint8_t g_s390x_opcode[] = {0x00, 0x01}; + static const uint8_t g_ppc64le_opcode[] = {0x08, 0x00, 0xe0, 0x7f}; // trap + + switch (GetArchitecture().GetMachine()) { + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_32: + return llvm::makeArrayRef(g_aarch64_opcode); + + case llvm::Triple::x86: + case llvm::Triple::x86_64: + return llvm::makeArrayRef(g_i386_opcode); + + case llvm::Triple::mips: + case llvm::Triple::mips64: + return llvm::makeArrayRef(g_mips64_opcode); + + case llvm::Triple::mipsel: + case llvm::Triple::mips64el: + return llvm::makeArrayRef(g_mips64el_opcode); + + case llvm::Triple::systemz: + return llvm::makeArrayRef(g_s390x_opcode); + + case llvm::Triple::ppc64le: + return llvm::makeArrayRef(g_ppc64le_opcode); + + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "CPU type not supported!"); + } +} + +size_t NativeProcessProtocol::GetSoftwareBreakpointPCOffset() { + switch (GetArchitecture().GetMachine()) { + case llvm::Triple::x86: + case llvm::Triple::x86_64: + case llvm::Triple::systemz: + // These architectures report increment the PC after breakpoint is hit. + return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size(); + + case llvm::Triple::arm: + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_32: + case llvm::Triple::mips64: + case llvm::Triple::mips64el: + case llvm::Triple::mips: + case llvm::Triple::mipsel: + case llvm::Triple::ppc64le: + // On these architectures the PC doesn't get updated for breakpoint hits. + return 0; + + default: + llvm_unreachable("CPU type not supported!"); + } +} + +void NativeProcessProtocol::FixupBreakpointPCAsNeeded( + NativeThreadProtocol &thread) { + Log *log = GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS); + + Status error; + + // Find out the size of a breakpoint (might depend on where we are in the + // code). + NativeRegisterContext &context = thread.GetRegisterContext(); + + uint32_t breakpoint_size = GetSoftwareBreakpointPCOffset(); + LLDB_LOG(log, "breakpoint size: {0}", breakpoint_size); + if (breakpoint_size == 0) + return; + + // First try probing for a breakpoint at a software breakpoint location: PC - + // breakpoint size. + const lldb::addr_t initial_pc_addr = context.GetPCfromBreakpointLocation(); + lldb::addr_t breakpoint_addr = initial_pc_addr; + // Do not allow breakpoint probe to wrap around. + if (breakpoint_addr >= breakpoint_size) + breakpoint_addr -= breakpoint_size; + + if (m_software_breakpoints.count(breakpoint_addr) == 0) { + // We didn't find one at a software probe location. Nothing to do. + LLDB_LOG(log, + "pid {0} no lldb software breakpoint found at current pc with " + "adjustment: {1}", + GetID(), breakpoint_addr); + return; + } + + // + // We have a software breakpoint and need to adjust the PC. + // + + // Change the program counter. + LLDB_LOG(log, "pid {0} tid {1}: changing PC from {2:x} to {3:x}", GetID(), + thread.GetID(), initial_pc_addr, breakpoint_addr); + + error = context.SetPC(breakpoint_addr); + if (error.Fail()) { + // This can happen in case the process was killed between the time we read + // the PC and when we are updating it. There's nothing better to do than to + // swallow the error. + LLDB_LOG(log, "pid {0} tid {1}: failed to set PC: {2}", GetID(), + thread.GetID(), error); + } +} + +Status NativeProcessProtocol::RemoveBreakpoint(lldb::addr_t addr, + bool hardware) { + if (hardware) + return RemoveHardwareBreakpoint(addr); + else + return RemoveSoftwareBreakpoint(addr); +} + +Status NativeProcessProtocol::ReadMemoryWithoutTrap(lldb::addr_t addr, + void *buf, size_t size, + size_t &bytes_read) { + Status error = ReadMemory(addr, buf, size, bytes_read); + if (error.Fail()) + return error; + + auto data = + llvm::makeMutableArrayRef(static_cast<uint8_t *>(buf), bytes_read); + for (const auto &pair : m_software_breakpoints) { + lldb::addr_t bp_addr = pair.first; + auto saved_opcodes = makeArrayRef(pair.second.saved_opcodes); + + if (bp_addr + saved_opcodes.size() < addr || addr + bytes_read <= bp_addr) + continue; // Breapoint not in range, ignore + + if (bp_addr < addr) { + saved_opcodes = saved_opcodes.drop_front(addr - bp_addr); + bp_addr = addr; + } + auto bp_data = data.drop_front(bp_addr - addr); + std::copy_n(saved_opcodes.begin(), + std::min(saved_opcodes.size(), bp_data.size()), + bp_data.begin()); + } + return Status(); +} + +llvm::Expected<llvm::StringRef> +NativeProcessProtocol::ReadCStringFromMemory(lldb::addr_t addr, char *buffer, + size_t max_size, + size_t &total_bytes_read) { + static const size_t cache_line_size = + llvm::sys::Process::getPageSizeEstimate(); + size_t bytes_read = 0; + size_t bytes_left = max_size; + addr_t curr_addr = addr; + size_t string_size; + char *curr_buffer = buffer; + total_bytes_read = 0; + Status status; + + while (bytes_left > 0 && status.Success()) { + addr_t cache_line_bytes_left = + cache_line_size - (curr_addr % cache_line_size); + addr_t bytes_to_read = std::min<addr_t>(bytes_left, cache_line_bytes_left); + status = ReadMemory(curr_addr, static_cast<void *>(curr_buffer), + bytes_to_read, bytes_read); + + if (bytes_read == 0) + break; + + void *str_end = std::memchr(curr_buffer, '\0', bytes_read); + if (str_end != nullptr) { + total_bytes_read = + static_cast<size_t>((static_cast<char *>(str_end) - buffer + 1)); + status.Clear(); + break; + } + + total_bytes_read += bytes_read; + curr_buffer += bytes_read; + curr_addr += bytes_read; + bytes_left -= bytes_read; + } + + string_size = total_bytes_read - 1; + + // Make sure we return a null terminated string. + if (bytes_left == 0 && max_size > 0 && buffer[max_size - 1] != '\0') { + buffer[max_size - 1] = '\0'; + total_bytes_read--; + } + + if (!status.Success()) + return status.ToError(); + + return llvm::StringRef(buffer, string_size); +} + +lldb::StateType NativeProcessProtocol::GetState() const { + std::lock_guard<std::recursive_mutex> guard(m_state_mutex); + return m_state; +} + +void NativeProcessProtocol::SetState(lldb::StateType state, + bool notify_delegates) { + std::lock_guard<std::recursive_mutex> guard(m_state_mutex); + + if (state == m_state) + return; + + m_state = state; + + if (StateIsStoppedState(state, false)) { + ++m_stop_id; + + // Give process a chance to do any stop id bump processing, such as + // clearing cached data that is invalidated each time the process runs. + // Note if/when we support some threads running, we'll end up needing to + // manage this per thread and per process. + DoStopIDBumped(m_stop_id); + } + + // Optionally notify delegates of the state change. + if (notify_delegates) + SynchronouslyNotifyProcessStateChanged(state); +} + +uint32_t NativeProcessProtocol::GetStopID() const { + std::lock_guard<std::recursive_mutex> guard(m_state_mutex); + return m_stop_id; +} + +void NativeProcessProtocol::DoStopIDBumped(uint32_t /* newBumpId */) { + // Default implementation does nothing. +} + +NativeProcessProtocol::Factory::~Factory() = default; diff --git a/gnu/llvm/lldb/source/Host/common/NativeRegisterContext.cpp b/gnu/llvm/lldb/source/Host/common/NativeRegisterContext.cpp new file mode 100644 index 00000000000..fe40073eb59 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/NativeRegisterContext.cpp @@ -0,0 +1,422 @@ +//===-- NativeRegisterContext.cpp -------------------------*- C++ -*-===// +// +// 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/common/NativeRegisterContext.h" + +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" + +#include "lldb/Host/PosixApi.h" +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/common/NativeThreadProtocol.h" + +using namespace lldb; +using namespace lldb_private; + +NativeRegisterContext::NativeRegisterContext(NativeThreadProtocol &thread) + : m_thread(thread) {} + +// Destructor +NativeRegisterContext::~NativeRegisterContext() {} + +// FIXME revisit invalidation, process stop ids, etc. Right now we don't +// support caching in NativeRegisterContext. We can do this later by utilizing +// NativeProcessProtocol::GetStopID () and adding a stop id to +// NativeRegisterContext. + +// void +// NativeRegisterContext::InvalidateIfNeeded (bool force) { +// ProcessSP process_sp (m_thread.GetProcess()); +// bool invalidate = force; +// uint32_t process_stop_id = UINT32_MAX; + +// if (process_sp) +// process_stop_id = process_sp->GetStopID(); +// else +// invalidate = true; + +// if (!invalidate) +// invalidate = process_stop_id != GetStopID(); + +// if (invalidate) +// { +// InvalidateAllRegisters (); +// SetStopID (process_stop_id); +// } +// } + +const RegisterInfo * +NativeRegisterContext::GetRegisterInfoByName(llvm::StringRef reg_name, + uint32_t start_idx) { + if (reg_name.empty()) + return nullptr; + + const uint32_t num_registers = GetRegisterCount(); + for (uint32_t reg = start_idx; reg < num_registers; ++reg) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + + if (reg_name.equals_lower(reg_info->name) || + reg_name.equals_lower(reg_info->alt_name)) + return reg_info; + } + return nullptr; +} + +const RegisterInfo *NativeRegisterContext::GetRegisterInfo(uint32_t kind, + uint32_t num) { + const uint32_t reg_num = ConvertRegisterKindToRegisterNumber(kind, num); + if (reg_num == LLDB_INVALID_REGNUM) + return nullptr; + return GetRegisterInfoAtIndex(reg_num); +} + +const char *NativeRegisterContext::GetRegisterName(uint32_t reg) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + if (reg_info) + return reg_info->name; + return nullptr; +} + +const char *NativeRegisterContext::GetRegisterSetNameForRegisterAtIndex( + uint32_t reg_index) const { + const RegisterInfo *const reg_info = GetRegisterInfoAtIndex(reg_index); + if (!reg_info) + return nullptr; + + for (uint32_t set_index = 0; set_index < GetRegisterSetCount(); ++set_index) { + const RegisterSet *const reg_set = GetRegisterSet(set_index); + if (!reg_set) + continue; + + for (uint32_t reg_num_index = 0; reg_num_index < reg_set->num_registers; + ++reg_num_index) { + const uint32_t reg_num = reg_set->registers[reg_num_index]; + // FIXME double check we're checking the right register kind here. + if (reg_info->kinds[RegisterKind::eRegisterKindLLDB] == reg_num) { + // The given register is a member of this register set. Return the + // register set name. + return reg_set->name; + } + } + } + + // Didn't find it. + return nullptr; +} + +lldb::addr_t NativeRegisterContext::GetPC(lldb::addr_t fail_value) { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + LLDB_LOGF(log, + "NativeRegisterContext::%s using reg index %" PRIu32 + " (default %" PRIu64 ")", + __FUNCTION__, reg, fail_value); + + const uint64_t retval = ReadRegisterAsUnsigned(reg, fail_value); + + LLDB_LOGF(log, "NativeRegisterContext::%s " PRIu32 " retval %" PRIu64, + __FUNCTION__, retval); + + return retval; +} + +lldb::addr_t +NativeRegisterContext::GetPCfromBreakpointLocation(lldb::addr_t fail_value) { + return GetPC(fail_value); +} + +Status NativeRegisterContext::SetPC(lldb::addr_t pc) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + return WriteRegisterFromUnsigned(reg, pc); +} + +lldb::addr_t NativeRegisterContext::GetSP(lldb::addr_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_SP); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +Status NativeRegisterContext::SetSP(lldb::addr_t sp) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_SP); + return WriteRegisterFromUnsigned(reg, sp); +} + +lldb::addr_t NativeRegisterContext::GetFP(lldb::addr_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FP); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +Status NativeRegisterContext::SetFP(lldb::addr_t fp) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FP); + return WriteRegisterFromUnsigned(reg, fp); +} + +lldb::addr_t NativeRegisterContext::GetReturnAddress(lldb::addr_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_RA); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +lldb::addr_t NativeRegisterContext::GetFlags(lldb::addr_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FLAGS); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +lldb::addr_t +NativeRegisterContext::ReadRegisterAsUnsigned(uint32_t reg, + lldb::addr_t fail_value) { + if (reg != LLDB_INVALID_REGNUM) + return ReadRegisterAsUnsigned(GetRegisterInfoAtIndex(reg), fail_value); + return fail_value; +} + +uint64_t +NativeRegisterContext::ReadRegisterAsUnsigned(const RegisterInfo *reg_info, + lldb::addr_t fail_value) { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + + if (reg_info) { + RegisterValue value; + Status error = ReadRegister(reg_info, value); + if (error.Success()) { + LLDB_LOGF(log, + "NativeRegisterContext::%s ReadRegister() succeeded, value " + "%" PRIu64, + __FUNCTION__, value.GetAsUInt64()); + return value.GetAsUInt64(); + } else { + LLDB_LOGF(log, + "NativeRegisterContext::%s ReadRegister() failed, error %s", + __FUNCTION__, error.AsCString()); + } + } else { + LLDB_LOGF(log, "NativeRegisterContext::%s ReadRegister() null reg_info", + __FUNCTION__); + } + return fail_value; +} + +Status NativeRegisterContext::WriteRegisterFromUnsigned(uint32_t reg, + uint64_t uval) { + if (reg == LLDB_INVALID_REGNUM) + return Status("NativeRegisterContext::%s (): reg is invalid", __FUNCTION__); + return WriteRegisterFromUnsigned(GetRegisterInfoAtIndex(reg), uval); +} + +Status +NativeRegisterContext::WriteRegisterFromUnsigned(const RegisterInfo *reg_info, + uint64_t uval) { + assert(reg_info); + if (!reg_info) + return Status("reg_info is nullptr"); + + RegisterValue value; + if (!value.SetUInt(uval, reg_info->byte_size)) + return Status("RegisterValue::SetUInt () failed"); + + return WriteRegister(reg_info, value); +} + +lldb::tid_t NativeRegisterContext::GetThreadID() const { + return m_thread.GetID(); +} + +uint32_t NativeRegisterContext::NumSupportedHardwareBreakpoints() { return 0; } + +uint32_t NativeRegisterContext::SetHardwareBreakpoint(lldb::addr_t addr, + size_t size) { + return LLDB_INVALID_INDEX32; +} + +Status NativeRegisterContext::ClearAllHardwareBreakpoints() { + return Status("not implemented"); +} + +bool NativeRegisterContext::ClearHardwareBreakpoint(uint32_t hw_idx) { + return false; +} + +Status NativeRegisterContext::GetHardwareBreakHitIndex(uint32_t &bp_index, + lldb::addr_t trap_addr) { + bp_index = LLDB_INVALID_INDEX32; + return Status("not implemented"); +} + +uint32_t NativeRegisterContext::NumSupportedHardwareWatchpoints() { return 0; } + +uint32_t NativeRegisterContext::SetHardwareWatchpoint(lldb::addr_t addr, + size_t size, + uint32_t watch_flags) { + return LLDB_INVALID_INDEX32; +} + +bool NativeRegisterContext::ClearHardwareWatchpoint(uint32_t hw_index) { + return false; +} + +Status NativeRegisterContext::ClearAllHardwareWatchpoints() { + return Status("not implemented"); +} + +Status NativeRegisterContext::IsWatchpointHit(uint32_t wp_index, bool &is_hit) { + is_hit = false; + return Status("not implemented"); +} + +Status NativeRegisterContext::GetWatchpointHitIndex(uint32_t &wp_index, + lldb::addr_t trap_addr) { + wp_index = LLDB_INVALID_INDEX32; + return Status("not implemented"); +} + +Status NativeRegisterContext::IsWatchpointVacant(uint32_t wp_index, + bool &is_vacant) { + is_vacant = false; + return Status("not implemented"); +} + +lldb::addr_t NativeRegisterContext::GetWatchpointAddress(uint32_t wp_index) { + return LLDB_INVALID_ADDRESS; +} + +lldb::addr_t NativeRegisterContext::GetWatchpointHitAddress(uint32_t wp_index) { + return LLDB_INVALID_ADDRESS; +} + +bool NativeRegisterContext::HardwareSingleStep(bool enable) { return false; } + +Status NativeRegisterContext::ReadRegisterValueFromMemory( + const RegisterInfo *reg_info, lldb::addr_t src_addr, size_t src_len, + RegisterValue ®_value) { + Status error; + if (reg_info == nullptr) { + error.SetErrorString("invalid register info argument."); + return error; + } + + // Moving from addr into a register + // + // Case 1: src_len == dst_len + // + // |AABBCCDD| Address contents + // |AABBCCDD| Register contents + // + // Case 2: src_len > dst_len + // + // Status! (The register should always be big enough to hold the data) + // + // Case 3: src_len < dst_len + // + // |AABB| Address contents + // |AABB0000| Register contents [on little-endian hardware] + // |0000AABB| Register contents [on big-endian hardware] + if (src_len > RegisterValue::kMaxRegisterByteSize) { + error.SetErrorString("register too small to receive memory data"); + return error; + } + + const size_t dst_len = reg_info->byte_size; + + if (src_len > dst_len) { + error.SetErrorStringWithFormat( + "%" PRIu64 " bytes is too big to store in register %s (%" PRIu64 + " bytes)", + static_cast<uint64_t>(src_len), reg_info->name, + static_cast<uint64_t>(dst_len)); + return error; + } + + NativeProcessProtocol &process = m_thread.GetProcess(); + uint8_t src[RegisterValue::kMaxRegisterByteSize]; + + // Read the memory + size_t bytes_read; + error = process.ReadMemory(src_addr, src, src_len, bytes_read); + if (error.Fail()) + return error; + + // Make sure the memory read succeeded... + if (bytes_read != src_len) { + // This might happen if we read _some_ bytes but not all + error.SetErrorStringWithFormat("read %" PRIu64 " of %" PRIu64 " bytes", + static_cast<uint64_t>(bytes_read), + static_cast<uint64_t>(src_len)); + return error; + } + + // We now have a memory buffer that contains the part or all of the register + // value. Set the register value using this memory data. + // TODO: we might need to add a parameter to this function in case the byte + // order of the memory data doesn't match the process. For now we are + // assuming they are the same. + reg_value.SetFromMemoryData(reg_info, src, src_len, process.GetByteOrder(), + error); + + return error; +} + +Status NativeRegisterContext::WriteRegisterValueToMemory( + const RegisterInfo *reg_info, lldb::addr_t dst_addr, size_t dst_len, + const RegisterValue ®_value) { + + uint8_t dst[RegisterValue::kMaxRegisterByteSize]; + + Status error; + + NativeProcessProtocol &process = m_thread.GetProcess(); + + // TODO: we might need to add a parameter to this function in case the byte + // order of the memory data doesn't match the process. For now we are + // assuming they are the same. + const size_t bytes_copied = reg_value.GetAsMemoryData( + reg_info, dst, dst_len, process.GetByteOrder(), error); + + if (error.Success()) { + if (bytes_copied == 0) { + error.SetErrorString("byte copy failed."); + } else { + size_t bytes_written; + error = process.WriteMemory(dst_addr, dst, bytes_copied, bytes_written); + if (error.Fail()) + return error; + + if (bytes_written != bytes_copied) { + // This might happen if we read _some_ bytes but not all + error.SetErrorStringWithFormat("only wrote %" PRIu64 " of %" PRIu64 + " bytes", + static_cast<uint64_t>(bytes_written), + static_cast<uint64_t>(bytes_copied)); + } + } + } + + return error; +} + +uint32_t +NativeRegisterContext::ConvertRegisterKindToRegisterNumber(uint32_t kind, + uint32_t num) const { + const uint32_t num_regs = GetRegisterCount(); + + assert(kind < kNumRegisterKinds); + for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg_idx); + + if (reg_info->kinds[kind] == num) + return reg_idx; + } + + return LLDB_INVALID_REGNUM; +} diff --git a/gnu/llvm/lldb/source/Host/common/NativeThreadProtocol.cpp b/gnu/llvm/lldb/source/Host/common/NativeThreadProtocol.cpp new file mode 100644 index 00000000000..e62b1425c89 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/NativeThreadProtocol.cpp @@ -0,0 +1,19 @@ +//===-- NativeThreadProtocol.cpp --------------------------------*- C++ -*-===// +// +// 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/common/NativeThreadProtocol.h" + +#include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/common/NativeRegisterContext.h" + +using namespace lldb; +using namespace lldb_private; + +NativeThreadProtocol::NativeThreadProtocol(NativeProcessProtocol &process, + lldb::tid_t tid) + : m_process(process), m_tid(tid) {} diff --git a/gnu/llvm/lldb/source/Host/common/NativeWatchpointList.cpp b/gnu/llvm/lldb/source/Host/common/NativeWatchpointList.cpp new file mode 100644 index 00000000000..c3db95fb252 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/NativeWatchpointList.cpp @@ -0,0 +1,30 @@ +//===-- NativeWatchpointList.cpp --------------------------------*- C++ -*-===// +// +// 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/common/NativeWatchpointList.h" + +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +Status NativeWatchpointList::Add(addr_t addr, size_t size, uint32_t watch_flags, + bool hardware) { + m_watchpoints[addr] = {addr, size, watch_flags, hardware}; + return Status(); +} + +Status NativeWatchpointList::Remove(addr_t addr) { + m_watchpoints.erase(addr); + return Status(); +} + +const NativeWatchpointList::WatchpointMap & +NativeWatchpointList::GetWatchpointMap() const { + return m_watchpoints; +} diff --git a/gnu/llvm/lldb/source/Host/common/OptionParser.cpp b/gnu/llvm/lldb/source/Host/common/OptionParser.cpp new file mode 100644 index 00000000000..1e76f9b8f9f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/OptionParser.cpp @@ -0,0 +1,83 @@ +//===-- source/Host/common/OptionParser.cpp ---------------------*- C++ -*-===// +// +// 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/OptionParser.h" +#include "lldb/Host/HostGetOpt.h" +#include "lldb/lldb-private-types.h" + +#include <vector> + +using namespace lldb_private; + +void OptionParser::Prepare(std::unique_lock<std::mutex> &lock) { + static std::mutex g_mutex; + lock = std::unique_lock<std::mutex>(g_mutex); +#ifdef __GLIBC__ + optind = 0; +#else + optreset = 1; + optind = 1; +#endif +} + +void OptionParser::EnableError(bool error) { opterr = error ? 1 : 0; } + +int OptionParser::Parse(llvm::MutableArrayRef<char *> argv, + llvm::StringRef optstring, const Option *longopts, + int *longindex) { + std::vector<option> opts; + while (longopts->definition != nullptr) { + option opt; + opt.flag = longopts->flag; + opt.val = longopts->val; + opt.name = longopts->definition->long_option; + opt.has_arg = longopts->definition->option_has_arg; + opts.push_back(opt); + ++longopts; + } + opts.push_back(option()); + std::string opt_cstr = optstring; + return getopt_long_only(argv.size() - 1, argv.data(), opt_cstr.c_str(), + &opts[0], longindex); +} + +char *OptionParser::GetOptionArgument() { return optarg; } + +int OptionParser::GetOptionIndex() { return optind; } + +int OptionParser::GetOptionErrorCause() { return optopt; } + +std::string OptionParser::GetShortOptionString(struct option *long_options) { + std::string s; + int i = 0; + bool done = false; + while (!done) { + if (long_options[i].name == nullptr && long_options[i].has_arg == 0 && + long_options[i].flag == nullptr && long_options[i].val == 0) { + done = true; + } else { + if (long_options[i].flag == nullptr && isalpha(long_options[i].val)) { + s.append(1, (char)long_options[i].val); + switch (long_options[i].has_arg) { + default: + case no_argument: + break; + + case optional_argument: + s.append(2, ':'); + break; + case required_argument: + s.append(1, ':'); + break; + } + } + ++i; + } + } + return s; +} diff --git a/gnu/llvm/lldb/source/Host/common/PipeBase.cpp b/gnu/llvm/lldb/source/Host/common/PipeBase.cpp new file mode 100644 index 00000000000..2cbadf0c85f --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/PipeBase.cpp @@ -0,0 +1,24 @@ +//===-- source/Host/common/PipeBase.cpp -------------------------*- C++ -*-===// +// +// 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/PipeBase.h" + +using namespace lldb_private; + +PipeBase::~PipeBase() = default; + +Status PipeBase::OpenAsWriter(llvm::StringRef name, + bool child_process_inherit) { + return OpenAsWriterWithTimeout(name, child_process_inherit, + std::chrono::microseconds::zero()); +} + +Status PipeBase::Read(void *buf, size_t size, size_t &bytes_read) { + return ReadWithTimeout(buf, size, std::chrono::microseconds::zero(), + bytes_read); +} diff --git a/gnu/llvm/lldb/source/Host/common/ProcessLaunchInfo.cpp b/gnu/llvm/lldb/source/Host/common/ProcessLaunchInfo.cpp new file mode 100644 index 00000000000..266b4676399 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -0,0 +1,350 @@ +//===-- ProcessLaunchInfo.cpp -----------------------------------*- C++ -*-===// +// +// 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 <climits> + +#include "lldb/Host/Config.h" +#include "lldb/Host/FileAction.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" + +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/FileSystem.h" + +#if !defined(_WIN32) +#include <limits.h> +#endif + +using namespace lldb; +using namespace lldb_private; + +// ProcessLaunchInfo member functions + +ProcessLaunchInfo::ProcessLaunchInfo() + : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(0), + m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0), + m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr), + m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() {} + +ProcessLaunchInfo::ProcessLaunchInfo(const FileSpec &stdin_file_spec, + const FileSpec &stdout_file_spec, + const FileSpec &stderr_file_spec, + const FileSpec &working_directory, + uint32_t launch_flags) + : ProcessInfo(), m_working_dir(), m_plugin_name(), m_flags(launch_flags), + m_file_actions(), m_pty(new PseudoTerminal), m_resume_count(0), + m_monitor_callback(nullptr), m_monitor_callback_baton(nullptr), + m_monitor_signals(false), m_listener_sp(), m_hijack_listener_sp() { + if (stdin_file_spec) { + FileAction file_action; + const bool read = true; + const bool write = false; + if (file_action.Open(STDIN_FILENO, stdin_file_spec, read, write)) + AppendFileAction(file_action); + } + if (stdout_file_spec) { + FileAction file_action; + const bool read = false; + const bool write = true; + if (file_action.Open(STDOUT_FILENO, stdout_file_spec, read, write)) + AppendFileAction(file_action); + } + if (stderr_file_spec) { + FileAction file_action; + const bool read = false; + const bool write = true; + if (file_action.Open(STDERR_FILENO, stderr_file_spec, read, write)) + AppendFileAction(file_action); + } + if (working_directory) + SetWorkingDirectory(working_directory); +} + +bool ProcessLaunchInfo::AppendCloseFileAction(int fd) { + FileAction file_action; + if (file_action.Close(fd)) { + AppendFileAction(file_action); + return true; + } + return false; +} + +bool ProcessLaunchInfo::AppendDuplicateFileAction(int fd, int dup_fd) { + FileAction file_action; + if (file_action.Duplicate(fd, dup_fd)) { + AppendFileAction(file_action); + return true; + } + return false; +} + +bool ProcessLaunchInfo::AppendOpenFileAction(int fd, const FileSpec &file_spec, + bool read, bool write) { + FileAction file_action; + if (file_action.Open(fd, file_spec, read, write)) { + AppendFileAction(file_action); + return true; + } + return false; +} + +bool ProcessLaunchInfo::AppendSuppressFileAction(int fd, bool read, + bool write) { + FileAction file_action; + if (file_action.Open(fd, FileSpec(FileSystem::DEV_NULL), read, write)) { + AppendFileAction(file_action); + return true; + } + return false; +} + +const FileAction *ProcessLaunchInfo::GetFileActionAtIndex(size_t idx) const { + if (idx < m_file_actions.size()) + return &m_file_actions[idx]; + return nullptr; +} + +const FileAction *ProcessLaunchInfo::GetFileActionForFD(int fd) const { + for (size_t idx = 0, count = m_file_actions.size(); idx < count; ++idx) { + if (m_file_actions[idx].GetFD() == fd) + return &m_file_actions[idx]; + } + return nullptr; +} + +const FileSpec &ProcessLaunchInfo::GetWorkingDirectory() const { + return m_working_dir; +} + +void ProcessLaunchInfo::SetWorkingDirectory(const FileSpec &working_dir) { + m_working_dir = working_dir; +} + +const char *ProcessLaunchInfo::GetProcessPluginName() const { + return (m_plugin_name.empty() ? nullptr : m_plugin_name.c_str()); +} + +void ProcessLaunchInfo::SetProcessPluginName(llvm::StringRef plugin) { + m_plugin_name = plugin; +} + +const FileSpec &ProcessLaunchInfo::GetShell() const { return m_shell; } + +void ProcessLaunchInfo::SetShell(const FileSpec &shell) { + m_shell = shell; + if (m_shell) { + FileSystem::Instance().ResolveExecutableLocation(m_shell); + m_flags.Set(lldb::eLaunchFlagLaunchInShell); + } else + m_flags.Clear(lldb::eLaunchFlagLaunchInShell); +} + +void ProcessLaunchInfo::SetLaunchInSeparateProcessGroup(bool separate) { + if (separate) + m_flags.Set(lldb::eLaunchFlagLaunchInSeparateProcessGroup); + else + m_flags.Clear(lldb::eLaunchFlagLaunchInSeparateProcessGroup); +} + +void ProcessLaunchInfo::SetShellExpandArguments(bool expand) { + if (expand) + m_flags.Set(lldb::eLaunchFlagShellExpandArguments); + else + m_flags.Clear(lldb::eLaunchFlagShellExpandArguments); +} + +void ProcessLaunchInfo::Clear() { + ProcessInfo::Clear(); + m_working_dir.Clear(); + m_plugin_name.clear(); + m_shell.Clear(); + m_flags.Clear(); + m_file_actions.clear(); + m_resume_count = 0; + m_listener_sp.reset(); + m_hijack_listener_sp.reset(); +} + +void ProcessLaunchInfo::SetMonitorProcessCallback( + const Host::MonitorChildProcessCallback &callback, bool monitor_signals) { + m_monitor_callback = callback; + m_monitor_signals = monitor_signals; +} + +bool ProcessLaunchInfo::NoOpMonitorCallback(lldb::pid_t pid, bool exited, int signal, int status) { + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); + LLDB_LOG(log, "pid = {0}, exited = {1}, signal = {2}, status = {3}", pid, + exited, signal, status); + return true; +} + +bool ProcessLaunchInfo::MonitorProcess() const { + if (m_monitor_callback && ProcessIDIsValid()) { + llvm::Expected<HostThread> maybe_thread = + Host::StartMonitoringChildProcess(m_monitor_callback, GetProcessID(), + m_monitor_signals); + if (!maybe_thread) + LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), + "failed to launch host thread: {}", + llvm::toString(maybe_thread.takeError())); + return true; + } + return false; +} + +void ProcessLaunchInfo::SetDetachOnError(bool enable) { + if (enable) + m_flags.Set(lldb::eLaunchFlagDetachOnError); + else + m_flags.Clear(lldb::eLaunchFlagDetachOnError); +} + +llvm::Error ProcessLaunchInfo::SetUpPtyRedirection() { + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); + LLDB_LOG(log, "Generating a pty to use for stdin/out/err"); + + int open_flags = O_RDWR | O_NOCTTY; +#if !defined(_WIN32) + // We really shouldn't be specifying platform specific flags that are + // intended for a system call in generic code. But this will have to + // do for now. + open_flags |= O_CLOEXEC; +#endif + if (!m_pty->OpenFirstAvailableMaster(open_flags, nullptr, 0)) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "PTY::OpenFirstAvailableMaster failed"); + } + const FileSpec slave_file_spec(m_pty->GetSlaveName(nullptr, 0)); + + // Only use the slave tty if we don't have anything specified for + // input and don't have an action for stdin + if (GetFileActionForFD(STDIN_FILENO) == nullptr) + AppendOpenFileAction(STDIN_FILENO, slave_file_spec, true, false); + + // Only use the slave tty if we don't have anything specified for + // output and don't have an action for stdout + if (GetFileActionForFD(STDOUT_FILENO) == nullptr) + AppendOpenFileAction(STDOUT_FILENO, slave_file_spec, false, true); + + // Only use the slave tty if we don't have anything specified for + // error and don't have an action for stderr + if (GetFileActionForFD(STDERR_FILENO) == nullptr) + AppendOpenFileAction(STDERR_FILENO, slave_file_spec, false, true); + return llvm::Error::success(); +} + +bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell( + Status &error, bool localhost, bool will_debug, + bool first_arg_is_full_shell_command, int32_t num_resumes) { + error.Clear(); + + if (GetFlags().Test(eLaunchFlagLaunchInShell)) { + if (m_shell) { + std::string shell_executable = m_shell.GetPath(); + + const char **argv = GetArguments().GetConstArgumentVector(); + if (argv == nullptr || argv[0] == nullptr) + return false; + Args shell_arguments; + std::string safe_arg; + shell_arguments.AppendArgument(shell_executable); + const llvm::Triple &triple = GetArchitecture().GetTriple(); + if (triple.getOS() == llvm::Triple::Win32 && + !triple.isWindowsCygwinEnvironment()) + shell_arguments.AppendArgument(llvm::StringRef("/C")); + else + shell_arguments.AppendArgument(llvm::StringRef("-c")); + + StreamString shell_command; + if (will_debug) { + // Add a modified PATH environment variable in case argv[0] is a + // relative path. + const char *argv0 = argv[0]; + FileSpec arg_spec(argv0); + if (arg_spec.IsRelative()) { + // We have a relative path to our executable which may not work if we + // just try to run "a.out" (without it being converted to "./a.out") + FileSpec working_dir = GetWorkingDirectory(); + // Be sure to put quotes around PATH's value in case any paths have + // spaces... + std::string new_path("PATH=\""); + const size_t empty_path_len = new_path.size(); + + if (working_dir) { + new_path += working_dir.GetPath(); + } else { + llvm::SmallString<64> cwd; + if (! llvm::sys::fs::current_path(cwd)) + new_path += cwd; + } + std::string curr_path; + if (HostInfo::GetEnvironmentVar("PATH", curr_path)) { + if (new_path.size() > empty_path_len) + new_path += ':'; + new_path += curr_path; + } + new_path += "\" "; + shell_command.PutCString(new_path); + } + + if (triple.getOS() != llvm::Triple::Win32 || + triple.isWindowsCygwinEnvironment()) + shell_command.PutCString("exec"); + + // Only Apple supports /usr/bin/arch being able to specify the + // architecture + if (GetArchitecture().IsValid() && // Valid architecture + GetArchitecture().GetTriple().getVendor() == + llvm::Triple::Apple && // Apple only + GetArchitecture().GetCore() != + ArchSpec::eCore_x86_64_x86_64h) // Don't do this for x86_64h + { + shell_command.Printf(" /usr/bin/arch -arch %s", + GetArchitecture().GetArchitectureName()); + // Set the resume count to 2: + // 1 - stop in shell + // 2 - stop in /usr/bin/arch + // 3 - then we will stop in our program + SetResumeCount(num_resumes + 1); + } else { + // Set the resume count to 1: + // 1 - stop in shell + // 2 - then we will stop in our program + SetResumeCount(num_resumes); + } + } + + if (first_arg_is_full_shell_command) { + // There should only be one argument that is the shell command itself + // to be used as is + if (argv[0] && !argv[1]) + shell_command.Printf("%s", argv[0]); + else + return false; + } else { + for (size_t i = 0; argv[i] != nullptr; ++i) { + const char *arg = + Args::GetShellSafeArgument(m_shell, argv[i], safe_arg); + shell_command.Printf(" %s", arg); + } + } + shell_arguments.AppendArgument(shell_command.GetString()); + m_executable = m_shell; + m_arguments = shell_arguments; + return true; + } else { + error.SetErrorString("invalid shell path"); + } + } else { + error.SetErrorString("not launching in shell"); + } + return false; +} diff --git a/gnu/llvm/lldb/source/Host/common/ProcessRunLock.cpp b/gnu/llvm/lldb/source/Host/common/ProcessRunLock.cpp new file mode 100644 index 00000000000..a931da71876 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/ProcessRunLock.cpp @@ -0,0 +1,64 @@ +//===-- ProcessRunLock.cpp --------------------------------------*- C++ -*-===// +// +// 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 _WIN32 +#include "lldb/Host/ProcessRunLock.h" + +namespace lldb_private { + +ProcessRunLock::ProcessRunLock() : m_running(false) { + int err = ::pthread_rwlock_init(&m_rwlock, nullptr); + (void)err; +} + +ProcessRunLock::~ProcessRunLock() { + int err = ::pthread_rwlock_destroy(&m_rwlock); + (void)err; +} + +bool ProcessRunLock::ReadTryLock() { + ::pthread_rwlock_rdlock(&m_rwlock); + if (!m_running) { + return true; + } + ::pthread_rwlock_unlock(&m_rwlock); + return false; +} + +bool ProcessRunLock::ReadUnlock() { + return ::pthread_rwlock_unlock(&m_rwlock) == 0; +} + +bool ProcessRunLock::SetRunning() { + ::pthread_rwlock_wrlock(&m_rwlock); + m_running = true; + ::pthread_rwlock_unlock(&m_rwlock); + return true; +} + +bool ProcessRunLock::TrySetRunning() { + bool r; + + if (::pthread_rwlock_trywrlock(&m_rwlock) == 0) { + r = !m_running; + m_running = true; + ::pthread_rwlock_unlock(&m_rwlock); + return r; + } + return false; +} + +bool ProcessRunLock::SetStopped() { + ::pthread_rwlock_wrlock(&m_rwlock); + m_running = false; + ::pthread_rwlock_unlock(&m_rwlock); + return true; +} +} + +#endif diff --git a/gnu/llvm/lldb/source/Host/common/PseudoTerminal.cpp b/gnu/llvm/lldb/source/Host/common/PseudoTerminal.cpp new file mode 100644 index 00000000000..85828283e21 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/PseudoTerminal.cpp @@ -0,0 +1,287 @@ +//===-- PseudoTerminal.cpp --------------------------------------*- C++ -*-===// +// +// 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/PseudoTerminal.h" +#include "lldb/Host/Config.h" + +#include "llvm/Support/Errno.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(TIOCSCTTY) +#include <sys/ioctl.h> +#endif + +#include "lldb/Host/PosixApi.h" + +#if defined(__ANDROID__) +int posix_openpt(int flags); +#endif + +using namespace lldb_private; + +// Write string describing error number +static void ErrnoToStr(char *error_str, size_t error_len) { + std::string strerror = llvm::sys::StrError(); + ::snprintf(error_str, error_len, "%s", strerror.c_str()); +} + +// PseudoTerminal constructor +PseudoTerminal::PseudoTerminal() + : m_master_fd(invalid_fd), m_slave_fd(invalid_fd) {} + +// Destructor +// +// The destructor will close the master and slave file descriptors if they are +// valid and ownership has not been released using the +// ReleaseMasterFileDescriptor() or the ReleaseSaveFileDescriptor() member +// functions. +PseudoTerminal::~PseudoTerminal() { + CloseMasterFileDescriptor(); + CloseSlaveFileDescriptor(); +} + +// Close the master file descriptor if it is valid. +void PseudoTerminal::CloseMasterFileDescriptor() { + if (m_master_fd >= 0) { + ::close(m_master_fd); + m_master_fd = invalid_fd; + } +} + +// Close the slave file descriptor if it is valid. +void PseudoTerminal::CloseSlaveFileDescriptor() { + if (m_slave_fd >= 0) { + ::close(m_slave_fd); + m_slave_fd = invalid_fd; + } +} + +// Open the first available pseudo terminal with OFLAG as the permissions. The +// file descriptor is stored in this object and can be accessed with the +// MasterFileDescriptor() accessor. The ownership of the master file descriptor +// can be released using the ReleaseMasterFileDescriptor() accessor. If this +// object has a valid master files descriptor when its destructor is called, it +// will close the master file descriptor, therefore clients must call +// ReleaseMasterFileDescriptor() if they wish to use the master file descriptor +// after this object is out of scope or destroyed. +// +// RETURNS: +// True when successful, false indicating an error occurred. +bool PseudoTerminal::OpenFirstAvailableMaster(int oflag, char *error_str, + size_t error_len) { + if (error_str) + error_str[0] = '\0'; + +#if LLDB_ENABLE_POSIX + // Open the master side of a pseudo terminal + m_master_fd = ::posix_openpt(oflag); + if (m_master_fd < 0) { + if (error_str) + ErrnoToStr(error_str, error_len); + return false; + } + + // Grant access to the slave pseudo terminal + if (::grantpt(m_master_fd) < 0) { + if (error_str) + ErrnoToStr(error_str, error_len); + CloseMasterFileDescriptor(); + return false; + } + + // Clear the lock flag on the slave pseudo terminal + if (::unlockpt(m_master_fd) < 0) { + if (error_str) + ErrnoToStr(error_str, error_len); + CloseMasterFileDescriptor(); + return false; + } + + return true; +#else + if (error_str) + ::snprintf(error_str, error_len, "%s", "pseudo terminal not supported"); + return false; +#endif +} + +// Open the slave pseudo terminal for the current master pseudo terminal. A +// master pseudo terminal should already be valid prior to calling this +// function (see OpenFirstAvailableMaster()). The file descriptor is stored +// this object's member variables and can be accessed via the +// GetSlaveFileDescriptor(), or released using the ReleaseSlaveFileDescriptor() +// member function. +// +// RETURNS: +// True when successful, false indicating an error occurred. +bool PseudoTerminal::OpenSlave(int oflag, char *error_str, size_t error_len) { + if (error_str) + error_str[0] = '\0'; + + CloseSlaveFileDescriptor(); + + // Open the master side of a pseudo terminal + const char *slave_name = GetSlaveName(error_str, error_len); + + if (slave_name == nullptr) + return false; + + m_slave_fd = llvm::sys::RetryAfterSignal(-1, ::open, slave_name, oflag); + + if (m_slave_fd < 0) { + if (error_str) + ErrnoToStr(error_str, error_len); + return false; + } + + return true; +} + +// Get the name of the slave pseudo terminal. A master pseudo terminal should +// already be valid prior to calling this function (see +// OpenFirstAvailableMaster()). +// +// RETURNS: +// NULL if no valid master pseudo terminal or if ptsname() fails. +// The name of the slave pseudo terminal as a NULL terminated C string +// that comes from static memory, so a copy of the string should be +// made as subsequent calls can change this value. +const char *PseudoTerminal::GetSlaveName(char *error_str, + size_t error_len) const { + if (error_str) + error_str[0] = '\0'; + + if (m_master_fd < 0) { + if (error_str) + ::snprintf(error_str, error_len, "%s", + "master file descriptor is invalid"); + return nullptr; + } + const char *slave_name = ::ptsname(m_master_fd); + + if (error_str && slave_name == nullptr) + ErrnoToStr(error_str, error_len); + + return slave_name; +} + +// Fork a child process and have its stdio routed to a pseudo terminal. +// +// In the parent process when a valid pid is returned, the master file +// descriptor can be used as a read/write access to stdio of the child process. +// +// In the child process the stdin/stdout/stderr will already be routed to the +// slave pseudo terminal and the master file descriptor will be closed as it is +// no longer needed by the child process. +// +// This class will close the file descriptors for the master/slave when the +// destructor is called, so be sure to call ReleaseMasterFileDescriptor() or +// ReleaseSlaveFileDescriptor() if any file descriptors are going to be used +// past the lifespan of this object. +// +// RETURNS: +// in the parent process: the pid of the child, or -1 if fork fails +// in the child process: zero +lldb::pid_t PseudoTerminal::Fork(char *error_str, size_t error_len) { + if (error_str) + error_str[0] = '\0'; + pid_t pid = LLDB_INVALID_PROCESS_ID; +#if LLDB_ENABLE_POSIX + int flags = O_RDWR; + flags |= O_CLOEXEC; + if (OpenFirstAvailableMaster(flags, error_str, error_len)) { + // Successfully opened our master pseudo terminal + + pid = ::fork(); + if (pid < 0) { + // Fork failed + if (error_str) + ErrnoToStr(error_str, error_len); + } else if (pid == 0) { + // Child Process + ::setsid(); + + if (OpenSlave(O_RDWR, error_str, error_len)) { + // Successfully opened slave + + // Master FD should have O_CLOEXEC set, but let's close it just in + // case... + CloseMasterFileDescriptor(); + +#if defined(TIOCSCTTY) + // Acquire the controlling terminal + if (::ioctl(m_slave_fd, TIOCSCTTY, (char *)0) < 0) { + if (error_str) + ErrnoToStr(error_str, error_len); + } +#endif + // Duplicate all stdio file descriptors to the slave pseudo terminal + if (::dup2(m_slave_fd, STDIN_FILENO) != STDIN_FILENO) { + if (error_str && !error_str[0]) + ErrnoToStr(error_str, error_len); + } + + if (::dup2(m_slave_fd, STDOUT_FILENO) != STDOUT_FILENO) { + if (error_str && !error_str[0]) + ErrnoToStr(error_str, error_len); + } + + if (::dup2(m_slave_fd, STDERR_FILENO) != STDERR_FILENO) { + if (error_str && !error_str[0]) + ErrnoToStr(error_str, error_len); + } + } + } else { + // Parent Process + // Do nothing and let the pid get returned! + } + } +#endif + return pid; +} + +// The master file descriptor accessor. This object retains ownership of the +// master file descriptor when this accessor is used. Use +// ReleaseMasterFileDescriptor() if you wish this object to release ownership +// of the master file descriptor. +// +// Returns the master file descriptor, or -1 if the master file descriptor is +// not currently valid. +int PseudoTerminal::GetMasterFileDescriptor() const { return m_master_fd; } + +// The slave file descriptor accessor. +// +// Returns the slave file descriptor, or -1 if the slave file descriptor is not +// currently valid. +int PseudoTerminal::GetSlaveFileDescriptor() const { return m_slave_fd; } + +// Release ownership of the master pseudo terminal file descriptor without +// closing it. The destructor for this class will close the master file +// descriptor if the ownership isn't released using this call and the master +// file descriptor has been opened. +int PseudoTerminal::ReleaseMasterFileDescriptor() { + // Release ownership of the master pseudo terminal file descriptor without + // closing it. (the destructor for this class will close it otherwise!) + int fd = m_master_fd; + m_master_fd = invalid_fd; + return fd; +} + +// Release ownership of the slave pseudo terminal file descriptor without +// closing it. The destructor for this class will close the slave file +// descriptor if the ownership isn't released using this call and the slave +// file descriptor has been opened. +int PseudoTerminal::ReleaseSlaveFileDescriptor() { + // Release ownership of the slave pseudo terminal file descriptor without + // closing it (the destructor for this class will close it otherwise!) + int fd = m_slave_fd; + m_slave_fd = invalid_fd; + return fd; +} diff --git a/gnu/llvm/lldb/source/Host/common/Socket.cpp b/gnu/llvm/lldb/source/Host/common/Socket.cpp new file mode 100644 index 00000000000..7fba1daff75 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/Socket.cpp @@ -0,0 +1,489 @@ +//===-- Socket.cpp ----------------------------------------------*- C++ -*-===// +// +// 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/Socket.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/SocketAddress.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Host/common/TCPSocket.h" +#include "lldb/Host/common/UDPSocket.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/WindowsError.h" + +#if LLDB_ENABLE_POSIX +#include "lldb/Host/posix/DomainSocket.h" + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#endif + +#ifdef __linux__ +#include "lldb/Host/linux/AbstractSocket.h" +#endif + +#ifdef __ANDROID__ +#include <arpa/inet.h> +#include <asm-generic/errno-base.h> +#include <errno.h> +#include <linux/tcp.h> +#include <fcntl.h> +#include <sys/syscall.h> +#include <unistd.h> +#endif // __ANDROID__ + +using namespace lldb; +using namespace lldb_private; + +#if defined(_WIN32) +typedef const char *set_socket_option_arg_type; +typedef char *get_socket_option_arg_type; +const NativeSocket Socket::kInvalidSocketValue = INVALID_SOCKET; +#else // #if defined(_WIN32) +typedef const void *set_socket_option_arg_type; +typedef void *get_socket_option_arg_type; +const NativeSocket Socket::kInvalidSocketValue = -1; +#endif // #if defined(_WIN32) + +namespace { + +bool IsInterrupted() { +#if defined(_WIN32) + return ::WSAGetLastError() == WSAEINTR; +#else + return errno == EINTR; +#endif +} +} + +Socket::Socket(SocketProtocol protocol, bool should_close, + bool child_processes_inherit) + : IOObject(eFDTypeSocket), m_protocol(protocol), + m_socket(kInvalidSocketValue), + m_child_processes_inherit(child_processes_inherit), + m_should_close_fd(should_close) {} + +Socket::~Socket() { Close(); } + +llvm::Error Socket::Initialize() { +#if defined(_WIN32) + auto wVersion = WINSOCK_VERSION; + WSADATA wsaData; + int err = ::WSAStartup(wVersion, &wsaData); + if (err == 0) { + if (wsaData.wVersion < wVersion) { + WSACleanup(); + return llvm::make_error<llvm::StringError>( + "WSASock version is not expected.", llvm::inconvertibleErrorCode()); + } + } else { + return llvm::errorCodeToError(llvm::mapWindowsError(::WSAGetLastError())); + } +#endif + + return llvm::Error::success(); +} + +void Socket::Terminate() { +#if defined(_WIN32) + ::WSACleanup(); +#endif +} + +std::unique_ptr<Socket> Socket::Create(const SocketProtocol protocol, + bool child_processes_inherit, + Status &error) { + error.Clear(); + + std::unique_ptr<Socket> socket_up; + switch (protocol) { + case ProtocolTcp: + socket_up = + std::make_unique<TCPSocket>(true, child_processes_inherit); + break; + case ProtocolUdp: + socket_up = + std::make_unique<UDPSocket>(true, child_processes_inherit); + break; + case ProtocolUnixDomain: +#if LLDB_ENABLE_POSIX + socket_up = + std::make_unique<DomainSocket>(true, child_processes_inherit); +#else + error.SetErrorString( + "Unix domain sockets are not supported on this platform."); +#endif + break; + case ProtocolUnixAbstract: +#ifdef __linux__ + socket_up = + std::make_unique<AbstractSocket>(child_processes_inherit); +#else + error.SetErrorString( + "Abstract domain sockets are not supported on this platform."); +#endif + break; + } + + if (error.Fail()) + socket_up.reset(); + + return socket_up; +} + +Status Socket::TcpConnect(llvm::StringRef host_and_port, + bool child_processes_inherit, Socket *&socket) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_COMMUNICATION)); + LLDB_LOGF(log, "Socket::%s (host/port = %s)", __FUNCTION__, + host_and_port.str().c_str()); + + Status error; + std::unique_ptr<Socket> connect_socket( + Create(ProtocolTcp, child_processes_inherit, error)); + if (error.Fail()) + return error; + + error = connect_socket->Connect(host_and_port); + if (error.Success()) + socket = connect_socket.release(); + + return error; +} + +Status Socket::TcpListen(llvm::StringRef host_and_port, + bool child_processes_inherit, Socket *&socket, + Predicate<uint16_t> *predicate, int backlog) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + LLDB_LOGF(log, "Socket::%s (%s)", __FUNCTION__, host_and_port.str().c_str()); + + Status error; + std::string host_str; + std::string port_str; + int32_t port = INT32_MIN; + if (!DecodeHostAndPort(host_and_port, host_str, port_str, port, &error)) + return error; + + std::unique_ptr<TCPSocket> listen_socket( + new TCPSocket(true, child_processes_inherit)); + if (error.Fail()) + return error; + + error = listen_socket->Listen(host_and_port, backlog); + if (error.Success()) { + // We were asked to listen on port zero which means we must now read the + // actual port that was given to us as port zero is a special code for + // "find an open port for me". + if (port == 0) + port = listen_socket->GetLocalPortNumber(); + + // Set the port predicate since when doing a listen://<host>:<port> it + // often needs to accept the incoming connection which is a blocking system + // call. Allowing access to the bound port using a predicate allows us to + // wait for the port predicate to be set to a non-zero value from another + // thread in an efficient manor. + if (predicate) + predicate->SetValue(port, eBroadcastAlways); + socket = listen_socket.release(); + } + + return error; +} + +Status Socket::UdpConnect(llvm::StringRef host_and_port, + bool child_processes_inherit, Socket *&socket) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + LLDB_LOGF(log, "Socket::%s (host/port = %s)", __FUNCTION__, + host_and_port.str().c_str()); + + return UDPSocket::Connect(host_and_port, child_processes_inherit, socket); +} + +Status Socket::UnixDomainConnect(llvm::StringRef name, + bool child_processes_inherit, + Socket *&socket) { + Status error; + std::unique_ptr<Socket> connect_socket( + Create(ProtocolUnixDomain, child_processes_inherit, error)); + if (error.Fail()) + return error; + + error = connect_socket->Connect(name); + if (error.Success()) + socket = connect_socket.release(); + + return error; +} + +Status Socket::UnixDomainAccept(llvm::StringRef name, + bool child_processes_inherit, Socket *&socket) { + Status error; + std::unique_ptr<Socket> listen_socket( + Create(ProtocolUnixDomain, child_processes_inherit, error)); + if (error.Fail()) + return error; + + error = listen_socket->Listen(name, 5); + if (error.Fail()) + return error; + + error = listen_socket->Accept(socket); + return error; +} + +Status Socket::UnixAbstractConnect(llvm::StringRef name, + bool child_processes_inherit, + Socket *&socket) { + Status error; + std::unique_ptr<Socket> connect_socket( + Create(ProtocolUnixAbstract, child_processes_inherit, error)); + if (error.Fail()) + return error; + + error = connect_socket->Connect(name); + if (error.Success()) + socket = connect_socket.release(); + return error; +} + +Status Socket::UnixAbstractAccept(llvm::StringRef name, + bool child_processes_inherit, + Socket *&socket) { + Status error; + std::unique_ptr<Socket> listen_socket( + Create(ProtocolUnixAbstract, child_processes_inherit, error)); + if (error.Fail()) + return error; + + error = listen_socket->Listen(name, 5); + if (error.Fail()) + return error; + + error = listen_socket->Accept(socket); + return error; +} + +bool Socket::DecodeHostAndPort(llvm::StringRef host_and_port, + std::string &host_str, std::string &port_str, + int32_t &port, Status *error_ptr) { + static RegularExpression g_regex( + llvm::StringRef("([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)")); + llvm::SmallVector<llvm::StringRef, 3> matches; + if (g_regex.Execute(host_and_port, &matches)) { + host_str = matches[1].str(); + port_str = matches[2].str(); + // IPv6 addresses are wrapped in [] when specified with ports + if (host_str.front() == '[' && host_str.back() == ']') + host_str = host_str.substr(1, host_str.size() - 2); + bool ok = false; + port = StringConvert::ToUInt32(port_str.c_str(), UINT32_MAX, 10, &ok); + if (ok && port <= UINT16_MAX) { + if (error_ptr) + error_ptr->Clear(); + return true; + } + // port is too large + if (error_ptr) + error_ptr->SetErrorStringWithFormat( + "invalid host:port specification: '%s'", host_and_port.str().c_str()); + return false; + } + + // If this was unsuccessful, then check if it's simply a signed 32-bit + // integer, representing a port with an empty host. + host_str.clear(); + port_str.clear(); + if (to_integer(host_and_port, port, 10) && port < UINT16_MAX) { + port_str = host_and_port; + if (error_ptr) + error_ptr->Clear(); + return true; + } + + if (error_ptr) + error_ptr->SetErrorStringWithFormat("invalid host:port specification: '%s'", + host_and_port.str().c_str()); + return false; +} + +IOObject::WaitableHandle Socket::GetWaitableHandle() { + // TODO: On Windows, use WSAEventSelect + return m_socket; +} + +Status Socket::Read(void *buf, size_t &num_bytes) { + Status error; + int bytes_received = 0; + do { + bytes_received = ::recv(m_socket, static_cast<char *>(buf), num_bytes, 0); + } while (bytes_received < 0 && IsInterrupted()); + + if (bytes_received < 0) { + SetLastError(error); + num_bytes = 0; + } else + num_bytes = bytes_received; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_COMMUNICATION)); + if (log) { + LLDB_LOGF(log, + "%p Socket::Read() (socket = %" PRIu64 + ", src = %p, src_len = %" PRIu64 ", flags = 0) => %" PRIi64 + " (error = %s)", + static_cast<void *>(this), static_cast<uint64_t>(m_socket), buf, + static_cast<uint64_t>(num_bytes), + static_cast<int64_t>(bytes_received), error.AsCString()); + } + + return error; +} + +Status Socket::Write(const void *buf, size_t &num_bytes) { + const size_t src_len = num_bytes; + Status error; + int bytes_sent = 0; + do { + bytes_sent = Send(buf, num_bytes); + } while (bytes_sent < 0 && IsInterrupted()); + + if (bytes_sent < 0) { + SetLastError(error); + num_bytes = 0; + } else + num_bytes = bytes_sent; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_COMMUNICATION)); + if (log) { + LLDB_LOGF(log, + "%p Socket::Write() (socket = %" PRIu64 + ", src = %p, src_len = %" PRIu64 ", flags = 0) => %" PRIi64 + " (error = %s)", + static_cast<void *>(this), static_cast<uint64_t>(m_socket), buf, + static_cast<uint64_t>(src_len), + static_cast<int64_t>(bytes_sent), error.AsCString()); + } + + return error; +} + +Status Socket::PreDisconnect() { + Status error; + return error; +} + +Status Socket::Close() { + Status error; + if (!IsValid() || !m_should_close_fd) + return error; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + LLDB_LOGF(log, "%p Socket::Close (fd = %" PRIu64 ")", + static_cast<void *>(this), static_cast<uint64_t>(m_socket)); + +#if defined(_WIN32) + bool success = !!closesocket(m_socket); +#else + bool success = !!::close(m_socket); +#endif + // A reference to a FD was passed in, set it to an invalid value + m_socket = kInvalidSocketValue; + if (!success) { + SetLastError(error); + } + + return error; +} + +int Socket::GetOption(int level, int option_name, int &option_value) { + get_socket_option_arg_type option_value_p = + reinterpret_cast<get_socket_option_arg_type>(&option_value); + socklen_t option_value_size = sizeof(int); + return ::getsockopt(m_socket, level, option_name, option_value_p, + &option_value_size); +} + +int Socket::SetOption(int level, int option_name, int option_value) { + set_socket_option_arg_type option_value_p = + reinterpret_cast<get_socket_option_arg_type>(&option_value); + return ::setsockopt(m_socket, level, option_name, option_value_p, + sizeof(option_value)); +} + +size_t Socket::Send(const void *buf, const size_t num_bytes) { + return ::send(m_socket, static_cast<const char *>(buf), num_bytes, 0); +} + +void Socket::SetLastError(Status &error) { +#if defined(_WIN32) + error.SetError(::WSAGetLastError(), lldb::eErrorTypeWin32); +#else + error.SetErrorToErrno(); +#endif +} + +NativeSocket Socket::CreateSocket(const int domain, const int type, + const int protocol, + bool child_processes_inherit, Status &error) { + error.Clear(); + auto socket_type = type; +#ifdef SOCK_CLOEXEC + if (!child_processes_inherit) + socket_type |= SOCK_CLOEXEC; +#endif + auto sock = ::socket(domain, socket_type, protocol); + if (sock == kInvalidSocketValue) + SetLastError(error); + + return sock; +} + +NativeSocket Socket::AcceptSocket(NativeSocket sockfd, struct sockaddr *addr, + socklen_t *addrlen, + bool child_processes_inherit, Status &error) { + error.Clear(); +#if defined(ANDROID_USE_ACCEPT_WORKAROUND) + // Hack: + // This enables static linking lldb-server to an API 21 libc, but still + // having it run on older devices. It is necessary because API 21 libc's + // implementation of accept() uses the accept4 syscall(), which is not + // available in older kernels. Using an older libc would fix this issue, but + // introduce other ones, as the old libraries were quite buggy. + int fd = syscall(__NR_accept, sockfd, addr, addrlen); + if (fd >= 0 && !child_processes_inherit) { + int flags = ::fcntl(fd, F_GETFD); + if (flags != -1 && ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1) + return fd; + SetLastError(error); + close(fd); + } + return fd; +#elif defined(SOCK_CLOEXEC) && defined(HAVE_ACCEPT4) + int flags = 0; + if (!child_processes_inherit) { + flags |= SOCK_CLOEXEC; + } + NativeSocket fd = llvm::sys::RetryAfterSignal( + static_cast<NativeSocket>(-1), ::accept4, sockfd, addr, addrlen, flags); +#else + NativeSocket fd = llvm::sys::RetryAfterSignal( + static_cast<NativeSocket>(-1), ::accept, sockfd, addr, addrlen); +#endif + if (fd == kInvalidSocketValue) + SetLastError(error); + return fd; +} diff --git a/gnu/llvm/lldb/source/Host/common/SocketAddress.cpp b/gnu/llvm/lldb/source/Host/common/SocketAddress.cpp new file mode 100644 index 00000000000..7431e3a155e --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/SocketAddress.cpp @@ -0,0 +1,322 @@ +//===-- SocketAddress.cpp ---------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Note: This file is used on Darwin by debugserver, so it needs to remain as +// self contained as possible, and devoid of references to LLVM unless +// there is compelling reason. +// +//===----------------------------------------------------------------------===// + +#if defined(_MSC_VER) +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include "lldb/Host/SocketAddress.h" +#include <stddef.h> +#include <stdio.h> + +#if !defined(_WIN32) +#include <arpa/inet.h> +#endif + +#include <assert.h> +#include <string.h> + +#include "lldb/Host/PosixApi.h" + +// WindowsXP needs an inet_ntop implementation +#ifdef _WIN32 + +#ifndef INET6_ADDRSTRLEN // might not be defined in older Windows SDKs +#define INET6_ADDRSTRLEN 46 +#endif + +// TODO: implement shortened form "::" for runs of zeros +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + if (size == 0) { + return nullptr; + } + + switch (af) { + case AF_INET: { + { + const char *formatted = inet_ntoa(*static_cast<const in_addr *>(src)); + if (formatted && strlen(formatted) < static_cast<size_t>(size)) { + return ::strcpy(dst, formatted); + } + } + return nullptr; + case AF_INET6: { + char tmp[INET6_ADDRSTRLEN] = {0}; + const uint16_t *src16 = static_cast<const uint16_t *>(src); + int full_size = ::snprintf( + tmp, sizeof(tmp), "%x:%x:%x:%x:%x:%x:%x:%x", ntohs(src16[0]), + ntohs(src16[1]), ntohs(src16[2]), ntohs(src16[3]), ntohs(src16[4]), + ntohs(src16[5]), ntohs(src16[6]), ntohs(src16[7])); + if (full_size < static_cast<int>(size)) { + return ::strcpy(dst, tmp); + } + return nullptr; + } + } + } + return nullptr; +} +#endif + +using namespace lldb_private; + +// SocketAddress constructor +SocketAddress::SocketAddress() { Clear(); } + +SocketAddress::SocketAddress(const struct sockaddr &s) { m_socket_addr.sa = s; } + +SocketAddress::SocketAddress(const struct sockaddr_in &s) { + m_socket_addr.sa_ipv4 = s; +} + +SocketAddress::SocketAddress(const struct sockaddr_in6 &s) { + m_socket_addr.sa_ipv6 = s; +} + +SocketAddress::SocketAddress(const struct sockaddr_storage &s) { + m_socket_addr.sa_storage = s; +} + +SocketAddress::SocketAddress(const struct addrinfo *addr_info) { + *this = addr_info; +} + +// Destructor +SocketAddress::~SocketAddress() {} + +void SocketAddress::Clear() { + memset(&m_socket_addr, 0, sizeof(m_socket_addr)); +} + +bool SocketAddress::IsValid() const { return GetLength() != 0; } + +static socklen_t GetFamilyLength(sa_family_t family) { + switch (family) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + } + assert(0 && "Unsupported address family"); + return 0; +} + +socklen_t SocketAddress::GetLength() const { +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + return m_socket_addr.sa.sa_len; +#else + return GetFamilyLength(GetFamily()); +#endif +} + +socklen_t SocketAddress::GetMaxLength() { return sizeof(sockaddr_t); } + +sa_family_t SocketAddress::GetFamily() const { + return m_socket_addr.sa.sa_family; +} + +void SocketAddress::SetFamily(sa_family_t family) { + m_socket_addr.sa.sa_family = family; +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + m_socket_addr.sa.sa_len = GetFamilyLength(family); +#endif +} + +std::string SocketAddress::GetIPAddress() const { + char str[INET6_ADDRSTRLEN] = {0}; + switch (GetFamily()) { + case AF_INET: + if (inet_ntop(GetFamily(), &m_socket_addr.sa_ipv4.sin_addr, str, + sizeof(str))) + return str; + break; + case AF_INET6: + if (inet_ntop(GetFamily(), &m_socket_addr.sa_ipv6.sin6_addr, str, + sizeof(str))) + return str; + break; + } + return ""; +} + +uint16_t SocketAddress::GetPort() const { + switch (GetFamily()) { + case AF_INET: + return ntohs(m_socket_addr.sa_ipv4.sin_port); + case AF_INET6: + return ntohs(m_socket_addr.sa_ipv6.sin6_port); + } + return 0; +} + +bool SocketAddress::SetPort(uint16_t port) { + switch (GetFamily()) { + case AF_INET: + m_socket_addr.sa_ipv4.sin_port = htons(port); + return true; + + case AF_INET6: + m_socket_addr.sa_ipv6.sin6_port = htons(port); + return true; + } + return false; +} + +// SocketAddress assignment operator +const SocketAddress &SocketAddress:: +operator=(const struct addrinfo *addr_info) { + Clear(); + if (addr_info && addr_info->ai_addr && addr_info->ai_addrlen > 0 && + size_t(addr_info->ai_addrlen) <= sizeof m_socket_addr) { + ::memcpy(&m_socket_addr, addr_info->ai_addr, addr_info->ai_addrlen); + } + return *this; +} + +const SocketAddress &SocketAddress::operator=(const struct sockaddr &s) { + m_socket_addr.sa = s; + return *this; +} + +const SocketAddress &SocketAddress::operator=(const struct sockaddr_in &s) { + m_socket_addr.sa_ipv4 = s; + return *this; +} + +const SocketAddress &SocketAddress::operator=(const struct sockaddr_in6 &s) { + m_socket_addr.sa_ipv6 = s; + return *this; +} + +const SocketAddress &SocketAddress:: +operator=(const struct sockaddr_storage &s) { + m_socket_addr.sa_storage = s; + return *this; +} + +bool SocketAddress::getaddrinfo(const char *host, const char *service, + int ai_family, int ai_socktype, int ai_protocol, + int ai_flags) { + Clear(); + + auto addresses = GetAddressInfo(host, service, ai_family, ai_socktype, + ai_protocol, ai_flags); + if (!addresses.empty()) + *this = addresses[0]; + return IsValid(); +} + +std::vector<SocketAddress> +SocketAddress::GetAddressInfo(const char *hostname, const char *servname, + int ai_family, int ai_socktype, int ai_protocol, + int ai_flags) { + std::vector<SocketAddress> addr_list; + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ai_family; + hints.ai_socktype = ai_socktype; + hints.ai_protocol = ai_protocol; + hints.ai_flags = ai_flags; + + struct addrinfo *service_info_list = nullptr; + int err = ::getaddrinfo(hostname, servname, &hints, &service_info_list); + if (err == 0 && service_info_list) { + for (struct addrinfo *service_ptr = service_info_list; + service_ptr != nullptr; service_ptr = service_ptr->ai_next) { + addr_list.emplace_back(SocketAddress(service_ptr)); + } + } + + if (service_info_list) + ::freeaddrinfo(service_info_list); + return addr_list; +} + +bool SocketAddress::SetToLocalhost(sa_family_t family, uint16_t port) { + switch (family) { + case AF_INET: + SetFamily(AF_INET); + if (SetPort(port)) { + m_socket_addr.sa_ipv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + return true; + } + break; + + case AF_INET6: + SetFamily(AF_INET6); + if (SetPort(port)) { + m_socket_addr.sa_ipv6.sin6_addr = in6addr_loopback; + return true; + } + break; + } + Clear(); + return false; +} + +bool SocketAddress::SetToAnyAddress(sa_family_t family, uint16_t port) { + switch (family) { + case AF_INET: + SetFamily(AF_INET); + if (SetPort(port)) { + m_socket_addr.sa_ipv4.sin_addr.s_addr = htonl(INADDR_ANY); + return true; + } + break; + + case AF_INET6: + SetFamily(AF_INET6); + if (SetPort(port)) { + m_socket_addr.sa_ipv6.sin6_addr = in6addr_any; + return true; + } + break; + } + Clear(); + return false; +} + +bool SocketAddress::IsAnyAddr() const { + return (GetFamily() == AF_INET) + ? m_socket_addr.sa_ipv4.sin_addr.s_addr == htonl(INADDR_ANY) + : 0 == memcmp(&m_socket_addr.sa_ipv6.sin6_addr, &in6addr_any, 16); +} + +bool SocketAddress::IsLocalhost() const { + return (GetFamily() == AF_INET) + ? m_socket_addr.sa_ipv4.sin_addr.s_addr == htonl(INADDR_LOOPBACK) + : 0 == memcmp(&m_socket_addr.sa_ipv6.sin6_addr, &in6addr_loopback, + 16); +} + +bool SocketAddress::operator==(const SocketAddress &rhs) const { + if (GetFamily() != rhs.GetFamily()) + return false; + if (GetLength() != rhs.GetLength()) + return false; + switch (GetFamily()) { + case AF_INET: + return m_socket_addr.sa_ipv4.sin_addr.s_addr == + rhs.m_socket_addr.sa_ipv4.sin_addr.s_addr; + case AF_INET6: + return 0 == memcmp(&m_socket_addr.sa_ipv6.sin6_addr, + &rhs.m_socket_addr.sa_ipv6.sin6_addr, 16); + } + return false; +} + +bool SocketAddress::operator!=(const SocketAddress &rhs) const { + return !(*this == rhs); +} diff --git a/gnu/llvm/lldb/source/Host/common/StringConvert.cpp b/gnu/llvm/lldb/source/Host/common/StringConvert.cpp new file mode 100644 index 00000000000..8bf04f0a9ca --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/StringConvert.cpp @@ -0,0 +1,95 @@ +//===-- StringConvert.cpp ---------------------------------------*- C++ -*-===// +// +// 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 <stdlib.h> + +#include "lldb/Host/StringConvert.h" + +namespace lldb_private { +namespace StringConvert { + +int32_t ToSInt32(const char *s, int32_t fail_value, int base, + bool *success_ptr) { + if (s && s[0]) { + char *end = nullptr; + const long sval = ::strtol(s, &end, base); + if (*end == '\0') { + if (success_ptr) + *success_ptr = ((sval <= INT32_MAX) && (sval >= INT32_MIN)); + return (int32_t)sval; // All characters were used, return the result + } + } + if (success_ptr) + *success_ptr = false; + return fail_value; +} + +uint32_t ToUInt32(const char *s, uint32_t fail_value, int base, + bool *success_ptr) { + if (s && s[0]) { + char *end = nullptr; + const unsigned long uval = ::strtoul(s, &end, base); + if (*end == '\0') { + if (success_ptr) + *success_ptr = (uval <= UINT32_MAX); + return (uint32_t)uval; // All characters were used, return the result + } + } + if (success_ptr) + *success_ptr = false; + return fail_value; +} + +int64_t ToSInt64(const char *s, int64_t fail_value, int base, + bool *success_ptr) { + if (s && s[0]) { + char *end = nullptr; + int64_t uval = ::strtoll(s, &end, base); + if (*end == '\0') { + if (success_ptr) + *success_ptr = true; + return uval; // All characters were used, return the result + } + } + if (success_ptr) + *success_ptr = false; + return fail_value; +} + +uint64_t ToUInt64(const char *s, uint64_t fail_value, int base, + bool *success_ptr) { + if (s && s[0]) { + char *end = nullptr; + uint64_t uval = ::strtoull(s, &end, base); + if (*end == '\0') { + if (success_ptr) + *success_ptr = true; + return uval; // All characters were used, return the result + } + } + if (success_ptr) + *success_ptr = false; + return fail_value; +} + +double ToDouble(const char *s, double fail_value, bool *success_ptr) { + if (s && s[0]) { + char *end = nullptr; + double val = strtod(s, &end); + if (*end == '\0') { + if (success_ptr) + *success_ptr = true; + return val; // All characters were used, return the result + } + } + if (success_ptr) + *success_ptr = false; + return fail_value; +} +} +} diff --git a/gnu/llvm/lldb/source/Host/common/TCPSocket.cpp b/gnu/llvm/lldb/source/Host/common/TCPSocket.cpp new file mode 100644 index 00000000000..b716935db6e --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/TCPSocket.cpp @@ -0,0 +1,308 @@ +//===-- TCPSocket.cpp -------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if defined(_MSC_VER) +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include "lldb/Host/common/TCPSocket.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/MainLoop.h" +#include "lldb/Utility/Log.h" + +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/raw_ostream.h" + +#if LLDB_ENABLE_POSIX +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <sys/socket.h> +#endif + +#if defined(_WIN32) +#include <winsock2.h> +#endif + +#ifdef _WIN32 +#define CLOSE_SOCKET closesocket +typedef const char *set_socket_option_arg_type; +#else +#include <unistd.h> +#define CLOSE_SOCKET ::close +typedef const void *set_socket_option_arg_type; +#endif + +using namespace lldb; +using namespace lldb_private; + +namespace { +const int kType = SOCK_STREAM; +} + +TCPSocket::TCPSocket(bool should_close, bool child_processes_inherit) + : Socket(ProtocolTcp, should_close, child_processes_inherit) {} + +TCPSocket::TCPSocket(NativeSocket socket, const TCPSocket &listen_socket) + : Socket(ProtocolTcp, listen_socket.m_should_close_fd, + listen_socket.m_child_processes_inherit) { + m_socket = socket; +} + +TCPSocket::TCPSocket(NativeSocket socket, bool should_close, + bool child_processes_inherit) + : Socket(ProtocolTcp, should_close, child_processes_inherit) { + m_socket = socket; +} + +TCPSocket::~TCPSocket() { CloseListenSockets(); } + +bool TCPSocket::IsValid() const { + return m_socket != kInvalidSocketValue || m_listen_sockets.size() != 0; +} + +// Return the port number that is being used by the socket. +uint16_t TCPSocket::GetLocalPortNumber() const { + if (m_socket != kInvalidSocketValue) { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength(); + if (::getsockname(m_socket, sock_addr, &sock_addr_len) == 0) + return sock_addr.GetPort(); + } else if (!m_listen_sockets.empty()) { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength(); + if (::getsockname(m_listen_sockets.begin()->first, sock_addr, + &sock_addr_len) == 0) + return sock_addr.GetPort(); + } + return 0; +} + +std::string TCPSocket::GetLocalIPAddress() const { + // We bound to port zero, so we need to figure out which port we actually + // bound to + if (m_socket != kInvalidSocketValue) { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength(); + if (::getsockname(m_socket, sock_addr, &sock_addr_len) == 0) + return sock_addr.GetIPAddress(); + } + return ""; +} + +uint16_t TCPSocket::GetRemotePortNumber() const { + if (m_socket != kInvalidSocketValue) { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength(); + if (::getpeername(m_socket, sock_addr, &sock_addr_len) == 0) + return sock_addr.GetPort(); + } + return 0; +} + +std::string TCPSocket::GetRemoteIPAddress() const { + // We bound to port zero, so we need to figure out which port we actually + // bound to + if (m_socket != kInvalidSocketValue) { + SocketAddress sock_addr; + socklen_t sock_addr_len = sock_addr.GetMaxLength(); + if (::getpeername(m_socket, sock_addr, &sock_addr_len) == 0) + return sock_addr.GetIPAddress(); + } + return ""; +} + +std::string TCPSocket::GetRemoteConnectionURI() const { + if (m_socket != kInvalidSocketValue) { + return llvm::formatv("connect://[{0}]:{1}", GetRemoteIPAddress(), + GetRemotePortNumber()); + } + return ""; +} + +Status TCPSocket::CreateSocket(int domain) { + Status error; + if (IsValid()) + error = Close(); + if (error.Fail()) + return error; + m_socket = Socket::CreateSocket(domain, kType, IPPROTO_TCP, + m_child_processes_inherit, error); + return error; +} + +Status TCPSocket::Connect(llvm::StringRef name) { + + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_COMMUNICATION)); + LLDB_LOGF(log, "TCPSocket::%s (host/port = %s)", __FUNCTION__, name.data()); + + Status error; + std::string host_str; + std::string port_str; + int32_t port = INT32_MIN; + if (!DecodeHostAndPort(name, host_str, port_str, port, &error)) + return error; + + std::vector<SocketAddress> addresses = SocketAddress::GetAddressInfo( + host_str.c_str(), nullptr, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); + for (SocketAddress &address : addresses) { + error = CreateSocket(address.GetFamily()); + if (error.Fail()) + continue; + + address.SetPort(port); + + if (-1 == llvm::sys::RetryAfterSignal(-1, ::connect, + GetNativeSocket(), &address.sockaddr(), address.GetLength())) { + CLOSE_SOCKET(GetNativeSocket()); + continue; + } + + SetOptionNoDelay(); + + error.Clear(); + return error; + } + + error.SetErrorString("Failed to connect port"); + return error; +} + +Status TCPSocket::Listen(llvm::StringRef name, int backlog) { + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + LLDB_LOGF(log, "TCPSocket::%s (%s)", __FUNCTION__, name.data()); + + Status error; + std::string host_str; + std::string port_str; + int32_t port = INT32_MIN; + if (!DecodeHostAndPort(name, host_str, port_str, port, &error)) + return error; + + if (host_str == "*") + host_str = "0.0.0.0"; + std::vector<SocketAddress> addresses = SocketAddress::GetAddressInfo( + host_str.c_str(), nullptr, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); + for (SocketAddress &address : addresses) { + int fd = Socket::CreateSocket(address.GetFamily(), kType, IPPROTO_TCP, + m_child_processes_inherit, error); + if (error.Fail()) { + error.Clear(); + continue; + } + + // enable local address reuse + int option_value = 1; + set_socket_option_arg_type option_value_p = + reinterpret_cast<set_socket_option_arg_type>(&option_value); + ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, option_value_p, + sizeof(option_value)); + + SocketAddress listen_address = address; + if(!listen_address.IsLocalhost()) + listen_address.SetToAnyAddress(address.GetFamily(), port); + else + listen_address.SetPort(port); + + int err = + ::bind(fd, &listen_address.sockaddr(), listen_address.GetLength()); + if (-1 != err) + err = ::listen(fd, backlog); + + if (-1 == err) { + CLOSE_SOCKET(fd); + continue; + } + + if (port == 0) { + socklen_t sa_len = address.GetLength(); + if (getsockname(fd, &address.sockaddr(), &sa_len) == 0) + port = address.GetPort(); + } + m_listen_sockets[fd] = address; + } + + if (m_listen_sockets.size() == 0) + error.SetErrorString("Failed to connect port"); + return error; +} + +void TCPSocket::CloseListenSockets() { + for (auto socket : m_listen_sockets) + CLOSE_SOCKET(socket.first); + m_listen_sockets.clear(); +} + +Status TCPSocket::Accept(Socket *&conn_socket) { + Status error; + if (m_listen_sockets.size() == 0) { + error.SetErrorString("No open listening sockets!"); + return error; + } + + int sock = -1; + int listen_sock = -1; + lldb_private::SocketAddress AcceptAddr; + MainLoop accept_loop; + std::vector<MainLoopBase::ReadHandleUP> handles; + for (auto socket : m_listen_sockets) { + auto fd = socket.first; + auto inherit = this->m_child_processes_inherit; + auto io_sp = IOObjectSP(new TCPSocket(socket.first, false, inherit)); + handles.emplace_back(accept_loop.RegisterReadObject( + io_sp, [fd, inherit, &sock, &AcceptAddr, &error, + &listen_sock](MainLoopBase &loop) { + socklen_t sa_len = AcceptAddr.GetMaxLength(); + sock = AcceptSocket(fd, &AcceptAddr.sockaddr(), &sa_len, inherit, + error); + listen_sock = fd; + loop.RequestTermination(); + }, error)); + if (error.Fail()) + return error; + } + + bool accept_connection = false; + std::unique_ptr<TCPSocket> accepted_socket; + // Loop until we are happy with our connection + while (!accept_connection) { + accept_loop.Run(); + + if (error.Fail()) + return error; + + lldb_private::SocketAddress &AddrIn = m_listen_sockets[listen_sock]; + if (!AddrIn.IsAnyAddr() && AcceptAddr != AddrIn) { + CLOSE_SOCKET(sock); + llvm::errs() << llvm::formatv( + "error: rejecting incoming connection from {0} (expecting {1})", + AcceptAddr.GetIPAddress(), AddrIn.GetIPAddress()); + continue; + } + accept_connection = true; + accepted_socket.reset(new TCPSocket(sock, *this)); + } + + if (!accepted_socket) + return error; + + // Keep our TCP packets coming without any delays. + accepted_socket->SetOptionNoDelay(); + error.Clear(); + conn_socket = accepted_socket.release(); + return error; +} + +int TCPSocket::SetOptionNoDelay() { + return SetOption(IPPROTO_TCP, TCP_NODELAY, 1); +} + +int TCPSocket::SetOptionReuseAddress() { + return SetOption(SOL_SOCKET, SO_REUSEADDR, 1); +} diff --git a/gnu/llvm/lldb/source/Host/common/TaskPool.cpp b/gnu/llvm/lldb/source/Host/common/TaskPool.cpp new file mode 100644 index 00000000000..73f761b5cf6 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/TaskPool.cpp @@ -0,0 +1,126 @@ +//===--------------------- TaskPool.cpp -------------------------*- C++ -*-===// +// +// 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/TaskPool.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/Log.h" + +#include <cstdint> +#include <queue> +#include <thread> + +namespace lldb_private { + +namespace { +class TaskPoolImpl { +public: + static TaskPoolImpl &GetInstance(); + + void AddTask(std::function<void()> &&task_fn); + +private: + TaskPoolImpl(); + + static lldb::thread_result_t WorkerPtr(void *pool); + + static void Worker(TaskPoolImpl *pool); + + std::queue<std::function<void()>> m_tasks; + std::mutex m_tasks_mutex; + uint32_t m_thread_count; +}; + +} // end of anonymous namespace + +TaskPoolImpl &TaskPoolImpl::GetInstance() { + static TaskPoolImpl g_task_pool_impl; + return g_task_pool_impl; +} + +void TaskPool::AddTaskImpl(std::function<void()> &&task_fn) { + TaskPoolImpl::GetInstance().AddTask(std::move(task_fn)); +} + +TaskPoolImpl::TaskPoolImpl() : m_thread_count(0) {} + +unsigned GetHardwareConcurrencyHint() { + // std::thread::hardware_concurrency may return 0 if the value is not well + // defined or not computable. + static const unsigned g_hardware_concurrency = + std::max(1u, std::thread::hardware_concurrency()); + return g_hardware_concurrency; +} + +void TaskPoolImpl::AddTask(std::function<void()> &&task_fn) { + const size_t min_stack_size = 8 * 1024 * 1024; + + std::unique_lock<std::mutex> lock(m_tasks_mutex); + m_tasks.emplace(std::move(task_fn)); + if (m_thread_count < GetHardwareConcurrencyHint()) { + m_thread_count++; + // Note that this detach call needs to happen with the m_tasks_mutex held. + // This prevents the thread from exiting prematurely and triggering a linux + // libc bug (https://sourceware.org/bugzilla/show_bug.cgi?id=19951). + llvm::Expected<HostThread> host_thread = + lldb_private::ThreadLauncher::LaunchThread( + "task-pool.worker", WorkerPtr, this, min_stack_size); + if (host_thread) { + host_thread->Release(); + } else { + LLDB_LOG(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST), + "failed to launch host thread: {}", + llvm::toString(host_thread.takeError())); + } + } +} + +lldb::thread_result_t TaskPoolImpl::WorkerPtr(void *pool) { + Worker((TaskPoolImpl *)pool); + return {}; +} + +void TaskPoolImpl::Worker(TaskPoolImpl *pool) { + while (true) { + std::unique_lock<std::mutex> lock(pool->m_tasks_mutex); + if (pool->m_tasks.empty()) { + pool->m_thread_count--; + break; + } + + std::function<void()> f = std::move(pool->m_tasks.front()); + pool->m_tasks.pop(); + lock.unlock(); + + f(); + } +} + +void TaskMapOverInt(size_t begin, size_t end, + const llvm::function_ref<void(size_t)> &func) { + const size_t num_workers = std::min<size_t>(end, GetHardwareConcurrencyHint()); + std::atomic<size_t> idx{begin}; + + auto wrapper = [&idx, end, &func]() { + while (true) { + size_t i = idx.fetch_add(1); + if (i >= end) + break; + func(i); + } + }; + + std::vector<std::future<void>> futures; + futures.reserve(num_workers); + for (size_t i = 0; i < num_workers; i++) + futures.push_back(TaskPool::AddTask(wrapper)); + for (size_t i = 0; i < num_workers; i++) + futures[i].wait(); +} + +} // namespace lldb_private + diff --git a/gnu/llvm/lldb/source/Host/common/Terminal.cpp b/gnu/llvm/lldb/source/Host/common/Terminal.cpp new file mode 100644 index 00000000000..e1aea26eeb9 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/Terminal.cpp @@ -0,0 +1,236 @@ +//===-- Terminal.cpp --------------------------------------------*- C++ -*-===// +// +// 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/Terminal.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/PosixApi.h" +#include "llvm/ADT/STLExtras.h" + +#include <fcntl.h> +#include <signal.h> + +#if LLDB_ENABLE_TERMIOS +#include <termios.h> +#endif + +using namespace lldb_private; + +bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); } + +bool Terminal::SetEcho(bool enabled) { + if (FileDescriptorIsValid()) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + bool set_corectly = false; + if (enabled) { + if (fd_termios.c_lflag & ECHO) + set_corectly = true; + else + fd_termios.c_lflag |= ECHO; + } else { + if (fd_termios.c_lflag & ECHO) + fd_termios.c_lflag &= ~ECHO; + else + set_corectly = true; + } + + if (set_corectly) + return true; + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + } + return false; +} + +bool Terminal::SetCanonical(bool enabled) { + if (FileDescriptorIsValid()) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + bool set_corectly = false; + if (enabled) { + if (fd_termios.c_lflag & ICANON) + set_corectly = true; + else + fd_termios.c_lflag |= ICANON; + } else { + if (fd_termios.c_lflag & ICANON) + fd_termios.c_lflag &= ~ICANON; + else + set_corectly = true; + } + + if (set_corectly) + return true; + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + } + return false; +} + +// Default constructor +TerminalState::TerminalState() + : m_tty(), m_tflags(-1), +#if LLDB_ENABLE_TERMIOS + m_termios_up(), +#endif + m_process_group(-1) { +} + +// Destructor +TerminalState::~TerminalState() {} + +void TerminalState::Clear() { + m_tty.Clear(); + m_tflags = -1; +#if LLDB_ENABLE_TERMIOS + m_termios_up.reset(); +#endif + m_process_group = -1; +} + +// Save the current state of the TTY for the file descriptor "fd" and if +// "save_process_group" is true, attempt to save the process group info for the +// TTY. +bool TerminalState::Save(int fd, bool save_process_group) { + m_tty.SetFileDescriptor(fd); + if (m_tty.IsATerminal()) { +#if LLDB_ENABLE_POSIX + m_tflags = ::fcntl(fd, F_GETFL, 0); +#endif +#if LLDB_ENABLE_TERMIOS + if (m_termios_up == nullptr) + m_termios_up.reset(new struct termios); + int err = ::tcgetattr(fd, m_termios_up.get()); + if (err != 0) + m_termios_up.reset(); +#endif // #if LLDB_ENABLE_TERMIOS +#if LLDB_ENABLE_POSIX + if (save_process_group) + m_process_group = ::tcgetpgrp(0); + else + m_process_group = -1; +#endif + } else { + m_tty.Clear(); + m_tflags = -1; +#if LLDB_ENABLE_TERMIOS + m_termios_up.reset(); +#endif + m_process_group = -1; + } + return IsValid(); +} + +// Restore the state of the TTY using the cached values from a previous call to +// Save(). +bool TerminalState::Restore() const { +#if LLDB_ENABLE_POSIX + if (IsValid()) { + const int fd = m_tty.GetFileDescriptor(); + if (TFlagsIsValid()) + fcntl(fd, F_SETFL, m_tflags); + +#if LLDB_ENABLE_TERMIOS + if (TTYStateIsValid()) + tcsetattr(fd, TCSANOW, m_termios_up.get()); +#endif // #if LLDB_ENABLE_TERMIOS + + if (ProcessGroupIsValid()) { + // Save the original signal handler. + void (*saved_sigttou_callback)(int) = nullptr; + saved_sigttou_callback = (void (*)(int))signal(SIGTTOU, SIG_IGN); + // Set the process group + tcsetpgrp(fd, m_process_group); + // Restore the original signal handler. + signal(SIGTTOU, saved_sigttou_callback); + } + return true; + } +#endif + return false; +} + +// Returns true if this object has valid saved TTY state settings that can be +// used to restore a previous state. +bool TerminalState::IsValid() const { + return m_tty.FileDescriptorIsValid() && + (TFlagsIsValid() || TTYStateIsValid()); +} + +// Returns true if m_tflags is valid +bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; } + +// Returns true if m_ttystate is valid +bool TerminalState::TTYStateIsValid() const { +#if LLDB_ENABLE_TERMIOS + return m_termios_up != nullptr; +#else + return false; +#endif +} + +// Returns true if m_process_group is valid +bool TerminalState::ProcessGroupIsValid() const { + return static_cast<int32_t>(m_process_group) != -1; +} + +// Constructor +TerminalStateSwitcher::TerminalStateSwitcher() : m_currentState(UINT32_MAX) {} + +// Destructor +TerminalStateSwitcher::~TerminalStateSwitcher() {} + +// Returns the number of states that this switcher contains +uint32_t TerminalStateSwitcher::GetNumberOfStates() const { + return llvm::array_lengthof(m_ttystates); +} + +// Restore the state at index "idx". +// +// Returns true if the restore was successful, false otherwise. +bool TerminalStateSwitcher::Restore(uint32_t idx) const { + const uint32_t num_states = GetNumberOfStates(); + if (idx >= num_states) + return false; + + // See if we already are in this state? + if (m_currentState < num_states && (idx == m_currentState) && + m_ttystates[idx].IsValid()) + return true; + + // Set the state to match the index passed in and only update the current + // state if there are no errors. + if (m_ttystates[idx].Restore()) { + m_currentState = idx; + return true; + } + + // We failed to set the state. The tty state was invalid or not initialized. + return false; +} + +// Save the state at index "idx" for file descriptor "fd" and save the process +// group if requested. +// +// Returns true if the restore was successful, false otherwise. +bool TerminalStateSwitcher::Save(uint32_t idx, int fd, + bool save_process_group) { + const uint32_t num_states = GetNumberOfStates(); + if (idx < num_states) + return m_ttystates[idx].Save(fd, save_process_group); + return false; +} diff --git a/gnu/llvm/lldb/source/Host/common/ThreadLauncher.cpp b/gnu/llvm/lldb/source/Host/common/ThreadLauncher.cpp new file mode 100644 index 00000000000..6e3c8b6a13a --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/ThreadLauncher.cpp @@ -0,0 +1,77 @@ +//===-- ThreadLauncher.cpp --------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// lldb Includes +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/HostNativeThread.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Utility/Log.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/windows.h" +#endif + +#include "llvm/Support/WindowsError.h" + +using namespace lldb; +using namespace lldb_private; + +llvm::Expected<HostThread> ThreadLauncher::LaunchThread( + llvm::StringRef name, lldb::thread_func_t thread_function, + lldb::thread_arg_t thread_arg, size_t min_stack_byte_size) { + // Host::ThreadCreateTrampoline will delete this pointer for us. + HostThreadCreateInfo *info_ptr = + new HostThreadCreateInfo(name.data(), thread_function, thread_arg); + lldb::thread_t thread; +#ifdef _WIN32 + thread = (lldb::thread_t)::_beginthreadex( + 0, (unsigned)min_stack_byte_size, + HostNativeThread::ThreadCreateTrampoline, info_ptr, 0, NULL); + if (thread == LLDB_INVALID_HOST_THREAD) + return llvm::errorCodeToError(llvm::mapWindowsError(GetLastError())); +#else + +// ASAN instrumentation adds a lot of bookkeeping overhead on stack frames. +#if __has_feature(address_sanitizer) + const size_t eight_megabytes = 8 * 1024 * 1024; + if (min_stack_byte_size < eight_megabytes) { + min_stack_byte_size += eight_megabytes; + } +#endif + + pthread_attr_t *thread_attr_ptr = nullptr; + pthread_attr_t thread_attr; + bool destroy_attr = false; + if (min_stack_byte_size > 0) { + if (::pthread_attr_init(&thread_attr) == 0) { + destroy_attr = true; + size_t default_min_stack_byte_size = 0; + if (::pthread_attr_getstacksize(&thread_attr, + &default_min_stack_byte_size) == 0) { + if (default_min_stack_byte_size < min_stack_byte_size) { + if (::pthread_attr_setstacksize(&thread_attr, min_stack_byte_size) == + 0) + thread_attr_ptr = &thread_attr; + } + } + } + } + int err = + ::pthread_create(&thread, thread_attr_ptr, + HostNativeThread::ThreadCreateTrampoline, info_ptr); + + if (destroy_attr) + ::pthread_attr_destroy(&thread_attr); + + if (err) + return llvm::errorCodeToError( + std::error_code(err, std::generic_category())); +#endif + + return HostThread(thread); +} diff --git a/gnu/llvm/lldb/source/Host/common/UDPSocket.cpp b/gnu/llvm/lldb/source/Host/common/UDPSocket.cpp new file mode 100644 index 00000000000..0a991c33645 --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/UDPSocket.cpp @@ -0,0 +1,143 @@ +//===-- UDPSocket.cpp -------------------------------------------*- C++ -*-===// +// +// 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/common/UDPSocket.h" + +#include "lldb/Host/Config.h" +#include "lldb/Utility/Log.h" + +#if LLDB_ENABLE_POSIX +#include <arpa/inet.h> +#include <sys/socket.h> +#endif + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +namespace { + +const int kDomain = AF_INET; +const int kType = SOCK_DGRAM; + +static const char *g_not_supported_error = "Not supported"; +} + +UDPSocket::UDPSocket(NativeSocket socket) : Socket(ProtocolUdp, true, true) { + m_socket = socket; +} + +UDPSocket::UDPSocket(bool should_close, bool child_processes_inherit) + : Socket(ProtocolUdp, should_close, child_processes_inherit) {} + +size_t UDPSocket::Send(const void *buf, const size_t num_bytes) { + return ::sendto(m_socket, static_cast<const char *>(buf), num_bytes, 0, + m_sockaddr, m_sockaddr.GetLength()); +} + +Status UDPSocket::Connect(llvm::StringRef name) { + return Status("%s", g_not_supported_error); +} + +Status UDPSocket::Listen(llvm::StringRef name, int backlog) { + return Status("%s", g_not_supported_error); +} + +Status UDPSocket::Accept(Socket *&socket) { + return Status("%s", g_not_supported_error); +} + +Status UDPSocket::Connect(llvm::StringRef name, bool child_processes_inherit, + Socket *&socket) { + std::unique_ptr<UDPSocket> final_socket; + + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + LLDB_LOGF(log, "UDPSocket::%s (host/port = %s)", __FUNCTION__, name.data()); + + Status error; + std::string host_str; + std::string port_str; + int32_t port = INT32_MIN; + if (!DecodeHostAndPort(name, host_str, port_str, port, &error)) + return error; + + // At this point we have setup the receive port, now we need to setup the UDP + // send socket + + struct addrinfo hints; + struct addrinfo *service_info_list = nullptr; + + ::memset(&hints, 0, sizeof(hints)); + hints.ai_family = kDomain; + hints.ai_socktype = kType; + int err = ::getaddrinfo(host_str.c_str(), port_str.c_str(), &hints, + &service_info_list); + if (err != 0) { + error.SetErrorStringWithFormat( +#if defined(_WIN32) && defined(UNICODE) + "getaddrinfo(%s, %s, &hints, &info) returned error %i (%S)", +#else + "getaddrinfo(%s, %s, &hints, &info) returned error %i (%s)", +#endif + host_str.c_str(), port_str.c_str(), err, gai_strerror(err)); + return error; + } + + for (struct addrinfo *service_info_ptr = service_info_list; + service_info_ptr != nullptr; + service_info_ptr = service_info_ptr->ai_next) { + auto send_fd = CreateSocket( + service_info_ptr->ai_family, service_info_ptr->ai_socktype, + service_info_ptr->ai_protocol, child_processes_inherit, error); + if (error.Success()) { + final_socket.reset(new UDPSocket(send_fd)); + final_socket->m_sockaddr = service_info_ptr; + break; + } else + continue; + } + + ::freeaddrinfo(service_info_list); + + if (!final_socket) + return error; + + SocketAddress bind_addr; + + // Only bind to the loopback address if we are expecting a connection from + // localhost to avoid any firewall issues. + const bool bind_addr_success = (host_str == "127.0.0.1" || host_str == "localhost") + ? bind_addr.SetToLocalhost(kDomain, port) + : bind_addr.SetToAnyAddress(kDomain, port); + + if (!bind_addr_success) { + error.SetErrorString("Failed to get hostspec to bind for"); + return error; + } + + bind_addr.SetPort(0); // Let the source port # be determined dynamically + + err = ::bind(final_socket->GetNativeSocket(), bind_addr, bind_addr.GetLength()); + + struct sockaddr_in source_info; + socklen_t address_len = sizeof (struct sockaddr_in); + err = ::getsockname(final_socket->GetNativeSocket(), (struct sockaddr *) &source_info, &address_len); + + socket = final_socket.release(); + error.Clear(); + return error; +} + +std::string UDPSocket::GetRemoteConnectionURI() const { + if (m_socket != kInvalidSocketValue) { + return llvm::formatv("udp://[{0}]:{1}", m_sockaddr.GetIPAddress(), + m_sockaddr.GetPort()); + } + return ""; +} diff --git a/gnu/llvm/lldb/source/Host/common/XML.cpp b/gnu/llvm/lldb/source/Host/common/XML.cpp new file mode 100644 index 00000000000..28d1f5a8eaf --- /dev/null +++ b/gnu/llvm/lldb/source/Host/common/XML.cpp @@ -0,0 +1,542 @@ +//===-- XML.cpp -------------------------------------------------*- C++ -*-===// +// +// 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 <stdlib.h> /* atof */ + +#include "lldb/Host/Config.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Host/XML.h" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark-- XMLDocument + +XMLDocument::XMLDocument() : m_document(nullptr) {} + +XMLDocument::~XMLDocument() { Clear(); } + +void XMLDocument::Clear() { +#if LLDB_ENABLE_LIBXML2 + if (m_document) { + xmlDocPtr doc = m_document; + m_document = nullptr; + xmlFreeDoc(doc); + } +#endif +} + +bool XMLDocument::IsValid() const { return m_document != nullptr; } + +void XMLDocument::ErrorCallback(void *ctx, const char *format, ...) { + XMLDocument *document = (XMLDocument *)ctx; + va_list args; + va_start(args, format); + document->m_errors.PrintfVarArg(format, args); + document->m_errors.EOL(); + va_end(args); +} + +bool XMLDocument::ParseFile(const char *path) { +#if LLDB_ENABLE_LIBXML2 + Clear(); + xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback); + m_document = xmlParseFile(path); + xmlSetGenericErrorFunc(nullptr, nullptr); +#endif + return IsValid(); +} + +bool XMLDocument::ParseMemory(const char *xml, size_t xml_length, + const char *url) { +#if LLDB_ENABLE_LIBXML2 + Clear(); + xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback); + m_document = xmlReadMemory(xml, (int)xml_length, url, nullptr, 0); + xmlSetGenericErrorFunc(nullptr, nullptr); +#endif + return IsValid(); +} + +XMLNode XMLDocument::GetRootElement(const char *required_name) { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + XMLNode root_node(xmlDocGetRootElement(m_document)); + if (required_name) { + llvm::StringRef actual_name = root_node.GetName(); + if (actual_name == required_name) + return root_node; + } else { + return root_node; + } + } +#endif + return XMLNode(); +} + +llvm::StringRef XMLDocument::GetErrors() const { return m_errors.GetString(); } + +bool XMLDocument::XMLEnabled() { +#if LLDB_ENABLE_LIBXML2 + return true; +#else + return false; +#endif +} + +#pragma mark-- XMLNode + +XMLNode::XMLNode() : m_node(nullptr) {} + +XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {} + +XMLNode::~XMLNode() {} + +void XMLNode::Clear() { m_node = nullptr; } + +XMLNode XMLNode::GetParent() const { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) + return XMLNode(m_node->parent); + else + return XMLNode(); +#else + return XMLNode(); +#endif +} + +XMLNode XMLNode::GetSibling() const { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) + return XMLNode(m_node->next); + else + return XMLNode(); +#else + return XMLNode(); +#endif +} + +XMLNode XMLNode::GetChild() const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) + return XMLNode(m_node->children); + else + return XMLNode(); +#else + return XMLNode(); +#endif +} + +llvm::StringRef XMLNode::GetAttributeValue(const char *name, + const char *fail_value) const { + const char *attr_value = nullptr; +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) + attr_value = (const char *)xmlGetProp(m_node, (const xmlChar *)name); + else + attr_value = fail_value; +#else + attr_value = fail_value; +#endif + if (attr_value) + return llvm::StringRef(attr_value); + else + return llvm::StringRef(); +} + +bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value, + uint64_t fail_value, int base) const { +#if LLDB_ENABLE_LIBXML2 + llvm::StringRef str_value = GetAttributeValue(name, ""); +#else + llvm::StringRef str_value; +#endif + bool success = false; + value = StringConvert::ToUInt64(str_value.data(), fail_value, base, &success); + return success; +} + +void XMLNode::ForEachChildNode(NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) + GetChild().ForEachSiblingNode(callback); +#endif +} + +void XMLNode::ForEachChildElement(NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + XMLNode child = GetChild(); + if (child) + child.ForEachSiblingElement(callback); +#endif +} + +void XMLNode::ForEachChildElementWithName(const char *name, + NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + XMLNode child = GetChild(); + if (child) + child.ForEachSiblingElementWithName(name, callback); +#endif +} + +void XMLNode::ForEachAttribute(AttributeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + for (xmlAttrPtr attr = m_node->properties; attr != nullptr; + attr = attr->next) { + // check if name matches + if (attr->name) { + // check child is a text node + xmlNodePtr child = attr->children; + if (child->type == XML_TEXT_NODE) { + llvm::StringRef attr_value; + if (child->content) + attr_value = llvm::StringRef((const char *)child->content); + if (!callback(llvm::StringRef((const char *)attr->name), attr_value)) + return; + } + } + } + } +#endif +} + +void XMLNode::ForEachSiblingNode(NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + // iterate through all siblings + for (xmlNodePtr node = m_node; node; node = node->next) { + if (!callback(XMLNode(node))) + return; + } + } +#endif +} + +void XMLNode::ForEachSiblingElement(NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + // iterate through all siblings + for (xmlNodePtr node = m_node; node; node = node->next) { + // we are looking for element nodes only + if (node->type != XML_ELEMENT_NODE) + continue; + + if (!callback(XMLNode(node))) + return; + } + } +#endif +} + +void XMLNode::ForEachSiblingElementWithName( + const char *name, NodeCallback const &callback) const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + // iterate through all siblings + for (xmlNodePtr node = m_node; node; node = node->next) { + // we are looking for element nodes only + if (node->type != XML_ELEMENT_NODE) + continue; + + // If name is nullptr, we take all nodes of type "t", else just the ones + // whose name matches + if (name) { + if (strcmp((const char *)node->name, name) != 0) + continue; // Name mismatch, ignore this one + } else { + if (node->name) + continue; // nullptr name specified and this element has a name, + // ignore this one + } + + if (!callback(XMLNode(node))) + return; + } + } +#endif +} + +llvm::StringRef XMLNode::GetName() const { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + if (m_node->name) + return llvm::StringRef((const char *)m_node->name); + } +#endif + return llvm::StringRef(); +} + +bool XMLNode::GetElementText(std::string &text) const { + text.clear(); +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + bool success = false; + if (m_node->type == XML_ELEMENT_NODE) { + // check child is a text node + for (xmlNodePtr node = m_node->children; node != nullptr; + node = node->next) { + if (node->type == XML_TEXT_NODE) { + text.append((const char *)node->content); + success = true; + } + } + } + return success; + } +#endif + return false; +} + +bool XMLNode::GetElementTextAsUnsigned(uint64_t &value, uint64_t fail_value, + int base) const { + bool success = false; +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + std::string text; + if (GetElementText(text)) + value = StringConvert::ToUInt64(text.c_str(), fail_value, base, &success); + } +#endif + if (!success) + value = fail_value; + return success; +} + +bool XMLNode::GetElementTextAsFloat(double &value, double fail_value) const { + bool success = false; +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + std::string text; + if (GetElementText(text)) { + value = atof(text.c_str()); + success = true; + } + } +#endif + if (!success) + value = fail_value; + return success; +} + +bool XMLNode::NameIs(const char *name) const { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + // In case we are looking for a nullptr name or an exact pointer match + if (m_node->name == (const xmlChar *)name) + return true; + if (m_node->name) + return strcmp((const char *)m_node->name, name) == 0; + } +#endif + return false; +} + +XMLNode XMLNode::FindFirstChildElementWithName(const char *name) const { + XMLNode result_node; + +#if LLDB_ENABLE_LIBXML2 + ForEachChildElementWithName( + name, [&result_node](const XMLNode &node) -> bool { + result_node = node; + // Stop iterating, we found the node we wanted + return false; + }); +#endif + + return result_node; +} + +bool XMLNode::IsValid() const { return m_node != nullptr; } + +bool XMLNode::IsElement() const { +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) + return m_node->type == XML_ELEMENT_NODE; +#endif + return false; +} + +XMLNode XMLNode::GetElementForPath(const NamePath &path) { +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + if (path.empty()) + return *this; + else { + XMLNode node = FindFirstChildElementWithName(path[0].c_str()); + const size_t n = path.size(); + for (size_t i = 1; node && i < n; ++i) + node = node.FindFirstChildElementWithName(path[i].c_str()); + return node; + } + } +#endif + + return XMLNode(); +} + +#pragma mark-- ApplePropertyList + +ApplePropertyList::ApplePropertyList() : m_xml_doc(), m_dict_node() {} + +ApplePropertyList::ApplePropertyList(const char *path) + : m_xml_doc(), m_dict_node() { + ParseFile(path); +} + +ApplePropertyList::~ApplePropertyList() {} + +llvm::StringRef ApplePropertyList::GetErrors() const { + return m_xml_doc.GetErrors(); +} + +bool ApplePropertyList::ParseFile(const char *path) { + if (m_xml_doc.ParseFile(path)) { + XMLNode plist = m_xml_doc.GetRootElement("plist"); + if (plist) { + plist.ForEachChildElementWithName("dict", + [this](const XMLNode &dict) -> bool { + this->m_dict_node = dict; + return false; // Stop iterating + }); + return (bool)m_dict_node; + } + } + return false; +} + +bool ApplePropertyList::IsValid() const { return (bool)m_dict_node; } + +bool ApplePropertyList::GetValueAsString(const char *key, + std::string &value) const { + XMLNode value_node = GetValueNode(key); + if (value_node) + return ApplePropertyList::ExtractStringFromValueNode(value_node, value); + return false; +} + +XMLNode ApplePropertyList::GetValueNode(const char *key) const { + XMLNode value_node; +#if LLDB_ENABLE_LIBXML2 + + if (IsValid()) { + m_dict_node.ForEachChildElementWithName( + "key", [key, &value_node](const XMLNode &key_node) -> bool { + std::string key_name; + if (key_node.GetElementText(key_name)) { + if (key_name == key) { + value_node = key_node.GetSibling(); + while (value_node && !value_node.IsElement()) + value_node = value_node.GetSibling(); + return false; // Stop iterating + } + } + return true; // Keep iterating + }); + } +#endif + return value_node; +} + +bool ApplePropertyList::ExtractStringFromValueNode(const XMLNode &node, + std::string &value) { + value.clear(); +#if LLDB_ENABLE_LIBXML2 + if (node.IsValid()) { + llvm::StringRef element_name = node.GetName(); + if (element_name == "true" || element_name == "false") { + // The text value _is_ the element name itself... + value = element_name.str(); + return true; + } else if (element_name == "dict" || element_name == "array") + return false; // dictionaries and arrays have no text value, so we fail + else + return node.GetElementText(value); + } +#endif + return false; +} + +#if LLDB_ENABLE_LIBXML2 + +namespace { + +StructuredData::ObjectSP CreatePlistValue(XMLNode node) { + llvm::StringRef element_name = node.GetName(); + if (element_name == "array") { + std::shared_ptr<StructuredData::Array> array_sp( + new StructuredData::Array()); + node.ForEachChildElement([&array_sp](const XMLNode &node) -> bool { + array_sp->AddItem(CreatePlistValue(node)); + return true; // Keep iterating through all child elements of the array + }); + return array_sp; + } else if (element_name == "dict") { + XMLNode key_node; + std::shared_ptr<StructuredData::Dictionary> dict_sp( + new StructuredData::Dictionary()); + node.ForEachChildElement( + [&key_node, &dict_sp](const XMLNode &node) -> bool { + if (node.NameIs("key")) { + // This is a "key" element node + key_node = node; + } else { + // This is a value node + if (key_node) { + std::string key_name; + key_node.GetElementText(key_name); + dict_sp->AddItem(key_name, CreatePlistValue(node)); + key_node.Clear(); + } + } + return true; // Keep iterating through all child elements of the + // dictionary + }); + return dict_sp; + } else if (element_name == "real") { + double value = 0.0; + node.GetElementTextAsFloat(value); + return StructuredData::ObjectSP(new StructuredData::Float(value)); + } else if (element_name == "integer") { + uint64_t value = 0; + node.GetElementTextAsUnsigned(value, 0, 0); + return StructuredData::ObjectSP(new StructuredData::Integer(value)); + } else if ((element_name == "string") || (element_name == "data") || + (element_name == "date")) { + std::string text; + node.GetElementText(text); + return StructuredData::ObjectSP( + new StructuredData::String(std::move(text))); + } else if (element_name == "true") { + return StructuredData::ObjectSP(new StructuredData::Boolean(true)); + } else if (element_name == "false") { + return StructuredData::ObjectSP(new StructuredData::Boolean(false)); + } + return StructuredData::ObjectSP(new StructuredData::Null()); +} +} +#endif + +StructuredData::ObjectSP ApplePropertyList::GetStructuredData() { + StructuredData::ObjectSP root_sp; +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + return CreatePlistValue(m_dict_node); + } +#endif + return root_sp; +} |