diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Host/common')
34 files changed, 9712 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp b/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp new file mode 100644 index 000000000000..afc770d20d7b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/Alarm.cpp @@ -0,0 +1,222 @@ +//===-- Alarm.cpp ---------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Alarm.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +Alarm::Alarm(Duration timeout, bool run_callback_on_exit) + : m_timeout(timeout), m_run_callbacks_on_exit(run_callback_on_exit) { + StartAlarmThread(); +} + +Alarm::~Alarm() { StopAlarmThread(); } + +Alarm::Handle Alarm::Create(std::function<void()> callback) { + // Gracefully deal with the unlikely event that the alarm thread failed to + // launch. + if (!AlarmThreadRunning()) + return INVALID_HANDLE; + + // Compute the next expiration before we take the lock. This ensures that + // waiting on the lock doesn't eat into the timeout. + const TimePoint expiration = GetNextExpiration(); + + Handle handle = INVALID_HANDLE; + + { + std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); + + // Create a new unique entry and remember its handle. + m_entries.emplace_back(callback, expiration); + handle = m_entries.back().handle; + + // Tell the alarm thread we need to recompute the next alarm. + m_recompute_next_alarm = true; + } + + m_alarm_cv.notify_one(); + return handle; +} + +bool Alarm::Restart(Handle handle) { + // Gracefully deal with the unlikely event that the alarm thread failed to + // launch. + if (!AlarmThreadRunning()) + return false; + + // Compute the next expiration before we take the lock. This ensures that + // waiting on the lock doesn't eat into the timeout. + const TimePoint expiration = GetNextExpiration(); + + { + std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); + + // Find the entry corresponding to the given handle. + const auto it = + std::find_if(m_entries.begin(), m_entries.end(), + [handle](Entry &entry) { return entry.handle == handle; }); + if (it == m_entries.end()) + return false; + + // Update the expiration. + it->expiration = expiration; + + // Tell the alarm thread we need to recompute the next alarm. + m_recompute_next_alarm = true; + } + + m_alarm_cv.notify_one(); + return true; +} + +bool Alarm::Cancel(Handle handle) { + // Gracefully deal with the unlikely event that the alarm thread failed to + // launch. + if (!AlarmThreadRunning()) + return false; + + { + std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); + + const auto it = + std::find_if(m_entries.begin(), m_entries.end(), + [handle](Entry &entry) { return entry.handle == handle; }); + + if (it == m_entries.end()) + return false; + + m_entries.erase(it); + } + + // No need to notify the alarm thread. This only affects the alarm thread if + // we removed the entry that corresponds to the next alarm. If that's the + // case, the thread will wake up as scheduled, find no expired events, and + // recompute the next alarm time. + return true; +} + +Alarm::Entry::Entry(Alarm::Callback callback, Alarm::TimePoint expiration) + : handle(Alarm::GetNextUniqueHandle()), callback(std::move(callback)), + expiration(std::move(expiration)) {} + +void Alarm::StartAlarmThread() { + if (!m_alarm_thread.IsJoinable()) { + llvm::Expected<HostThread> alarm_thread = ThreadLauncher::LaunchThread( + "lldb.debugger.alarm-thread", [this] { return AlarmThread(); }, + 8 * 1024 * 1024); // Use larger 8MB stack for this thread + if (alarm_thread) { + m_alarm_thread = *alarm_thread; + } else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), alarm_thread.takeError(), + "failed to launch host thread: {0}"); + } + } +} + +void Alarm::StopAlarmThread() { + if (m_alarm_thread.IsJoinable()) { + { + std::lock_guard<std::mutex> alarm_guard(m_alarm_mutex); + m_exit = true; + } + m_alarm_cv.notify_one(); + m_alarm_thread.Join(nullptr); + } +} + +bool Alarm::AlarmThreadRunning() { return m_alarm_thread.IsJoinable(); } + +lldb::thread_result_t Alarm::AlarmThread() { + bool exit = false; + std::optional<TimePoint> next_alarm; + + const auto predicate = [this] { return m_exit || m_recompute_next_alarm; }; + + while (!exit) { + // Synchronization between the main thread and the alarm thread using a + // mutex and condition variable. There are 2 reasons the thread can wake up: + // + // 1. The timeout for the next alarm expired. + // + // 2. The condition variable is notified that one of our shared variables + // (see predicate) was modified. Either the thread is asked to shut down + // or a new alarm came in and we need to recompute the next timeout. + // + // Below we only deal with the timeout expiring and fall through for dealing + // with the rest. + llvm::SmallVector<Callback, 1> callbacks; + { + std::unique_lock<std::mutex> alarm_lock(m_alarm_mutex); + if (next_alarm) { + if (!m_alarm_cv.wait_until(alarm_lock, *next_alarm, predicate)) { + // The timeout for the next alarm expired. + + // Clear the next timeout to signal that we need to recompute the next + // timeout. + next_alarm.reset(); + + // Iterate over all the callbacks. Call the ones that have expired + // and remove them from the list. + const TimePoint now = std::chrono::system_clock::now(); + auto it = m_entries.begin(); + while (it != m_entries.end()) { + if (it->expiration <= now) { + callbacks.emplace_back(std::move(it->callback)); + it = m_entries.erase(it); + } else { + it++; + } + } + } + } else { + m_alarm_cv.wait(alarm_lock, predicate); + } + + // Fall through after waiting on the condition variable. At this point + // either the predicate is true or we woke up because an alarm expired. + + // The alarm thread is shutting down. + if (m_exit) { + exit = true; + if (m_run_callbacks_on_exit) { + for (Entry &entry : m_entries) + callbacks.emplace_back(std::move(entry.callback)); + } + } + + // A new alarm was added or an alarm expired. Either way we need to + // recompute when this thread should wake up for the next alarm. + if (m_recompute_next_alarm || !next_alarm) { + for (Entry &entry : m_entries) { + if (!next_alarm || entry.expiration < *next_alarm) + next_alarm = entry.expiration; + } + m_recompute_next_alarm = false; + } + } + + // Outside the lock, call the callbacks. + for (Callback &callback : callbacks) + callback(); + } + return {}; +} + +Alarm::TimePoint Alarm::GetNextExpiration() const { + return std::chrono::system_clock::now() + m_timeout; +} + +Alarm::Handle Alarm::GetNextUniqueHandle() { + static std::atomic<Handle> g_next_handle = 1; + return g_next_handle++; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/Editline.cpp b/contrib/llvm-project/lldb/source/Host/common/Editline.cpp new file mode 100644 index 000000000000..561ec228cdb2 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/Editline.cpp @@ -0,0 +1,1603 @@ +//===-- Editline.cpp ------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <climits> +#include <iomanip> +#include <optional> + +#include "lldb/Host/Editline.h" + +#include "lldb/Host/ConnectionFileDescriptor.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/Locale.h" +#include "llvm/Support/Threading.h" + +using namespace lldb_private; +using namespace lldb_private::line_editor; + +// 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. + +/// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf +#define ESCAPE "\x1b" +#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 size_t ColumnWidth(llvm::StringRef str) { + return llvm::sys::locale::columnWidth(str); +} + +static int GetOperation(HistoryOperation op) { + // The naming used by editline for the history operations is counter + // intuitive to how it's used in LLDB's editline implementation. + // + // - The H_LAST returns the oldest entry in the history. + // + // - The H_PREV operation returns the previous element in the history, which + // is newer than the current one. + // + // - The H_CURR returns the current entry in the history. + // + // - The H_NEXT operation returns the next element in the history, which is + // older than the current one. + // + // - The H_FIRST returns the most recent entry in the history. + // + // The naming of the enum entries match the semantic meaning. + switch(op) { + case HistoryOperation::Oldest: + return H_LAST; + case HistoryOperation::Older: + return H_NEXT; + case HistoryOperation::Current: + return H_CURR; + case HistoryOperation::Newer: + return H_PREV; + case HistoryOperation::Newest: + return H_FIRST; + } + 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.push_back(input.substr(start)); + break; + } + result.push_back(input.substr(start, end - start)); + start = end + 1; + } + // Treat an empty history session as a single command of zero-length instead + // of returning an empty vector. + if (result.empty()) { + result.emplace_back(); + } + 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_prefix(prefix) { + 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; + FileSystem::Instance().GetHomeDirectory(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 = std::string(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: + /// The history object. + HistoryW *m_history = nullptr; + /// The history event needed to contain all history events. + HistEventW m_event; + /// The prefix name (usually the editline program name) to use when + /// loading/saving history. + std::string m_prefix; + /// Path to the history file. + std::string m_path; +}; +} +} + +// Editline private methods + +void Editline::SetBaseLineNumber(int line_number) { + m_base_line_number = line_number; + m_line_number_digits = + std::max<int>(3, std::to_string(line_number).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 + const size_t prompt_width = ColumnWidth(prompt); + const size_t cont_prompt_width = ColumnWidth(continuation_prompt); + const size_t padded_prompt_width = + std::max(prompt_width, cont_prompt_width); + if (prompt_width < padded_prompt_width) + prompt += std::string(padded_prompt_width - prompt_width, ' '); + else if (cont_prompt_width < padded_prompt_width) + continuation_prompt += + std::string(padded_prompt_width - cont_prompt_width, ' '); + } + + 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::string(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); +} + +size_t Editline::GetPromptWidth() { return ColumnWidth(PromptForIndex(0)); } + +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(); + for (int index = firstIndex; index < line_count; index++) { + fprintf(m_output_file, + "%s" + "%s" + "%s" EditLineStringFormatSpec " ", + m_prompt_ansi_prefix.c_str(), PromptForIndex(index).c_str(), + m_prompt_ansi_suffix.c_str(), m_input_lines[index].c_str()); + if (index < line_count - 1) + fprintf(m_output_file, "\n"); + } +} + +int Editline::CountRowsForLine(const EditLineStringType &content) { + std::string prompt = + PromptForIndex(0); // Prompt width is constant during an edit session + int line_length = (int)(content.length() + ColumnWidth(prompt)); + 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 ANSI formatted 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", + m_prompt_ansi_prefix.c_str(), Prompt(), + m_prompt_ansi_suffix.c_str()); + 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; + + if (m_terminal_size_has_changed) + ApplyTerminalSizeChange(); + + // 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, std::nullopt, 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, std::nullopt, 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_prompt_ansi_prefix.empty() || !m_prompt_ansi_suffix.empty()) + 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); + 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)) { + 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_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); + + // 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"); + } +} + +void Editline::DisplayCompletions( + Editline &editline, llvm::ArrayRef<CompletionResult::Completion> results) { + assert(!results.empty()); + + fprintf(editline.m_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(editline.m_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(editline.m_output_file, results.slice(cur_pos, next_size), + max_len); + + cur_pos += next_size; + + if (cur_pos >= results.size()) + break; + + fprintf(editline.m_output_file, "More (Y/n/a): "); + // The type for the output and the type for the parameter are different, + // to allow interoperability with older versions of libedit. The container + // for the reply must be as wide as what our implementation is using, + // but libedit may use a narrower type depending on the build + // configuration. + EditLineGetCharType reply = L'n'; + int got_char = el_wgetc(editline.m_editline, + reinterpret_cast<EditLineCharType *>(&reply)); + // Check for a ^C or other interruption. + if (editline.m_editor_status == EditorStatus::Interrupted) { + editline.m_editor_status = EditorStatus::Editing; + fprintf(editline.m_output_file, "^C\n"); + break; + } + + fprintf(editline.m_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) + 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); + + 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(); + // Terminate the current argument with a quote if it started with a quote. + Args &parsedLine = request.GetParsedLine(); + if (!parsedLine.empty() && request.GetCursorIndex() < parsedLine.size() && + request.GetParsedArg().IsQuoted()) { + to_add.push_back(request.GetParsedArg().GetQuoteChar()); + } + to_add.push_back(' '); + el_deletestr(m_editline, request.GetCursorArgumentPrefix().size()); + el_insertstr(m_editline, to_add.c_str()); + // Clear all the autosuggestion parts if the only single space can be completed. + if (to_add == " ") + return CC_REDISPLAY; + return CC_REFRESH; + } + 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(*this, results); + + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); + return CC_REDISPLAY; +} + +unsigned char Editline::ApplyAutosuggestCommand(int ch) { + if (!m_suggestion_callback) { + return CC_REDISPLAY; + } + + const LineInfo *line_info = el_line(m_editline); + llvm::StringRef line(line_info->buffer, + line_info->lastchar - line_info->buffer); + + if (std::optional<std::string> to_add = m_suggestion_callback(line)) + el_insertstr(m_editline, to_add->c_str()); + + return CC_REDISPLAY; +} + +unsigned char Editline::TypedCharacter(int ch) { + std::string typed = std::string(1, ch); + el_insertstr(m_editline, typed.c_str()); + + if (!m_suggestion_callback) { + return CC_REDISPLAY; + } + + const LineInfo *line_info = el_line(m_editline); + llvm::StringRef line(line_info->buffer, + line_info->lastchar - line_info->buffer); + + if (std::optional<std::string> to_add = m_suggestion_callback(line)) { + std::string to_add_color = + m_suggestion_ansi_prefix + to_add.value() + m_suggestion_ansi_suffix; + fputs(typed.c_str(), m_output_file); + fputs(to_add_color.c_str(), m_output_file); + size_t new_autosuggestion_size = line.size() + to_add->length(); + // Print spaces to hide any remains of a previous longer autosuggestion. + if (new_autosuggestion_size < m_previous_autosuggestion_size) { + size_t spaces_to_print = + m_previous_autosuggestion_size - new_autosuggestion_size; + std::string spaces = std::string(spaces_to_print, ' '); + fputs(spaces.c_str(), m_output_file); + } + m_previous_autosuggestion_size = new_autosuggestion_size; + + int editline_cursor_position = + (int)((line_info->cursor - line_info->buffer) + GetPromptWidth()); + int editline_cursor_row = editline_cursor_position / m_terminal_width; + int toColumn = + editline_cursor_position - (editline_cursor_row * m_terminal_width); + fprintf(m_output_file, ANSI_SET_COLUMN_N, toColumn); + return CC_REFRESH; + } + + return CC_REDISPLAY; +} + +void Editline::AddFunctionToEditLine(const EditLineCharType *command, + const EditLineCharType *helptext, + EditlineCommandCallbackType callbackFn) { + el_wset(m_editline, EL_ADDFN, command, helptext, callbackFn); +} + +void Editline::SetEditLinePromptCallback( + EditlinePromptCallbackType callbackFn) { + el_set(m_editline, EL_PROMPT, callbackFn); +} + +void Editline::SetGetCharacterFunction(EditlineGetCharCallbackType callbackFn) { + el_wset(m_editline, EL_GETCFN, callbackFn); +} + +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); + ApplyTerminalSizeChange(); + + 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"); + + SetGetCharacterFunction([](EditLine *editline, EditLineGetCharType *c) { + return Editline::InstanceFor(editline)->GetCharacter(c); + }); + + SetEditLinePromptCallback([](EditLine *editline) { + return Editline::InstanceFor(editline)->Prompt(); + }); + + // Commands used for multiline support, registered whether or not they're + // used + AddFunctionToEditLine( + EditLineConstString("lldb-break-line"), + EditLineConstString("Insert a line break"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BreakLineCommand(ch); + }); + + AddFunctionToEditLine( + EditLineConstString("lldb-end-or-add-line"), + EditLineConstString("End editing or continue when incomplete"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->EndOrAddLineCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-delete-next-char"), + EditLineConstString("Delete next character"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeleteNextCharCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-delete-previous-char"), + EditLineConstString("Delete previous character"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->DeletePreviousCharCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-previous-line"), + EditLineConstString("Move to previous line"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousLineCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-next-line"), + EditLineConstString("Move to next line"), [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextLineCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-previous-history"), + EditLineConstString("Move to previous history"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->PreviousHistoryCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-next-history"), + EditLineConstString("Move to next history"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->NextHistoryCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-buffer-start"), + EditLineConstString("Move to start of buffer"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferStartCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-buffer-end"), + EditLineConstString("Move to end of buffer"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->BufferEndCommand(ch); + }); + AddFunctionToEditLine( + EditLineConstString("lldb-fix-indentation"), + EditLineConstString("Fix line indentation"), + [](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); + }; + AddFunctionToEditLine(EditLineConstString("lldb-complete"), + EditLineConstString("Invoke completion"), + complete_callback); + AddFunctionToEditLine(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 + + if (m_suggestion_callback) { + AddFunctionToEditLine( + EditLineConstString("lldb-apply-complete"), + EditLineConstString("Adopt autocompletion"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->ApplyAutosuggestCommand(ch); + }); + + el_set(m_editline, EL_BIND, "^f", "lldb-apply-complete", + NULL); // Apply a part that is suggested automatically + + AddFunctionToEditLine( + EditLineConstString("lldb-typed-character"), + EditLineConstString("Typed character"), + [](EditLine *editline, int ch) { + return Editline::InstanceFor(editline)->TypedCharacter(ch); + }); + + char bind_key[2] = {0, 0}; + llvm::StringRef ascii_chars = + "abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXZY1234567890!\"#$%" + "&'()*+,./:;<=>?@[]_`{|}~ "; + for (char c : ascii_chars) { + bind_key[0] = c; + el_set(m_editline, EL_BIND, bind_key, "lldb-typed-character", NULL); + } + el_set(m_editline, EL_BIND, "\\-", "lldb-typed-character", NULL); + el_set(m_editline, EL_BIND, "\\^", "lldb-typed-character", NULL); + el_set(m_editline, EL_BIND, "\\\\", "lldb-typed-character", NULL); + } + } + + 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 + AddFunctionToEditLine( + EditLineConstString("lldb-revert-line"), + EditLineConstString("Revert line to saved state"), + [](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, + std::recursive_mutex &output_mutex) + : m_editor_status(EditorStatus::Complete), m_input_file(input_file), + m_output_file(output_file), m_error_file(error_file), + m_input_connection(fileno(input_file), false), + m_output_mutex(output_mutex) { + // Get a shared history instance + m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; + m_history_sp = EditlineHistory::GetHistory(m_editor_name); +} + +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() { m_terminal_size_has_changed = 1; } + +void Editline::ApplyTerminalSizeChange() { + if (!m_editline) + return; + + m_terminal_size_has_changed = 0; + 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::recursive_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::recursive_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; +} + +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::recursive_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::recursive_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. Don't save empty + // input as that just clutters the command history. + if (!m_input_lines.empty()) + 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::recursive_mutex> guard(m_output_mutex); + if (m_editor_status == EditorStatus::Editing) { + SaveEditedLine(); + 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 != (EditLineGetCharType)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/contrib/llvm-project/lldb/source/Host/common/File.cpp b/contrib/llvm-project/lldb/source/Host/common/File.cpp new file mode 100644 index 000000000000..174feecf2de0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/File.cpp @@ -0,0 +1,907 @@ +//===-- File.cpp ----------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/File.h" + +#include <cerrno> +#include <climits> +#include <cstdarg> +#include <cstdio> +#include <fcntl.h> +#include <optional> + +#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 "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" +#include "lldb/Utility/VASPrintf.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Process.h" + +using namespace lldb; +using namespace lldb_private; +using llvm::Expected; + +Expected<const char *> +File::GetStreamOpenModeFromOptions(File::OpenOptions options) { + File::OpenOptions rw = + options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | + File::eOpenOptionReadWrite); + + if (options & File::eOpenOptionAppend) { + if (rw == File::eOpenOptionReadWrite) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "a+x"; + else + return "a+"; + } else if (rw == File::eOpenOptionWriteOnly) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "ax"; + else + return "a"; + } + } else if (rw == File::eOpenOptionReadWrite) { + if (options & File::eOpenOptionCanCreate) { + if (options & File::eOpenOptionCanCreateNewOnly) + return "w+x"; + else + return "w+"; + } else + return "r+"; + } else if (rw == File::eOpenOptionWriteOnly) { + return "w"; + } else if (rw == File::eOpenOptionReadOnly) { + return "r"; + } + 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", eOpenOptionReadOnly) + .Cases("w", "wb", eOpenOptionWriteOnly) + .Cases("a", "ab", + eOpenOptionWriteOnly | eOpenOptionAppend | + eOpenOptionCanCreate) + .Cases("r+", "rb+", "r+b", eOpenOptionReadWrite) + .Cases("w+", "wb+", "w+b", + eOpenOptionReadWrite | eOpenOptionCanCreate | + eOpenOptionTruncate) + .Cases("a+", "ab+", "a+b", + eOpenOptionReadWrite | eOpenOptionAppend | + eOpenOptionCanCreate) + .Default(eOpenOptionInvalid); + if (opts != eOpenOptionInvalid) + 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) { + llvm::SmallString<0> s; + if (VASprintf(s, format, args)) { + size_t written = s.size(); + Write(s.data(), written); + return written; + } + return 0; +} + +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); +} + +bool NativeFile::IsValid() const { + std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); + return DescriptorIsValidUnlocked() || StreamIsValidUnlocked(); +} + +Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; } + +int NativeFile::GetDescriptor() const { + if (ValueGuard descriptor_guard = 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 (ValueGuard stream_guard = 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() { + ValueGuard stream_guard = StreamIsValid(); + if (!stream_guard) { + if (ValueGuard descriptor_guard = 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(m_descriptor); +#else + m_descriptor = dup(m_descriptor); +#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() { + std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); + + Status error; + + if (StreamIsValidUnlocked()) { + if (m_own_stream) { + if (::fclose(m_stream) == EOF) + error.SetErrorToErrno(); + } else { + File::OpenOptions rw = + m_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | + File::eOpenOptionReadWrite); + + if (rw == eOpenOptionWriteOnly || rw == eOpenOptionReadWrite) { + if (::fflush(m_stream) == EOF) + error.SetErrorToErrno(); + } + } + } + + if (DescriptorIsValidUnlocked() && m_own_descriptor) { + if (::close(m_descriptor) != 0) + error.SetErrorToErrno(); + } + + m_stream = kInvalidStream; + m_own_stream = false; + m_descriptor = kInvalidDescriptor; + m_own_descriptor = false; + m_options = OpenOptions(0); + 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 (ValueGuard descriptor_guard = DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_SET); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + return result; + } + + if (ValueGuard stream_guard = StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_SET); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + return result; + } + + 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 (ValueGuard descriptor_guard = DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_CUR); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + return result; + } + + if (ValueGuard stream_guard = StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_CUR); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + return result; + } + + 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 (ValueGuard descriptor_guard = DescriptorIsValid()) { + result = ::lseek(m_descriptor, offset, SEEK_END); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + return result; + } + + if (ValueGuard stream_guard = StreamIsValid()) { + result = ::fseek(m_stream, offset, SEEK_END); + + if (error_ptr) { + if (result == -1) + error_ptr->SetErrorToErrno(); + else + error_ptr->Clear(); + } + } + + if (error_ptr) + error_ptr->SetErrorString("invalid file handle"); + return result; +} + +Status NativeFile::Flush() { + Status error; + if (ValueGuard stream_guard = StreamIsValid()) { + if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) + error.SetErrorToErrno(); + return error; + } + + { + ValueGuard descriptor_guard = DescriptorIsValid(); + if (!descriptor_guard) + error.SetErrorString("invalid file handle"); + } + return error; +} + +Status NativeFile::Sync() { + Status error; + if (ValueGuard descriptor_guard = 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 (ValueGuard descriptor_guard = 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; + return error; + } + + if (ValueGuard file_lock = 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; + return error; + } + + 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 (ValueGuard descriptor_guard = 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; + return error; + } + + if (ValueGuard stream_guard = 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; + return error; + } + + 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; + File::OpenOptions rw = + open_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | + File::eOpenOptionReadWrite); + if (rw == eOpenOptionReadWrite) + mode |= O_RDWR; + else if (rw == eOpenOptionWriteOnly) + mode |= O_WRONLY; + else if (rw == eOpenOptionReadOnly) + mode |= O_RDONLY; + + 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; +} + +llvm::Expected<SerialPort::Options> +SerialPort::OptionsFromURL(llvm::StringRef urlqs) { + SerialPort::Options serial_options; + for (llvm::StringRef x : llvm::split(urlqs, '&')) { + if (x.consume_front("baud=")) { + unsigned int baud_rate; + if (!llvm::to_integer(x, baud_rate, 10)) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid baud rate: %s", + x.str().c_str()); + serial_options.BaudRate = baud_rate; + } else if (x.consume_front("parity=")) { + serial_options.Parity = + llvm::StringSwitch<std::optional<Terminal::Parity>>(x) + .Case("no", Terminal::Parity::No) + .Case("even", Terminal::Parity::Even) + .Case("odd", Terminal::Parity::Odd) + .Case("mark", Terminal::Parity::Mark) + .Case("space", Terminal::Parity::Space) + .Default(std::nullopt); + if (!serial_options.Parity) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Invalid parity (must be no, even, odd, mark or space): %s", + x.str().c_str()); + } else if (x.consume_front("parity-check=")) { + serial_options.ParityCheck = + llvm::StringSwitch<std::optional<Terminal::ParityCheck>>(x) + .Case("no", Terminal::ParityCheck::No) + .Case("replace", Terminal::ParityCheck::ReplaceWithNUL) + .Case("ignore", Terminal::ParityCheck::Ignore) + // "mark" mode is not currently supported as it requires special + // input processing + // .Case("mark", Terminal::ParityCheck::Mark) + .Default(std::nullopt); + if (!serial_options.ParityCheck) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Invalid parity-check (must be no, replace, ignore or mark): %s", + x.str().c_str()); + } else if (x.consume_front("stop-bits=")) { + unsigned int stop_bits; + if (!llvm::to_integer(x, stop_bits, 10) || + (stop_bits != 1 && stop_bits != 2)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Invalid stop bit number (must be 1 or 2): %s", x.str().c_str()); + serial_options.StopBits = stop_bits; + } else + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unknown parameter: %s", x.str().c_str()); + } + return serial_options; +} + +llvm::Expected<std::unique_ptr<SerialPort>> +SerialPort::Create(int fd, OpenOptions options, Options serial_options, + bool transfer_ownership) { + std::unique_ptr<SerialPort> out{ + new SerialPort(fd, options, serial_options, transfer_ownership)}; + + if (!out->GetIsInteractive()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "the specified file is not a teletype"); + + Terminal term{fd}; + if (llvm::Error error = term.SetRaw()) + return std::move(error); + if (serial_options.BaudRate) { + if (llvm::Error error = term.SetBaudRate(*serial_options.BaudRate)) + return std::move(error); + } + if (serial_options.Parity) { + if (llvm::Error error = term.SetParity(*serial_options.Parity)) + return std::move(error); + } + if (serial_options.ParityCheck) { + if (llvm::Error error = term.SetParityCheck(*serial_options.ParityCheck)) + return std::move(error); + } + if (serial_options.StopBits) { + if (llvm::Error error = term.SetStopBits(*serial_options.StopBits)) + return std::move(error); + } + + return std::move(out); +} + +SerialPort::SerialPort(int fd, OpenOptions options, + SerialPort::Options serial_options, + bool transfer_ownership) + : NativeFile(fd, options, transfer_ownership), m_state(fd) {} + +Status SerialPort::Close() { + m_state.Restore(); + return NativeFile::Close(); +} + +char File::ID = 0; +char NativeFile::ID = 0; +char SerialPort::ID = 0; diff --git a/contrib/llvm-project/lldb/source/Host/common/FileAction.cpp b/contrib/llvm-project/lldb/source/Host/common/FileAction.cpp new file mode 100644 index 000000000000..f980d3224640 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/FileAction.cpp @@ -0,0 +1,89 @@ +//===-- FileAction.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <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_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.GetPathAsConstString().AsCString(); +} + +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.GetPath().c_str(), m_arg); + break; + } +} diff --git a/contrib/llvm-project/lldb/source/Host/common/FileCache.cpp b/contrib/llvm-project/lldb/source/Host/common/FileCache.cpp new file mode 100644 index 000000000000..da9a748e2f13 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/FileCache.cpp @@ -0,0 +1,114 @@ +//===-- FileCache.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/FileSystem.cpp b/contrib/llvm-project/lldb/source/Host/common/FileSystem.cpp new file mode 100644 index 000000000000..5153a0a9ec51 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/FileSystem.cpp @@ -0,0 +1,470 @@ +//===-- FileSystem.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/FileSystem.h" + +#include "lldb/Utility/DataBufferLLVM.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 <cerrno> +#include <climits> +#include <cstdarg> +#include <cstdio> +#include <fcntl.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 <optional> +#include <vector> + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +FileSystem &FileSystem::Instance() { return *InstanceImpl(); } + +void FileSystem::Terminate() { + lldbassert(InstanceImpl() && "Already terminated."); + InstanceImpl().reset(); +} + +std::optional<FileSystem> &FileSystem::InstanceImpl() { + static std::optional<FileSystem> g_fs; + return g_fs; +} + +vfs::directory_iterator FileSystem::DirBegin(const FileSpec &file_spec, + std::error_code &ec) { + if (!file_spec) { + ec = std::error_code(static_cast<int>(errc::no_such_file_or_directory), + std::system_category()); + return {}; + } + 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 { + if (!file_spec) + return std::error_code(static_cast<int>(errc::no_such_file_or_directory), + std::system_category()); + 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 { + if (!file_spec) + return sys::TimePoint<>(); + 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 { + if (!file_spec) + return 0; + 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 { + if (!file_spec) + return sys::fs::perms::perms_not_known; + 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 file_spec && 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 file_spec && 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 file_spec && 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 file_spec && 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) + continue; + 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()); + assert(m_tilde_resolver && "must initialize tilde resolver in constructor"); + m_tilde_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) { + if (!file_spec) + return; + + // 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.SetDirectory(path); + else + file_spec.SetPath(path); +} + +template <typename T> +static std::unique_ptr<T> GetMemoryBuffer(const llvm::Twine &path, + uint64_t size, uint64_t offset, + bool is_volatile) { + std::unique_ptr<T> buffer; + if (size == 0) { + auto buffer_or_error = T::getFile(path, is_volatile); + if (!buffer_or_error) + return nullptr; + buffer = std::move(*buffer_or_error); + } else { + auto buffer_or_error = T::getFileSlice(path, size, offset, is_volatile); + if (!buffer_or_error) + return nullptr; + buffer = std::move(*buffer_or_error); + } + return buffer; +} + +std::shared_ptr<WritableDataBuffer> +FileSystem::CreateWritableDataBuffer(const llvm::Twine &path, uint64_t size, + uint64_t offset) { + const bool is_volatile = !IsLocal(path); + auto buffer = GetMemoryBuffer<llvm::WritableMemoryBuffer>(path, size, offset, + is_volatile); + if (!buffer) + return {}; + return std::shared_ptr<WritableDataBufferLLVM>( + new WritableDataBufferLLVM(std::move(buffer))); +} + +std::shared_ptr<DataBuffer> +FileSystem::CreateDataBuffer(const llvm::Twine &path, uint64_t size, + uint64_t offset) { + const bool is_volatile = !IsLocal(path); + auto buffer = + GetMemoryBuffer<llvm::MemoryBuffer>(path, size, offset, is_volatile); + if (!buffer) + return {}; + return std::shared_ptr<DataBufferLLVM>(new DataBufferLLVM(std::move(buffer))); +} + +std::shared_ptr<WritableDataBuffer> +FileSystem::CreateWritableDataBuffer(const FileSpec &file_spec, uint64_t size, + uint64_t offset) { + return CreateWritableDataBuffer(file_spec.GetPath(), size, offset); +} + +std::shared_ptr<DataBuffer> +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; +} + +bool FileSystem::GetHomeDirectory(SmallVectorImpl<char> &path) const { + if (!m_home_directory.empty()) { + path.assign(m_home_directory.begin(), m_home_directory.end()); + return true; + } + return llvm::sys::path::home_directory(path); +} + +bool FileSystem::GetHomeDirectory(FileSpec &file_spec) const { + SmallString<128> home_dir; + if (!GetHomeDirectory(home_dir)) + return false; + file_spec.SetPath(home_dir); + 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(File::OpenOptions options) { + int open_flags = 0; + File::OpenOptions rw = + options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | + File::eOpenOptionReadWrite); + if (rw == File::eOpenOptionWriteOnly || rw == File::eOpenOptionReadWrite) { + if (rw == File::eOpenOptionReadWrite) + 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 (rw == File::eOpenOptionReadOnly) { + 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) { + const int open_flags = GetOpenFlags(options); + const mode_t open_mode = + (open_flags & O_CREAT) ? GetOpenMode(permissions) : 0; + + auto path = file_spec.GetPath(); + + 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); +} + +void FileSystem::SetHomeDirectory(std::string home_directory) { + m_home_directory = std::move(home_directory); +} + +Status FileSystem::RemoveFile(const FileSpec &file_spec) { + return RemoveFile(file_spec.GetPath()); +} + +Status FileSystem::RemoveFile(const llvm::Twine &path) { + return Status(llvm::sys::fs::remove(path)); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/GetOptInc.cpp b/contrib/llvm-project/lldb/source/Host/common/GetOptInc.cpp new file mode 100644 index 000000000000..c2044b687322 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/GetOptInc.cpp @@ -0,0 +1,451 @@ +//===-- GetOptInc.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/common/GetOptInc.h" + +#if defined(REPLACE_GETOPT) || defined(REPLACE_GETOPT_LONG) || \ + defined(REPLACE_GETOPT_LONG_ONLY) + +// getopt.cpp +#include <cerrno> +#include <cstdlib> +#include <cstring> + +#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/contrib/llvm-project/lldb/source/Host/common/Host.cpp b/contrib/llvm-project/lldb/source/Host/common/Host.cpp new file mode 100644 index 000000000000..06ccc0e2b342 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/Host.cpp @@ -0,0 +1,655 @@ +//===-- Host.cpp ----------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// C includes +#include <cerrno> +#include <climits> +#include <cstdlib> +#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__) || defined(__EMSCRIPTEN__) +#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/FileSpec.h" +#include "lldb/Utility/LLDBLog.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/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__) +#if !defined(_WIN32) +#include <syslog.h> +void Host::SystemLog(Severity severity, llvm::StringRef message) { + static llvm::once_flag g_openlog_once; + llvm::call_once(g_openlog_once, [] { + openlog("lldb", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_USER); + }); + int level = LOG_DEBUG; + switch (severity) { + case lldb::eSeverityInfo: + level = LOG_INFO; + break; + case lldb::eSeverityWarning: + level = LOG_WARNING; + break; + case lldb::eSeverityError: + level = LOG_ERR; + break; + } + syslog(level, "%s", message.data()); +} +#else +void Host::SystemLog(Severity severity, llvm::StringRef message) { + switch (severity) { + case lldb::eSeverityInfo: + case lldb::eSeverityWarning: + llvm::outs() << message; + break; + case lldb::eSeverityError: + llvm::errs() << message; + break; + } +} +#endif +#endif + +#if !defined(__APPLE__) && !defined(_WIN32) +static thread_result_t +MonitorChildProcessThreadFunction(::pid_t pid, + Host::MonitorChildProcessCallback callback); + +llvm::Expected<HostThread> Host::StartMonitoringChildProcess( + const Host::MonitorChildProcessCallback &callback, lldb::pid_t pid) { + char thread_name[256]; + ::snprintf(thread_name, sizeof(thread_name), + "<lldb.host.wait4(pid=%" PRIu64 ")>", pid); + assert(pid <= UINT32_MAX); + return ThreadLauncher::LaunchThread(thread_name, [pid, callback] { + return MonitorChildProcessThreadFunction(pid, callback); + }); +} + +#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__ +static thread_local volatile sig_atomic_t g_usr1_called; + +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(::pid_t pid, + Host::MonitorChildProcessCallback callback) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "pid = {0}", pid); + + int status = -1; + +#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 (true) { + log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "::waitpid({0}, &status, 0)...", pid); + + if (CheckForMonitorCancellation()) + return nullptr; + + const ::pid_t wait_pid = ::waitpid(pid, &status, 0); + + LLDB_LOG(log, "::waitpid({0}, &status, 0) => pid = {1}, status = {2:x}", pid, + wait_pid, status); + + if (CheckForMonitorCancellation()) + return nullptr; + + if (wait_pid != -1) + break; + if (errno != EINTR) { + LLDB_LOG(log, "pid = {0}, thread exiting because waitpid failed ({1})...", + pid, llvm::sys::StrError()); + return nullptr; + } + } + + int signal = 0; + int exit_status = 0; + if (WIFEXITED(status)) { + exit_status = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + signal = WTERMSIG(status); + exit_status = -1; + } else { + llvm_unreachable("Unknown status"); + } + + // Scope for pthread_cancel_disabler + { +#ifndef __linux__ + ScopedPThreadCancelDisabler pthread_cancel_disabler; +#endif + + if (callback) + callback(pid, signal, exit_status); + } + + LLDB_LOG(GetLog(LLDBLog::Process), "pid = {0} thread exiting...", pid); + return nullptr; +} + +#endif // #if !defined (__APPLE__) && !defined (_WIN32) + +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) {} + + lldb_private::Predicate<bool> process_reaped; + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + int signo = -1; + int status = -1; +}; + +static void +MonitorShellCommand(std::shared_ptr<ShellInfo> shell_info, lldb::pid_t pid, + 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); +} + +Status Host::RunShellCommand(llvm::StringRef command, + const FileSpec &working_dir, int *status_ptr, + int *signo_ptr, std::string *command_output_ptr, + const Timeout<std::micro> &timeout, + bool run_in_shell, bool hide_stderr) { + return RunShellCommand(llvm::StringRef(), Args(command), working_dir, + status_ptr, signo_ptr, command_output_ptr, timeout, + run_in_shell, hide_stderr); +} + +Status Host::RunShellCommand(llvm::StringRef shell_path, + llvm::StringRef command, + const FileSpec &working_dir, int *status_ptr, + int *signo_ptr, std::string *command_output_ptr, + const Timeout<std::micro> &timeout, + bool run_in_shell, bool hide_stderr) { + return RunShellCommand(shell_path, Args(command), working_dir, status_ptr, + signo_ptr, command_output_ptr, timeout, run_in_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_shell, bool hide_stderr) { + return RunShellCommand(llvm::StringRef(), args, working_dir, status_ptr, + signo_ptr, command_output_ptr, timeout, run_in_shell, + hide_stderr); +} + +Status Host::RunShellCommand(llvm::StringRef shell_path, 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_shell, bool hide_stderr) { + Status error; + ProcessLaunchInfo launch_info; + launch_info.SetArchitecture(HostInfo::GetArchitecture()); + if (run_in_shell) { + // Run the command in a shell + FileSpec shell = HostInfo::GetDefaultShell(); + if (!shell_path.empty()) + shell.SetPath(shell_path); + + launch_info.SetShell(shell); + launch_info.GetArguments().AppendArguments(args); + const bool will_debug = false; + const bool first_arg_is_full_shell_command = false; + launch_info.ConvertArgumentsForLaunchingInShell( + error, 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); + } + + launch_info.GetEnvironment() = Host::GetEnvironment(); + + 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.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()); + launch_info.SetMonitorProcessCallback( + std::bind(MonitorShellCommand, shell_info_sp, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + + 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 { + WritableDataBufferSP Buffer = + FileSystem::Instance().CreateWritableDataBuffer( + output_file_spec); + if (error.Success()) + command_output_ptr->assign( + reinterpret_cast<char *>(Buffer->GetBytes()), + 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__) +llvm::Error Host::OpenFileInExternalEditor(llvm::StringRef editor, + const FileSpec &file_spec, + uint32_t line_no) { + return llvm::errorCodeToError( + std::error_code(ENOTSUP, std::system_category())); +} + +bool Host::IsInteractiveGraphicSession() { return false; } +#endif + +std::unique_ptr<Connection> Host::CreateDefaultConnection(llvm::StringRef url) { +#if defined(_WIN32) + if (url.starts_with("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); +} + +uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &process_infos) { + return FindProcessesImpl(match_info, process_infos); +} + +char SystemLogHandler::ID; + +SystemLogHandler::SystemLogHandler() {} + +void SystemLogHandler::Emit(llvm::StringRef message) { + Host::SystemLog(lldb::eSeverityInfo, message); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/HostInfoBase.cpp b/contrib/llvm-project/lldb/source/Host/common/HostInfoBase.cpp new file mode 100644 index 000000000000..5c44c2f38b28 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/HostInfoBase.cpp @@ -0,0 +1,352 @@ +//===-- HostInfoBase.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" + +#include <mutex> +#include <optional> +#include <thread> + +using namespace lldb; +using namespace lldb_private; + +namespace { +/// Contains the state of the HostInfoBase plugin. +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; +}; +} // namespace + +static HostInfoBaseFields *g_fields = nullptr; +static HostInfoBase::SharedLibraryDirectoryHelper *g_shlib_dir_helper = nullptr; + +void HostInfoBase::Initialize(SharedLibraryDirectoryHelper *helper) { + g_shlib_dir_helper = helper; + g_fields = new HostInfoBaseFields(); +} + +void HostInfoBase::Terminate() { + g_shlib_dir_helper = nullptr; + 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; +} + +std::optional<HostInfoBase::ArchitectureKind> +HostInfoBase::ParseArchitectureKind(llvm::StringRef kind) { + return llvm::StringSwitch<std::optional<ArchitectureKind>>(kind) + .Case(LLDB_ARCH_DEFAULT, eArchKindDefault) + .Case(LLDB_ARCH_DEFAULT_32BIT, eArchKind32) + .Case(LLDB_ARCH_DEFAULT_64BIT, eArchKind64) + .Default(std::nullopt); +} + +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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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() && + !host_triple.getEnvironmentName().empty()) + normalized_triple.setEnvironment(host_triple.getEnvironment()); + return ArchSpec(normalized_triple); +} + +bool HostInfoBase::ComputePathRelativeToLibrary(FileSpec &file_spec, + llvm::StringRef dir) { + Log *log = GetLog(LLDBLog::Host); + + FileSpec lldb_file_spec = GetShlibDir(); + if (!lldb_file_spec) + return false; + + std::string raw_path = lldb_file_spec.GetPath(); + LLDB_LOG( + log, + "Attempting to derive the path {0} relative to liblldb install path: {1}", + dir, raw_path); + + // Drop bin (windows) or lib + llvm::StringRef parent_path = llvm::sys::path::parent_path(raw_path); + if (parent_path.empty()) { + LLDB_LOG(log, "Failed to find liblldb within the shared lib path"); + return false; + } + + raw_path = (parent_path + dir).str(); + LLDB_LOG(log, "Derived the path as: {0}", raw_path); + file_spec.SetDirectory(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))); + + if (g_shlib_dir_helper) + g_shlib_dir_helper(lldb_file_spec); + + // Remove the filename so that this FileSpec only represents the directory. + file_spec.SetDirectory(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.SetDirectory(temp_file_spec.GetPathAsConstString()); + 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.SetDirectory(temp_file_spec.GetPathAsConstString()); + 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: + case llvm::Triple::riscv64: + case llvm::Triple::loongarch64: + 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/contrib/llvm-project/lldb/source/Host/common/HostNativeThreadBase.cpp b/contrib/llvm-project/lldb/source/Host/common/HostNativeThreadBase.cpp new file mode 100644 index 000000000000..a9cbb69c4d98 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/HostNativeThreadBase.cpp @@ -0,0 +1,63 @@ +//===-- HostNativeThreadBase.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostNativeThreadBase.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/LLDBLog.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(thread_t thread) + : m_thread(thread) {} + +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; // NOLINT(modernize-use-nullptr) +} + +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; // NOLINT(modernize-use-nullptr) + + return result; +} + +lldb::thread_result_t +HostNativeThreadBase::ThreadCreateTrampoline(lldb::thread_arg_t arg) { + std::unique_ptr<ThreadLauncher::HostThreadCreateInfo> info_up( + (ThreadLauncher::HostThreadCreateInfo *)arg); + llvm::set_thread_name(info_up->thread_name); + + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "thread created"); + + return info_up->impl(); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/HostProcess.cpp b/contrib/llvm-project/lldb/source/Host/common/HostProcess.cpp new file mode 100644 index 000000000000..8db5a61e8796 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/HostProcess.cpp @@ -0,0 +1,42 @@ +//===-- HostProcess.cpp ---------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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() = default; + +Status HostProcess::Terminate() { return m_native_process->Terminate(); } + +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) { + return m_native_process->StartMonitoring(callback); +} + +HostNativeProcessBase &HostProcess::GetNativeProcess() { + return *m_native_process; +} + +const HostNativeProcessBase &HostProcess::GetNativeProcess() const { + return *m_native_process; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/HostThread.cpp b/contrib/llvm-project/lldb/source/Host/common/HostThread.cpp new file mode 100644 index 000000000000..eec029be1c09 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/HostThread.cpp @@ -0,0 +1,46 @@ +//===-- HostThread.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/LZMA.cpp b/contrib/llvm-project/lldb/source/Host/common/LZMA.cpp new file mode 100644 index 000000000000..5b457f07afca --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/LZMA.cpp @@ -0,0 +1,146 @@ +//===-- LZMA.cpp ----------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/LockFileBase.cpp b/contrib/llvm-project/lldb/source/Host/common/LockFileBase.cpp new file mode 100644 index 000000000000..1c0de9e04e29 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/LockFileBase.cpp @@ -0,0 +1,78 @@ +//===-- LockFileBase.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/LockFileBase.h" + +using namespace lldb; +using namespace lldb_private; + +static Status AlreadyLocked() { return Status("Already locked"); } + +static 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/contrib/llvm-project/lldb/source/Host/common/MainLoopBase.cpp b/contrib/llvm-project/lldb/source/Host/common/MainLoopBase.cpp new file mode 100644 index 000000000000..030a4f037168 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/MainLoopBase.cpp @@ -0,0 +1,33 @@ +//===-- MainLoopBase.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/MainLoopBase.h" + +using namespace lldb; +using namespace lldb_private; + +void MainLoopBase::AddPendingCallback(const Callback &callback) { + { + std::lock_guard<std::mutex> lock{m_callback_mutex}; + m_pending_callbacks.push_back(callback); + } + TriggerPendingCallbacks(); +} + +void MainLoopBase::ProcessPendingCallbacks() { + // Move the callbacks to a local vector to avoid keeping m_pending_callbacks + // locked throughout the calls. + std::vector<Callback> pending_callbacks; + { + std::lock_guard<std::mutex> lock{m_callback_mutex}; + pending_callbacks = std::move(m_pending_callbacks); + } + + for (const Callback &callback : pending_callbacks) + callback(*this); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/MonitoringProcessLauncher.cpp b/contrib/llvm-project/lldb/source/Host/common/MonitoringProcessLauncher.cpp new file mode 100644 index 000000000000..953934ea4a6e --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/MonitoringProcessLauncher.cpp @@ -0,0 +1,70 @@ +//===-- MonitoringProcessLauncher.cpp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/MonitoringProcessLauncher.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/ProcessLaunchInfo.h" +#include "lldb/Utility/LLDBLog.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 = GetLog(LLDBLog::Process); + + assert(launch_info.GetMonitorProcessCallback()); + llvm::Expected<HostThread> maybe_thread = + process.StartMonitoring(launch_info.GetMonitorProcessCallback()); + 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/contrib/llvm-project/lldb/source/Host/common/NativeProcessProtocol.cpp b/contrib/llvm-project/lldb/source/Host/common/NativeProcessProtocol.cpp new file mode 100644 index 000000000000..b3ef8f027bcf --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -0,0 +1,766 @@ +//===-- NativeProcessProtocol.cpp -----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" +#include "lldb/lldb-enumerations.h" + +#include "llvm/Support/Process.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +// NativeProcessProtocol Members + +NativeProcessProtocol::NativeProcessProtocol(lldb::pid_t pid, int terminal_fd, + NativeDelegate &delegate) + : m_pid(pid), m_delegate(delegate), m_terminal_fd(terminal_fd) { + delegate.InitializeDelegate(this); +} + +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"); +} + +lldb_private::Status +NativeProcessProtocol::ReadMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, std::vector<uint8_t> &tags) { + return Status("not implemented"); +} + +lldb_private::Status +NativeProcessProtocol::WriteMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, + const std::vector<uint8_t> &tags) { + return Status("not implemented"); +} + +std::optional<WaitStatus> NativeProcessProtocol::GetExitStatus() { + if (m_state == lldb::eStateExited) + return m_exit_status; + + return std::nullopt; +} + +bool NativeProcessProtocol::SetExitStatus(WaitStatus status, + bool bNotifyStateChange) { + Log *log = GetLog(LLDBLog::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(); +} + +std::optional<std::pair<uint32_t, uint32_t>> +NativeProcessProtocol::GetHardwareDebugSupportInfo() const { + Log *log = GetLog(LLDBLog::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 std::nullopt; + } + + 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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 == std::nullopt || 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; +} + +void NativeProcessProtocol::SynchronouslyNotifyProcessStateChanged( + lldb::StateType state) { + Log *log = GetLog(LLDBLog::Process); + + m_delegate.ProcessStateChanged(this, state); + + switch (state) { + case eStateStopped: + case eStateExited: + case eStateCrashed: + NotifyTracersProcessDidStop(); + break; + default: + break; + } + + LLDB_LOG(log, "sent state notification [{0}] from process {1}", state, + GetID()); +} + +void NativeProcessProtocol::NotifyDidExec() { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "process {0} exec()ed", GetID()); + + m_software_breakpoints.clear(); + + m_delegate.DidExec(this); +} + +Status NativeProcessProtocol::SetSoftwareBreakpoint(lldb::addr_t addr, + uint32_t size_hint) { + Log *log = GetLog(LLDBLog::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 = GetLog(LLDBLog::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 (llvm::ArrayRef(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 = GetLog(LLDBLog::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::ArrayRef(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_msp430_opcode[] = {0x43, 0x43}; + static const uint8_t g_s390x_opcode[] = {0x00, 0x01}; + static const uint8_t g_ppc_opcode[] = {0x7f, 0xe0, 0x00, 0x08}; // trap + static const uint8_t g_ppcle_opcode[] = {0x08, 0x00, 0xe0, 0x7f}; // trap + static const uint8_t g_riscv_opcode[] = {0x73, 0x00, 0x10, 0x00}; // ebreak + static const uint8_t g_riscv_opcode_c[] = {0x02, 0x90}; // c.ebreak + static const uint8_t g_loongarch_opcode[] = {0x05, 0x00, 0x2a, + 0x00}; // break 0x5 + + switch (GetArchitecture().GetMachine()) { + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_32: + return llvm::ArrayRef(g_aarch64_opcode); + + case llvm::Triple::x86: + case llvm::Triple::x86_64: + return llvm::ArrayRef(g_i386_opcode); + + case llvm::Triple::mips: + case llvm::Triple::mips64: + return llvm::ArrayRef(g_mips64_opcode); + + case llvm::Triple::mipsel: + case llvm::Triple::mips64el: + return llvm::ArrayRef(g_mips64el_opcode); + + case llvm::Triple::msp430: + return llvm::ArrayRef(g_msp430_opcode); + + case llvm::Triple::systemz: + return llvm::ArrayRef(g_s390x_opcode); + + case llvm::Triple::ppc: + case llvm::Triple::ppc64: + return llvm::ArrayRef(g_ppc_opcode); + + case llvm::Triple::ppc64le: + return llvm::ArrayRef(g_ppcle_opcode); + + case llvm::Triple::riscv32: + case llvm::Triple::riscv64: { + return size_hint == 2 ? llvm::ArrayRef(g_riscv_opcode_c) + : llvm::ArrayRef(g_riscv_opcode); + } + + case llvm::Triple::loongarch32: + case llvm::Triple::loongarch64: + return llvm::ArrayRef(g_loongarch_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::ppc: + case llvm::Triple::ppc64: + case llvm::Triple::ppc64le: + case llvm::Triple::riscv32: + case llvm::Triple::riscv64: + case llvm::Triple::loongarch32: + case llvm::Triple::loongarch64: + // 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 = GetLog(LLDBLog::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; + + llvm::MutableArrayRef data(static_cast<uint8_t *>(buf), bytes_read); + for (const auto &pair : m_software_breakpoints) { + lldb::addr_t bp_addr = pair.first; + auto saved_opcodes = llvm::ArrayRef(pair.second.saved_opcodes); + + if (bp_addr + saved_opcodes.size() < addr || addr + bytes_read <= bp_addr) + continue; // Breakpoint 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::Manager::~Manager() = default; diff --git a/contrib/llvm-project/lldb/source/Host/common/NativeRegisterContext.cpp b/contrib/llvm-project/lldb/source/Host/common/NativeRegisterContext.cpp new file mode 100644 index 000000000000..40a57f3d5c82 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/NativeRegisterContext.cpp @@ -0,0 +1,458 @@ +//===-- NativeRegisterContext.cpp -----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/common/NativeRegisterContext.h" +#include "lldb/Utility/LLDBLog.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() = default; + +// 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; + + // Generic register names take precedence over specific register names. + // For example, on x86 we want "sp" to refer to the complete RSP/ESP register + // rather than the 16-bit SP pseudo-register. + uint32_t generic_reg = Args::StringToGenericRegister(reg_name); + if (generic_reg != LLDB_INVALID_REGNUM) { + const RegisterInfo *reg_info = + GetRegisterInfo(eRegisterKindGeneric, generic_reg); + if (reg_info) + return reg_info; + } + + 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_insensitive(reg_info->name) || + reg_name.equals_insensitive(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 = GetLog(LLDBLog::Thread); + + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + LLDB_LOGF(log, "Using reg index %" PRIu32 " (default %" PRIu64 ")", reg, + fail_value); + + const uint64_t retval = ReadRegisterAsUnsigned(reg, fail_value); + + LLDB_LOGF(log, PRIu32 " retval %" PRIu64, 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 = GetLog(LLDBLog::Thread); + + if (reg_info) { + RegisterValue value; + Status error = ReadRegister(reg_info, value); + if (error.Success()) { + LLDB_LOGF(log, + "Read register succeeded: value " + "%" PRIu64, + value.GetAsUInt64()); + return value.GetAsUInt64(); + } else { + LLDB_LOGF(log, "Read register failed: error %s", error.AsCString()); + } + } else { + LLDB_LOGF(log, "Read register failed: null reg_info"); + } + return fail_value; +} + +Status NativeRegisterContext::WriteRegisterFromUnsigned(uint32_t reg, + uint64_t uval) { + if (reg == LLDB_INVALID_REGNUM) + return Status("Write register failed: reg is invalid"); + 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::ClearWatchpointHit(uint32_t hw_index) { + return Status("not implemented"); +} + +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] + 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(); + RegisterValue::BytesContainer src(src_len); + + // Read the memory + size_t bytes_read; + error = process.ReadMemory(src_addr, src.data(), 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.data(), 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) { + Status error; + if (reg_info == nullptr) { + error.SetErrorString("Invalid register info argument."); + return error; + } + + RegisterValue::BytesContainer dst(dst_len); + 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.data(), 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.data(), 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; +} + +std::vector<uint32_t> +NativeRegisterContext::GetExpeditedRegisters(ExpeditedRegs expType) const { + if (expType == ExpeditedRegs::Minimal) { + // Expedite only a minimum set of important generic registers. + static const uint32_t k_expedited_registers[] = { + LLDB_REGNUM_GENERIC_PC, LLDB_REGNUM_GENERIC_SP, LLDB_REGNUM_GENERIC_FP, + LLDB_REGNUM_GENERIC_RA}; + + std::vector<uint32_t> expedited_reg_nums; + for (uint32_t gen_reg : k_expedited_registers) { + uint32_t reg_num = + ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, gen_reg); + if (reg_num == LLDB_INVALID_REGNUM) + continue; // Target does not support the given register. + else + expedited_reg_nums.push_back(reg_num); + } + + return expedited_reg_nums; + } + + if (GetRegisterSetCount() > 0 && expType == ExpeditedRegs::Full) + return std::vector<uint32_t>(GetRegisterSet(0)->registers, + GetRegisterSet(0)->registers + + GetRegisterSet(0)->num_registers); + + return std::vector<uint32_t>(); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/NativeThreadProtocol.cpp b/contrib/llvm-project/lldb/source/Host/common/NativeThreadProtocol.cpp new file mode 100644 index 000000000000..16901bc9cf7b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/NativeThreadProtocol.cpp @@ -0,0 +1,19 @@ +//===-- NativeThreadProtocol.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/NativeWatchpointList.cpp b/contrib/llvm-project/lldb/source/Host/common/NativeWatchpointList.cpp new file mode 100644 index 000000000000..6d856202d147 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/NativeWatchpointList.cpp @@ -0,0 +1,30 @@ +//===-- NativeWatchpointList.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/OptionParser.cpp b/contrib/llvm-project/lldb/source/Host/common/OptionParser.cpp new file mode 100644 index 000000000000..0274fdc4ac3f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/OptionParser.cpp @@ -0,0 +1,84 @@ +//===-- source/Host/common/OptionParser.cpp -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/HostGetOpt.h" +#include "lldb/Utility/OptionDefinition.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 = std::string(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/contrib/llvm-project/lldb/source/Host/common/PipeBase.cpp b/contrib/llvm-project/lldb/source/Host/common/PipeBase.cpp new file mode 100644 index 000000000000..b3e0ab34a58d --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/PipeBase.cpp @@ -0,0 +1,24 @@ +//===-- source/Host/common/PipeBase.cpp -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "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/contrib/llvm-project/lldb/source/Host/common/ProcessLaunchInfo.cpp b/contrib/llvm-project/lldb/source/Host/common/ProcessLaunchInfo.cpp new file mode 100644 index 000000000000..a1866b2a99fd --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/ProcessLaunchInfo.cpp @@ -0,0 +1,342 @@ +//===-- ProcessLaunchInfo.cpp ---------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <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/LLDBLog.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 <climits> +#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_monitor_callback(nullptr) { +} + +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) { + 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; +} + +llvm::StringRef ProcessLaunchInfo::GetProcessPluginName() const { + return llvm::StringRef(m_plugin_name); +} + +void ProcessLaunchInfo::SetProcessPluginName(llvm::StringRef plugin) { + m_plugin_name = std::string(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::NoOpMonitorCallback(lldb::pid_t pid, int signal, + int status) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "pid = {0}, signal = {1}, status = {2}", pid, signal, status); +} + +bool ProcessLaunchInfo::MonitorProcess() const { + if (m_monitor_callback && ProcessIDIsValid()) { + llvm::Expected<HostThread> maybe_thread = + Host::StartMonitoringChildProcess(m_monitor_callback, GetProcessID()); + if (!maybe_thread) + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), maybe_thread.takeError(), + "failed to launch host thread: {0}"); + 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 = GetLog(LLDBLog::Process); + + bool stdin_free = GetFileActionForFD(STDIN_FILENO) == nullptr; + bool stdout_free = GetFileActionForFD(STDOUT_FILENO) == nullptr; + bool stderr_free = GetFileActionForFD(STDERR_FILENO) == nullptr; + bool any_free = stdin_free || stdout_free || stderr_free; + if (!any_free) + return llvm::Error::success(); + + 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 (llvm::Error Err = m_pty->OpenFirstAvailablePrimary(open_flags)) + return Err; + + const FileSpec secondary_file_spec(m_pty->GetSecondaryName()); + + if (stdin_free) + AppendOpenFileAction(STDIN_FILENO, secondary_file_spec, true, false); + + if (stdout_free) + AppendOpenFileAction(STDOUT_FILENO, secondary_file_spec, false, true); + + if (stderr_free) + AppendOpenFileAction(STDERR_FILENO, secondary_file_spec, false, true); + return llvm::Error::success(); +} + +bool ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell( + Status &error, bool will_debug, bool first_arg_is_full_shell_command, + uint32_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; + 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) { + std::string safe_arg = Args::GetShellSafeArgument(m_shell, argv[i]); + if (safe_arg.empty()) + safe_arg = "\"\""; + // Add a space to separate this arg from the previous one. + shell_command.PutCString(" "); + shell_command.PutCString(safe_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/contrib/llvm-project/lldb/source/Host/common/ProcessRunLock.cpp b/contrib/llvm-project/lldb/source/Host/common/ProcessRunLock.cpp new file mode 100644 index 000000000000..da59f4057697 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/ProcessRunLock.cpp @@ -0,0 +1,65 @@ +//===-- ProcessRunLock.cpp ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _WIN32 +#include "lldb/Host/ProcessRunLock.h" + +namespace lldb_private { + +ProcessRunLock::ProcessRunLock() { + 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) { + // coverity[missing_unlock] + 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/contrib/llvm-project/lldb/source/Host/common/PseudoTerminal.cpp b/contrib/llvm-project/lldb/source/Host/common/PseudoTerminal.cpp new file mode 100644 index 000000000000..d53327973eb2 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/PseudoTerminal.cpp @@ -0,0 +1,221 @@ +//===-- PseudoTerminal.cpp ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/PseudoTerminal.h" +#include "lldb/Host/Config.h" +#include "lldb/Host/FileSystem.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Errno.h" +#include <cassert> +#include <climits> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <mutex> +#if defined(TIOCSCTTY) +#include <sys/ioctl.h> +#endif + +#include "lldb/Host/PosixApi.h" + +#if defined(__APPLE__) +#include <Availability.h> +#endif + +#if defined(__ANDROID__) +int posix_openpt(int flags); +#endif + +using namespace lldb_private; + +// PseudoTerminal constructor +PseudoTerminal::PseudoTerminal() = default; + +// Destructor +// +// The destructor will close the primary and secondary file descriptors if they +// are valid and ownership has not been released using the +// ReleasePrimaryFileDescriptor() or the ReleaseSaveFileDescriptor() member +// functions. +PseudoTerminal::~PseudoTerminal() { + ClosePrimaryFileDescriptor(); + CloseSecondaryFileDescriptor(); +} + +// Close the primary file descriptor if it is valid. +void PseudoTerminal::ClosePrimaryFileDescriptor() { + if (m_primary_fd >= 0) { + ::close(m_primary_fd); + m_primary_fd = invalid_fd; + } +} + +// Close the secondary file descriptor if it is valid. +void PseudoTerminal::CloseSecondaryFileDescriptor() { + if (m_secondary_fd >= 0) { + ::close(m_secondary_fd); + m_secondary_fd = invalid_fd; + } +} + +llvm::Error PseudoTerminal::OpenFirstAvailablePrimary(int oflag) { +#if LLDB_ENABLE_POSIX + // Open the primary side of a pseudo terminal + m_primary_fd = ::posix_openpt(oflag); + if (m_primary_fd < 0) { + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + } + + // Grant access to the secondary pseudo terminal + if (::grantpt(m_primary_fd) < 0) { + std::error_code EC(errno, std::generic_category()); + ClosePrimaryFileDescriptor(); + return llvm::errorCodeToError(EC); + } + + // Clear the lock flag on the secondary pseudo terminal + if (::unlockpt(m_primary_fd) < 0) { + std::error_code EC(errno, std::generic_category()); + ClosePrimaryFileDescriptor(); + return llvm::errorCodeToError(EC); + } + + return llvm::Error::success(); +#else + return llvm::errorCodeToError(llvm::errc::not_supported); +#endif +} + +llvm::Error PseudoTerminal::OpenSecondary(int oflag) { + CloseSecondaryFileDescriptor(); + + std::string name = GetSecondaryName(); + m_secondary_fd = FileSystem::Instance().Open(name.c_str(), oflag); + if (m_secondary_fd >= 0) + return llvm::Error::success(); + + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); +} + +#if !HAVE_PTSNAME_R || defined(__APPLE__) +static std::string use_ptsname(int fd) { + static std::mutex mutex; + std::lock_guard<std::mutex> guard(mutex); + const char *r = ptsname(fd); + assert(r != nullptr); + return r; +} +#endif + +std::string PseudoTerminal::GetSecondaryName() const { + assert(m_primary_fd >= 0); +#if HAVE_PTSNAME_R +#if defined(__APPLE__) + if (__builtin_available(macos 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.4, *)) { +#endif + char buf[PATH_MAX]; + buf[0] = '\0'; + int r = ptsname_r(m_primary_fd, buf, sizeof(buf)); + UNUSED_IF_ASSERT_DISABLED(r); + assert(r == 0); + return buf; +#if defined(__APPLE__) + } else { + return use_ptsname(m_primary_fd); + } +#endif +#else + return use_ptsname(m_primary_fd); +#endif +} + +llvm::Expected<lldb::pid_t> PseudoTerminal::Fork() { +#if LLDB_ENABLE_POSIX + if (llvm::Error Err = OpenFirstAvailablePrimary(O_RDWR | O_CLOEXEC)) + return std::move(Err); + + pid_t pid = ::fork(); + if (pid < 0) { + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + } + if (pid > 0) { + // Parent process. + return pid; + } + + // Child Process + ::setsid(); + + if (llvm::Error Err = OpenSecondary(O_RDWR)) + return std::move(Err); + + // Primary FD should have O_CLOEXEC set, but let's close it just in + // case... + ClosePrimaryFileDescriptor(); + +#if defined(TIOCSCTTY) + // Acquire the controlling terminal + if (::ioctl(m_secondary_fd, TIOCSCTTY, (char *)0) < 0) { + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + } +#endif + // Duplicate all stdio file descriptors to the secondary pseudo terminal + for (int fd : {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) { + if (::dup2(m_secondary_fd, fd) != fd) { + return llvm::errorCodeToError( + std::error_code(errno, std::generic_category())); + } + } +#endif + return 0; +} + +// The primary file descriptor accessor. This object retains ownership of the +// primary file descriptor when this accessor is used. Use +// ReleasePrimaryFileDescriptor() if you wish this object to release ownership +// of the primary file descriptor. +// +// Returns the primary file descriptor, or -1 if the primary file descriptor is +// not currently valid. +int PseudoTerminal::GetPrimaryFileDescriptor() const { return m_primary_fd; } + +// The secondary file descriptor accessor. +// +// Returns the secondary file descriptor, or -1 if the secondary file descriptor +// is not currently valid. +int PseudoTerminal::GetSecondaryFileDescriptor() const { + return m_secondary_fd; +} + +// Release ownership of the primary pseudo terminal file descriptor without +// closing it. The destructor for this class will close the primary file +// descriptor if the ownership isn't released using this call and the primary +// file descriptor has been opened. +int PseudoTerminal::ReleasePrimaryFileDescriptor() { + // Release ownership of the primary pseudo terminal file descriptor without + // closing it. (the destructor for this class will close it otherwise!) + int fd = m_primary_fd; + m_primary_fd = invalid_fd; + return fd; +} + +// Release ownership of the secondary pseudo terminal file descriptor without +// closing it. The destructor for this class will close the secondary file +// descriptor if the ownership isn't released using this call and the secondary +// file descriptor has been opened. +int PseudoTerminal::ReleaseSecondaryFileDescriptor() { + // Release ownership of the secondary pseudo terminal file descriptor without + // closing it (the destructor for this class will close it otherwise!) + int fd = m_secondary_fd; + m_secondary_fd = invalid_fd; + return fd; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/Socket.cpp b/contrib/llvm-project/lldb/source/Host/common/Socket.cpp new file mode 100644 index 000000000000..f9911cf136cb --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/Socket.cpp @@ -0,0 +1,376 @@ +//===-- Socket.cpp --------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Socket.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/SocketAddress.h" +#include "lldb/Host/common/TCPSocket.h" +#include "lldb/Host/common/UDPSocket.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Regex.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 <cerrno> +#include <fcntl.h> +#include <linux/tcp.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) + +static 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::createStringError("WSASock version is not expected."); + } + } 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; +} + +llvm::Expected<std::unique_ptr<Socket>> +Socket::TcpConnect(llvm::StringRef host_and_port, + bool child_processes_inherit) { + Log *log = GetLog(LLDBLog::Connection); + LLDB_LOG(log, "host_and_port = {0}", host_and_port); + + Status error; + std::unique_ptr<Socket> connect_socket( + Create(ProtocolTcp, child_processes_inherit, error)); + if (error.Fail()) + return error.ToError(); + + error = connect_socket->Connect(host_and_port); + if (error.Success()) + return std::move(connect_socket); + + return error.ToError(); +} + +llvm::Expected<std::unique_ptr<TCPSocket>> +Socket::TcpListen(llvm::StringRef host_and_port, bool child_processes_inherit, + int backlog) { + Log *log = GetLog(LLDBLog::Connection); + LLDB_LOG(log, "host_and_port = {0}", host_and_port); + + std::unique_ptr<TCPSocket> listen_socket( + new TCPSocket(true, child_processes_inherit)); + + Status error = listen_socket->Listen(host_and_port, backlog); + if (error.Fail()) + return error.ToError(); + + return std::move(listen_socket); +} + +llvm::Expected<std::unique_ptr<UDPSocket>> +Socket::UdpConnect(llvm::StringRef host_and_port, + bool child_processes_inherit) { + return UDPSocket::Connect(host_and_port, child_processes_inherit); +} + +llvm::Expected<Socket::HostAndPort> Socket::DecodeHostAndPort(llvm::StringRef host_and_port) { + static llvm::Regex g_regex("([^:]+|\\[[0-9a-fA-F:]+.*\\]):([0-9]+)"); + HostAndPort ret; + llvm::SmallVector<llvm::StringRef, 3> matches; + if (g_regex.match(host_and_port, &matches)) { + ret.hostname = matches[1].str(); + // IPv6 addresses are wrapped in [] when specified with ports + if (ret.hostname.front() == '[' && ret.hostname.back() == ']') + ret.hostname = ret.hostname.substr(1, ret.hostname.size() - 2); + if (to_integer(matches[2], ret.port, 10)) + return ret; + } else { + // If this was unsuccessful, then check if it's simply an unsigned 16-bit + // integer, representing a port with an empty host. + if (to_integer(host_and_port, ret.port, 10)) + return ret; + } + + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid host:port specification: '%s'", + host_and_port.str().c_str()); +} + +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 = GetLog(LLDBLog::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 = GetLog(LLDBLog::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::Close() { + Status error; + if (!IsValid() || !m_should_close_fd) + return error; + + Log *log = GetLog(LLDBLog::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) == 0; +#else + bool success = ::close(m_socket) == 0; +#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; +} + +llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS, + const Socket::HostAndPort &HP) { + return OS << '[' << HP.hostname << ']' << ':' << HP.port; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/SocketAddress.cpp b/contrib/llvm-project/lldb/source/Host/common/SocketAddress.cpp new file mode 100644 index 000000000000..6a23c633e54b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/SocketAddress.cpp @@ -0,0 +1,324 @@ +//===-- SocketAddress.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// 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 <cstddef> +#include <cstdio> + +#if !defined(_WIN32) +#include <arpa/inet.h> +#endif + +#include <cassert> +#include <cstring> + +#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() = default; + +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/contrib/llvm-project/lldb/source/Host/common/StreamFile.cpp b/contrib/llvm-project/lldb/source/Host/common/StreamFile.cpp new file mode 100644 index 000000000000..099980a0993c --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/StreamFile.cpp @@ -0,0 +1,54 @@ +//===-- StreamFile.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/StreamFile.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include <cstdio> + +using namespace lldb; +using namespace lldb_private; + +StreamFile::StreamFile(uint32_t flags, uint32_t addr_size, ByteOrder byte_order) + : Stream(flags, addr_size, byte_order) { + m_file_sp = std::make_shared<File>(); +} + +StreamFile::StreamFile(int fd, bool transfer_ownership) : Stream() { + m_file_sp = std::make_shared<NativeFile>(fd, File::eOpenOptionWriteOnly, + transfer_ownership); +} + +StreamFile::StreamFile(FILE *fh, bool transfer_ownership) : Stream() { + m_file_sp = std::make_shared<NativeFile>(fh, transfer_ownership); +} + +StreamFile::StreamFile(const char *path, File::OpenOptions options, + uint32_t permissions) + : Stream() { + auto file = FileSystem::Instance().Open(FileSpec(path), options, permissions); + if (file) + m_file_sp = std::move(file.get()); + else { + // TODO refactor this so the error gets popagated up instead of logged here. + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), file.takeError(), + "Cannot open {1}: {0}", path); + m_file_sp = std::make_shared<File>(); + } +} + +StreamFile::~StreamFile() = default; + +void StreamFile::Flush() { m_file_sp->Flush(); } + +size_t StreamFile::WriteImpl(const void *s, size_t length) { + m_file_sp->Write(s, length); + return length; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/TCPSocket.cpp b/contrib/llvm-project/lldb/source/Host/common/TCPSocket.cpp new file mode 100644 index 000000000000..df4737216ed3 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/TCPSocket.cpp @@ -0,0 +1,326 @@ +//===-- TCPSocket.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#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/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/WindowsError.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; + +static Status GetLastSocketError() { + std::error_code EC; +#ifdef _WIN32 + EC = llvm::mapWindowsError(WSAGetLastError()); +#else + EC = std::error_code(errno, std::generic_category()); +#endif + return EC; +} + +static 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 std::string(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 = GetLog(LLDBLog::Communication); + LLDB_LOG(log, "Connect to host/port {0}", name); + + Status error; + llvm::Expected<HostAndPort> host_port = DecodeHostAndPort(name); + if (!host_port) + return Status(host_port.takeError()); + + std::vector<SocketAddress> addresses = + SocketAddress::GetAddressInfo(host_port->hostname.c_str(), nullptr, + AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); + for (SocketAddress &address : addresses) { + error = CreateSocket(address.GetFamily()); + if (error.Fail()) + continue; + + address.SetPort(host_port->port); + + if (llvm::sys::RetryAfterSignal(-1, ::connect, GetNativeSocket(), + &address.sockaddr(), + address.GetLength()) == -1) { + Close(); + continue; + } + + if (SetOptionNoDelay() == -1) { + Close(); + continue; + } + + error.Clear(); + return error; + } + + error.SetErrorString("Failed to connect port"); + return error; +} + +Status TCPSocket::Listen(llvm::StringRef name, int backlog) { + Log *log = GetLog(LLDBLog::Connection); + LLDB_LOG(log, "Listen to {0}", name); + + Status error; + llvm::Expected<HostAndPort> host_port = DecodeHostAndPort(name); + if (!host_port) + return Status(host_port.takeError()); + + if (host_port->hostname == "*") + host_port->hostname = "0.0.0.0"; + std::vector<SocketAddress> addresses = SocketAddress::GetAddressInfo( + host_port->hostname.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() || fd < 0) + 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); + if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, option_value_p, + sizeof(option_value)) == -1) { + CLOSE_SOCKET(fd); + continue; + } + + SocketAddress listen_address = address; + if(!listen_address.IsLocalhost()) + listen_address.SetToAnyAddress(address.GetFamily(), host_port->port); + else + listen_address.SetPort(host_port->port); + + int err = + ::bind(fd, &listen_address.sockaddr(), listen_address.GetLength()); + if (err != -1) + err = ::listen(fd, backlog); + + if (err == -1) { + error = GetLastSocketError(); + CLOSE_SOCKET(fd); + continue; + } + + if (host_port->port == 0) { + socklen_t sa_len = address.GetLength(); + if (getsockname(fd, &address.sockaddr(), &sa_len) == 0) + host_port->port = address.GetPort(); + } + m_listen_sockets[fd] = address; + } + + if (m_listen_sockets.empty()) { + assert(error.Fail()); + return error; + } + return Status(); +} + +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; + } + + NativeSocket sock = kInvalidSocketValue; + NativeSocket listen_sock = kInvalidSocketValue; + 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) { + if (sock != kInvalidSocketValue) { + CLOSE_SOCKET(sock); + sock = kInvalidSocketValue; + } + 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/contrib/llvm-project/lldb/source/Host/common/Terminal.cpp b/contrib/llvm-project/lldb/source/Host/common/Terminal.cpp new file mode 100644 index 000000000000..436dfd8130d9 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/Terminal.cpp @@ -0,0 +1,474 @@ +//===-- Terminal.cpp ------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Terminal.h" + +#include "lldb/Host/Config.h" +#include "lldb/Host/PosixApi.h" +#include "llvm/ADT/STLExtras.h" + +#include <csignal> +#include <fcntl.h> +#include <optional> + +#if LLDB_ENABLE_TERMIOS +#include <termios.h> +#endif + +using namespace lldb_private; + +struct Terminal::Data { +#if LLDB_ENABLE_TERMIOS + struct termios m_termios; ///< Cached terminal state information. +#endif +}; + +bool Terminal::IsATerminal() const { return m_fd >= 0 && ::isatty(m_fd); } + +#if !LLDB_ENABLE_TERMIOS +static llvm::Error termiosMissingError() { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "termios support missing in LLDB"); +} +#endif + +llvm::Expected<Terminal::Data> Terminal::GetData() { +#if LLDB_ENABLE_TERMIOS + if (!FileDescriptorIsValid()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid fd"); + + if (!IsATerminal()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "fd not a terminal"); + + Data data; + if (::tcgetattr(m_fd, &data.m_termios) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "unable to get teletype attributes"); + return data; +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetData(const Terminal::Data &data) { +#if LLDB_ENABLE_TERMIOS + assert(FileDescriptorIsValid()); + assert(IsATerminal()); + + if (::tcsetattr(m_fd, TCSANOW, &data.m_termios) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "unable to set teletype attributes"); + return llvm::Error::success(); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetEcho(bool enabled) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + fd_termios.c_lflag &= ~ECHO; + if (enabled) + fd_termios.c_lflag |= ECHO; + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetCanonical(bool enabled) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + fd_termios.c_lflag &= ~ICANON; + if (enabled) + fd_termios.c_lflag |= ICANON; + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetRaw() { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + ::cfmakeraw(&fd_termios); + + // Make sure only one character is needed to return from a read + // (cfmakeraw() doesn't do this on NetBSD) + fd_termios.c_cc[VMIN] = 1; + fd_termios.c_cc[VTIME] = 0; + + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +#if LLDB_ENABLE_TERMIOS +static std::optional<speed_t> baudRateToConst(unsigned int baud_rate) { + switch (baud_rate) { +#if defined(B50) + case 50: + return B50; +#endif +#if defined(B75) + case 75: + return B75; +#endif +#if defined(B110) + case 110: + return B110; +#endif +#if defined(B134) + case 134: + return B134; +#endif +#if defined(B150) + case 150: + return B150; +#endif +#if defined(B200) + case 200: + return B200; +#endif +#if defined(B300) + case 300: + return B300; +#endif +#if defined(B600) + case 600: + return B600; +#endif +#if defined(B1200) + case 1200: + return B1200; +#endif +#if defined(B1800) + case 1800: + return B1800; +#endif +#if defined(B2400) + case 2400: + return B2400; +#endif +#if defined(B4800) + case 4800: + return B4800; +#endif +#if defined(B9600) + case 9600: + return B9600; +#endif +#if defined(B19200) + case 19200: + return B19200; +#endif +#if defined(B38400) + case 38400: + return B38400; +#endif +#if defined(B57600) + case 57600: + return B57600; +#endif +#if defined(B115200) + case 115200: + return B115200; +#endif +#if defined(B230400) + case 230400: + return B230400; +#endif +#if defined(B460800) + case 460800: + return B460800; +#endif +#if defined(B500000) + case 500000: + return B500000; +#endif +#if defined(B576000) + case 576000: + return B576000; +#endif +#if defined(B921600) + case 921600: + return B921600; +#endif +#if defined(B1000000) + case 1000000: + return B1000000; +#endif +#if defined(B1152000) + case 1152000: + return B1152000; +#endif +#if defined(B1500000) + case 1500000: + return B1500000; +#endif +#if defined(B2000000) + case 2000000: + return B2000000; +#endif +#if defined(B76800) + case 76800: + return B76800; +#endif +#if defined(B153600) + case 153600: + return B153600; +#endif +#if defined(B307200) + case 307200: + return B307200; +#endif +#if defined(B614400) + case 614400: + return B614400; +#endif +#if defined(B2500000) + case 2500000: + return B2500000; +#endif +#if defined(B3000000) + case 3000000: + return B3000000; +#endif +#if defined(B3500000) + case 3500000: + return B3500000; +#endif +#if defined(B4000000) + case 4000000: + return B4000000; +#endif + default: + return std::nullopt; + } +} +#endif + +llvm::Error Terminal::SetBaudRate(unsigned int baud_rate) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + std::optional<speed_t> val = baudRateToConst(baud_rate); + if (!val) // invalid value + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "baud rate %d unsupported by the platform", + baud_rate); + if (::cfsetispeed(&fd_termios, *val) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "setting input baud rate failed"); + if (::cfsetospeed(&fd_termios, *val) != 0) + return llvm::createStringError( + std::error_code(errno, std::generic_category()), + "setting output baud rate failed"); + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetStopBits(unsigned int stop_bits) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + switch (stop_bits) { + case 1: + fd_termios.c_cflag &= ~CSTOPB; + break; + case 2: + fd_termios.c_cflag |= CSTOPB; + break; + default: + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "invalid stop bit count: %d (must be 1 or 2)", stop_bits); + } + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetParity(Terminal::Parity parity) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + fd_termios.c_cflag &= ~( +#if defined(CMSPAR) + CMSPAR | +#endif + PARENB | PARODD); + + if (parity != Parity::No) { + fd_termios.c_cflag |= PARENB; + if (parity == Parity::Odd || parity == Parity::Mark) + fd_termios.c_cflag |= PARODD; + if (parity == Parity::Mark || parity == Parity::Space) { +#if defined(CMSPAR) + fd_termios.c_cflag |= CMSPAR; +#else + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "space/mark parity is not supported by the platform"); +#endif + } + } + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetParityCheck(Terminal::ParityCheck parity_check) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + + struct termios &fd_termios = data->m_termios; + fd_termios.c_iflag &= ~(IGNPAR | PARMRK | INPCK); + + if (parity_check != ParityCheck::No) { + fd_termios.c_iflag |= INPCK; + if (parity_check == ParityCheck::Ignore) + fd_termios.c_iflag |= IGNPAR; + else if (parity_check == ParityCheck::Mark) + fd_termios.c_iflag |= PARMRK; + } + return SetData(data.get()); +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +llvm::Error Terminal::SetHardwareFlowControl(bool enabled) { +#if LLDB_ENABLE_TERMIOS + llvm::Expected<Data> data = GetData(); + if (!data) + return data.takeError(); + +#if defined(CRTSCTS) + struct termios &fd_termios = data->m_termios; + fd_termios.c_cflag &= ~CRTSCTS; + if (enabled) + fd_termios.c_cflag |= CRTSCTS; + return SetData(data.get()); +#else // !defined(CRTSCTS) + if (enabled) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "hardware flow control is not supported by the platform"); + return llvm::Error::success(); +#endif // defined(CRTSCTS) +#else // !LLDB_ENABLE_TERMIOS + return termiosMissingError(); +#endif // LLDB_ENABLE_TERMIOS +} + +TerminalState::TerminalState(Terminal term, bool save_process_group) + : m_tty(term) { + Save(term, save_process_group); +} + +TerminalState::~TerminalState() { Restore(); } + +void TerminalState::Clear() { + m_tty.Clear(); + m_tflags = -1; + m_data.reset(); + m_process_group = -1; +} + +bool TerminalState::Save(Terminal term, bool save_process_group) { + Clear(); + m_tty = term; + if (m_tty.IsATerminal()) { +#if LLDB_ENABLE_POSIX + int fd = m_tty.GetFileDescriptor(); + m_tflags = ::fcntl(fd, F_GETFL, 0); +#if LLDB_ENABLE_TERMIOS + std::unique_ptr<Terminal::Data> new_data{new Terminal::Data()}; + if (::tcgetattr(fd, &new_data->m_termios) == 0) + m_data = std::move(new_data); +#endif // LLDB_ENABLE_TERMIOS + if (save_process_group) + m_process_group = ::tcgetpgrp(fd); +#endif // LLDB_ENABLE_POSIX + } + return IsValid(); +} + +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_data->m_termios); +#endif // 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 // LLDB_ENABLE_POSIX + return false; +} + +bool TerminalState::IsValid() const { + return m_tty.FileDescriptorIsValid() && + (TFlagsIsValid() || TTYStateIsValid() || ProcessGroupIsValid()); +} + +bool TerminalState::TFlagsIsValid() const { return m_tflags != -1; } + +bool TerminalState::TTYStateIsValid() const { return bool(m_data); } + +bool TerminalState::ProcessGroupIsValid() const { + return static_cast<int32_t>(m_process_group) != -1; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/ThreadLauncher.cpp b/contrib/llvm-project/lldb/source/Host/common/ThreadLauncher.cpp new file mode 100644 index 000000000000..28c90215f874 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/ThreadLauncher.cpp @@ -0,0 +1,79 @@ +//===-- ThreadLauncher.cpp ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// 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, + std::function<thread_result_t()> impl, + size_t min_stack_byte_size) { + // Host::ThreadCreateTrampoline will take ownership if thread creation is + // successful. + auto info_up = std::make_unique<HostThreadCreateInfo>(name.str(), impl); + lldb::thread_t thread; +#ifdef _WIN32 + thread = (lldb::thread_t)::_beginthreadex( + 0, (unsigned)min_stack_byte_size, + HostNativeThread::ThreadCreateTrampoline, info_up.get(), 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_up.get()); + + if (destroy_attr) + ::pthread_attr_destroy(&thread_attr); + + if (err) + return llvm::errorCodeToError( + std::error_code(err, std::generic_category())); +#endif + + info_up.release(); + return HostThread(thread); +} diff --git a/contrib/llvm-project/lldb/source/Host/common/UDPSocket.cpp b/contrib/llvm-project/lldb/source/Host/common/UDPSocket.cpp new file mode 100644 index 000000000000..e40066d519c6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/UDPSocket.cpp @@ -0,0 +1,138 @@ +//===-- UDPSocket.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/common/UDPSocket.h" + +#include "lldb/Host/Config.h" +#include "lldb/Utility/LLDBLog.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; + +static const int kDomain = AF_INET; +static 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); +} + +llvm::Expected<std::unique_ptr<UDPSocket>> +UDPSocket::Connect(llvm::StringRef name, bool child_processes_inherit) { + std::unique_ptr<UDPSocket> socket; + + Log *log = GetLog(LLDBLog::Connection); + LLDB_LOG(log, "host/port = {0}", name); + + Status error; + llvm::Expected<HostAndPort> host_port = DecodeHostAndPort(name); + if (!host_port) + return host_port.takeError(); + + // 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_port->hostname.c_str(), std::to_string(host_port->port).c_str(), &hints, + &service_info_list); + if (err != 0) { + error.SetErrorStringWithFormat( +#if defined(_WIN32) && defined(UNICODE) + "getaddrinfo(%s, %d, &hints, &info) returned error %i (%S)", +#else + "getaddrinfo(%s, %d, &hints, &info) returned error %i (%s)", +#endif + host_port->hostname.c_str(), host_port->port, err, gai_strerror(err)); + return error.ToError(); + } + + 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()) { + socket.reset(new UDPSocket(send_fd)); + socket->m_sockaddr = service_info_ptr; + break; + } else + continue; + } + + ::freeaddrinfo(service_info_list); + + if (!socket) + return error.ToError(); + + 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_port->hostname == "127.0.0.1" || host_port->hostname == "localhost") + ? bind_addr.SetToLocalhost(kDomain, host_port->port) + : bind_addr.SetToAnyAddress(kDomain, host_port->port); + + if (!bind_addr_success) { + error.SetErrorString("Failed to get hostspec to bind for"); + return error.ToError(); + } + + bind_addr.SetPort(0); // Let the source port # be determined dynamically + + err = ::bind(socket->GetNativeSocket(), bind_addr, bind_addr.GetLength()); + + struct sockaddr_in source_info; + socklen_t address_len = sizeof (struct sockaddr_in); + err = ::getsockname(socket->GetNativeSocket(), + (struct sockaddr *)&source_info, &address_len); + + return std::move(socket); +} + +std::string UDPSocket::GetRemoteConnectionURI() const { + if (m_socket != kInvalidSocketValue) { + return std::string(llvm::formatv( + "udp://[{0}]:{1}", m_sockaddr.GetIPAddress(), m_sockaddr.GetPort())); + } + return ""; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/XML.cpp b/contrib/llvm-project/lldb/source/Host/common/XML.cpp new file mode 100644 index 000000000000..f480ef3166a4 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/XML.cpp @@ -0,0 +1,519 @@ +//===-- XML.cpp -----------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/Config.h" +#include "lldb/Host/XML.h" + +#include "llvm/ADT/StringExtras.h" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark-- XMLDocument + +XMLDocument::XMLDocument() = default; + +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() = default; + +XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {} + +XMLNode::~XMLNode() = default; + +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 +} + +std::string XMLNode::GetAttributeValue(const char *name, + const char *fail_value) const { + std::string attr_value; +#if LLDB_ENABLE_LIBXML2 + if (IsValid()) { + xmlChar *value = xmlGetProp(m_node, (const xmlChar *)name); + if (value) { + attr_value = (const char *)value; + xmlFree(value); + } + } else { + if (fail_value) + attr_value = fail_value; + } +#else + if (fail_value) + attr_value = fail_value; +#endif + return attr_value; +} + +bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value, + uint64_t fail_value, int base) const { + value = fail_value; + return llvm::to_integer(GetAttributeValue(name, ""), value, base); +} + +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 { + std::string text; + + value = fail_value; + return GetElementText(text) && llvm::to_integer(text, value, base); +} + +bool XMLNode::GetElementTextAsFloat(double &value, double fail_value) const { + std::string text; + + value = fail_value; + return GetElementText(text) && llvm::to_float(text, value); +} + +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() = default; + +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 + +static 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::UnsignedInteger(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; +} diff --git a/contrib/llvm-project/lldb/source/Host/common/ZipFileResolver.cpp b/contrib/llvm-project/lldb/source/Host/common/ZipFileResolver.cpp new file mode 100644 index 000000000000..f70ccb79d089 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Host/common/ZipFileResolver.cpp @@ -0,0 +1,72 @@ +//===-- ZipFileResolver.cpp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/common/ZipFileResolver.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/DataBuffer.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/ZipFile.h" + +using namespace lldb_private; +using namespace llvm::support; + +bool ZipFileResolver::ResolveSharedLibraryPath(const FileSpec &file_spec, + FileKind &file_kind, + std::string &file_path, + lldb::offset_t &so_file_offset, + lldb::offset_t &so_file_size) { + // When bionic loads .so file from APK or zip file, this file_spec will be + // "zip_path!/so_path". Otherwise it is just a normal file path. + static constexpr llvm::StringLiteral k_zip_separator("!/"); + std::string path(file_spec.GetPath()); + size_t pos = path.find(k_zip_separator); + +#if defined(_WIN32) + // When the file_spec is resolved as a Windows path, the zip .so path will be + // "zip_path!\so_path". Support both patterns on Windows. + static constexpr llvm::StringLiteral k_zip_separator_win("!\\"); + if (pos == std::string::npos) + pos = path.find(k_zip_separator_win); +#endif + + if (pos == std::string::npos) { + // This file_spec does not contain the zip separator. + // Treat this file_spec as a normal file. + // so_file_offset and so_file_size should be 0. + file_kind = FileKind::eFileKindNormal; + file_path = path; + so_file_offset = 0; + so_file_size = 0; + return true; + } + + // This file_spec is a zip .so path. Extract the zip path and the .so path. + std::string zip_path(path.substr(0, pos)); + std::string so_path(path.substr(pos + k_zip_separator.size())); + +#if defined(_WIN32) + // Replace the .so path to use POSIX file separator for file searching inside + // the zip file. + std::replace(so_path.begin(), so_path.end(), '\\', '/'); +#endif + + // Try to find the .so file from the zip file. + FileSpec zip_file_spec(zip_path); + uint64_t zip_file_size = FileSystem::Instance().GetByteSize(zip_file_spec); + lldb::DataBufferSP zip_data = + FileSystem::Instance().CreateDataBuffer(zip_file_spec, zip_file_size); + if (ZipFile::Find(zip_data, so_path, so_file_offset, so_file_size)) { + // Found the .so file from the zip file and got the file offset and size. + // Return the zip path. so_file_offset and so_file_size are already set. + file_kind = FileKind::eFileKindZip; + file_path = zip_path; + return true; + } + + return false; +} |