diff options
Diffstat (limited to 'source/Host/common')
-rw-r--r-- | source/Host/common/Editline.cpp | 1735 | ||||
-rw-r--r-- | source/Host/common/File.cpp | 2 | ||||
-rw-r--r-- | source/Host/common/FileSpec.cpp | 189 | ||||
-rw-r--r-- | source/Host/common/Host.cpp | 471 | ||||
-rw-r--r-- | source/Host/common/HostInfoBase.cpp | 36 | ||||
-rw-r--r-- | source/Host/common/HostNativeThreadBase.cpp | 82 | ||||
-rw-r--r-- | source/Host/common/HostProcess.cpp | 65 | ||||
-rw-r--r-- | source/Host/common/HostThread.cpp | 78 | ||||
-rw-r--r-- | source/Host/common/MonitoringProcessLauncher.cpp | 102 | ||||
-rw-r--r-- | source/Host/common/NativeProcessProtocol.cpp | 23 | ||||
-rw-r--r-- | source/Host/common/NativeProcessProtocol.h | 23 | ||||
-rw-r--r-- | source/Host/common/NativeThreadProtocol.h | 2 | ||||
-rw-r--r-- | source/Host/common/Pipe.cpp | 171 | ||||
-rw-r--r-- | source/Host/common/PipeBase.cpp | 27 | ||||
-rw-r--r-- | source/Host/common/Socket.cpp | 78 | ||||
-rw-r--r-- | source/Host/common/SocketAddress.cpp | 10 | ||||
-rw-r--r-- | source/Host/common/SoftwareBreakpoint.cpp | 19 | ||||
-rw-r--r-- | source/Host/common/ThisThread.cpp | 52 | ||||
-rw-r--r-- | source/Host/common/ThreadLauncher.cpp | 74 |
19 files changed, 2019 insertions, 1220 deletions
diff --git a/source/Host/common/Editline.cpp b/source/Host/common/Editline.cpp index 7af9f39a78636..b82fbea90c6ca 100644 --- a/source/Host/common/Editline.cpp +++ b/source/Host/common/Editline.cpp @@ -7,527 +7,913 @@ // //===----------------------------------------------------------------------===// +#include <iomanip> +#include <iostream> +#include <limits.h> #include "lldb/Host/Editline.h" - +#include "lldb/Host/ConnectionFileDescriptor.h" #include "lldb/Core/Error.h" -#include "lldb/Core/StreamString.h" #include "lldb/Core/StringList.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Host/FileSpec.h" +#include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" +#include "lldb/Host/Mutex.h" -#include <limits.h> - -using namespace lldb; using namespace lldb_private; +using namespace lldb_private::line_editor; -namespace lldb_private { - typedef std::weak_ptr<EditlineHistory> EditlineHistoryWP; - - - // EditlineHistory objects are sometimes shared between multiple - // Editline instances with the same program name. This class allows - // multiple editline instances to - // - - class EditlineHistory - { - private: - // Use static GetHistory() function to get a EditlineHistorySP to one of these objects - EditlineHistory(const std::string &prefix, uint32_t size, bool unique_entries) : - m_history (NULL), - m_event (), - m_prefix (prefix), - m_path () +// Editline uses careful cursor management to achieve the illusion of editing a multi-line block of text +// with a single line editor. Preserving this illusion requires fairly careful management of cursor +// state. Read and understand the relationship between DisplayInput(), MoveCursor(), SetCurrentLine(), +// and SaveEditedLine() before making changes. + +#define ESCAPE "\x1b" +#define ANSI_FAINT ESCAPE "[2m" +#define ANSI_UNFAINT ESCAPE "[22m" +#define ANSI_CLEAR_BELOW ESCAPE "[J" +#define ANSI_CLEAR_RIGHT ESCAPE "[K" +#define ANSI_SET_COLUMN_N ESCAPE "[%dG" +#define ANSI_UP_N_ROWS ESCAPE "[%dA" +#define ANSI_DOWN_N_ROWS ESCAPE "[%dB" + +#if LLDB_EDITLINE_USE_WCHAR + +#define EditLineConstString(str) L##str +#define EditLineStringFormatSpec "%ls" + +#else + +#define EditLineConstString(str) str +#define EditLineStringFormatSpec "%s" + +// use #defines so wide version functions and structs will resolve to old versions +// for case of libedit not built with wide char support +#define history_w history +#define history_winit history_init +#define history_wend history_end +#define HistoryW History +#define HistEventW HistEvent +#define LineInfoW LineInfo + +#define el_wgets el_gets +#define el_wgetc el_getc +#define el_wpush el_push +#define el_wparse el_parse +#define el_wset el_set +#define el_wget el_get +#define el_wline el_line +#define el_winsertstr el_insertstr +#define el_wdeletestr el_deletestr + +#endif // #if LLDB_EDITLINE_USE_WCHAR + +bool +IsOnlySpaces (const EditLineStringType & content) +{ + for (wchar_t ch : content) + { + if (ch != EditLineCharType(' ')) + return false; + } + return true; +} + +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) { - m_history = ::history_init(); - ::history (m_history, &m_event, H_SETSIZE, size); - if (unique_entries) - ::history (m_history, &m_event, H_SETUNIQUE, 1); + result.insert (result.end(), input.substr (start)); + break; } + result.insert (result.end(), input.substr (start, end - start)); + start = end + 1; + } + return result; +} + +EditLineStringType +FixIndentation (const EditLineStringType & line, int indent_correction) +{ + if (indent_correction == 0) + return line; + if (indent_correction < 0) + return line.substr (-indent_correction); + return EditLineStringType (indent_correction, EditLineCharType(' ')) + line; +} + +int +GetIndentation (const EditLineStringType & line) +{ + int space_count = 0; + for (EditLineCharType ch : line) + { + if (ch != EditLineCharType(' ')) + break; + ++space_count; + } + return space_count; +} + +bool +IsInputPending (FILE * file) +{ + // FIXME: This will be broken on Windows if we ever re-enable Editline. You can't use select + // on something that isn't a socket. This will have to be re-written to not use a FILE*, but + // instead use some kind of yet-to-be-created abstraction that select-like functionality on + // non-socket objects. + const int fd = fileno (file); + fd_set fds; + FD_ZERO (&fds); + FD_SET (fd, &fds); + timeval timeout = { 0, 0 }; + return select (fd + 1, &fds, NULL, NULL, &timeout); +} + +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. - const char * - GetHistoryFilePath() + class EditlineHistory { - if (m_path.empty() && m_history && !m_prefix.empty()) + private: + // Use static GetHistory() function to get a EditlineHistorySP to one of these objects + EditlineHistory (const std::string &prefix, uint32_t size, bool unique_entries) : + m_history (NULL), + m_event (), + m_prefix (prefix), + m_path () { - char history_path[PATH_MAX]; - ::snprintf (history_path, sizeof(history_path), "~/.%s-history", m_prefix.c_str()); - m_path = std::move(FileSpec(history_path, true).GetPath()); + 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() + { + if (m_path.empty() && m_history && !m_prefix.empty()) + { + std::string parent_path = FileSpec ("~/.lldb", true).GetPath(); + char history_path[PATH_MAX]; + if (FileSystem::MakeDirectory(parent_path.c_str(), lldb::eFilePermissionsDirectoryDefault).Success()) + { + snprintf (history_path, sizeof (history_path), "~/.lldb/%s-history", m_prefix.c_str()); + } + else + { + snprintf (history_path, sizeof (history_path), "~/%s-widehistory", m_prefix.c_str()); + } + m_path = std::move (FileSpec (history_path, true).GetPath()); + } + if (m_path.empty()) + return NULL; + return m_path.c_str(); } - if (m_path.empty()) - return NULL; - return m_path.c_str(); - } - - public: - - ~EditlineHistory() - { - Save (); - if (m_history) + public: + + ~EditlineHistory() { - ::history_end (m_history); - m_history = NULL; + Save(); + + if (m_history) + { + history_wend (m_history); + m_history = NULL; + } } - } - - static EditlineHistorySP - GetHistory (const std::string &prefix) - { - typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; - static Mutex g_mutex(Mutex::eMutexTypeRecursive); - static WeakHistoryMap g_weak_map; - Mutex::Locker locker (g_mutex); - WeakHistoryMap::const_iterator pos = g_weak_map.find (prefix); - EditlineHistorySP history_sp; - if (pos != g_weak_map.end()) + + static EditlineHistorySP + GetHistory (const std::string &prefix) { - history_sp = pos->second.lock(); - if (history_sp) - return history_sp; - g_weak_map.erase(pos); + typedef std::map<std::string, EditlineHistoryWP> WeakHistoryMap; + static Mutex g_mutex (Mutex::eMutexTypeRecursive); + static WeakHistoryMap g_weak_map; + Mutex::Locker locker (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; } - history_sp.reset(new EditlineHistory(prefix, 800, true)); - g_weak_map[prefix] = history_sp; - return history_sp; - } - - bool IsValid() const - { - return m_history != NULL; - } - - ::History * - GetHistoryPtr () - { - return m_history; - } - - void - Enter (const char *line_cstr) - { - if (m_history) - ::history (m_history, &m_event, H_ENTER, line_cstr); - } - - bool - Load () - { - if (m_history) + + bool IsValid() const { - const char *path = GetHistoryFilePath(); - if (path) + return m_history != NULL; + } + + 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) { - ::history (m_history, &m_event, H_LOAD, path); - return true; + const char *path = GetHistoryFilePath(); + if (path) + { + history_w (m_history, &m_event, H_LOAD, path); + return true; + } } + return false; } - return false; - } - - bool - Save () - { - if (m_history) + + bool + Save () { - const char *path = GetHistoryFilePath(); - if (path) + if (m_history) { - ::history (m_history, &m_event, H_SAVE, path); - return true; + const char *path = GetHistoryFilePath(); + if (path) + { + history_w (m_history, &m_event, H_SAVE, path); + return true; + } } + return false; } - return false; - } - - protected: - ::History *m_history; // The history object - ::HistEvent m_event;// The history event needed to contain all history events - std::string m_prefix; // The prefix name (usually the editline program name) to use when loading/saving history - std::string m_path; // Path to the history file - }; + + protected: + HistoryW * m_history; // The history object + HistEventW m_event; // The history event needed to contain all history events + std::string m_prefix; // The prefix name (usually the editline program name) to use when loading/saving history + std::string m_path; // Path to the history file + }; + } } +//------------------------------------------------------------------ +// Editline private methods +//------------------------------------------------------------------ -static const char k_prompt_escape_char = '\1'; - -Editline::Editline (const char *prog, // prog can't be NULL - const char *prompt, // can be NULL for no prompt - bool configure_for_multiline, - FILE *fin, - FILE *fout, - FILE *ferr) : - m_editline (NULL), - m_history_sp (), - m_prompt (), - m_lines_prompt (), - m_getting_char (false), - m_completion_callback (NULL), - m_completion_callback_baton (NULL), - m_line_complete_callback (NULL), - m_line_complete_callback_baton (NULL), - m_lines_command (Command::None), - m_line_offset (0), - m_lines_curr_line (0), - m_lines_max_line (0), - m_file (fileno(fin), false), - m_prompt_with_line_numbers (false), - m_getting_line (false), - m_got_eof (false), - m_interrupted (false) -{ - if (prog && prog[0]) - { - m_editline = ::el_init(prog, fin, fout, ferr); - - // Get a shared history instance - m_history_sp = EditlineHistory::GetHistory(prog); +void +Editline::SetBaseLineNumber (int line_number) +{ + std::stringstream line_number_stream; + line_number_stream << line_number; + m_base_line_number = line_number; + m_line_number_digits = std::max (3, (int)line_number_stream.str().length() + 1); +} + +std::string +Editline::PromptForIndex (int line_index) +{ + bool use_line_numbers = m_multiline_enabled && m_base_line_number > 0; + std::string prompt = m_set_prompt; + if (use_line_numbers && prompt.length() == 0) + { + prompt = ": "; } - else + std::string continuation_prompt = prompt; + if (m_set_continuation_prompt.length() > 0) { - m_editline = ::el_init("lldb-tmp", fin, fout, ferr); + continuation_prompt = m_set_continuation_prompt; + + // Ensure that both prompts are the same length through space padding + while (continuation_prompt.length() < prompt.length()) + { + continuation_prompt += ' '; + } + while (prompt.length() < continuation_prompt.length()) + { + prompt += ' '; + } } - if (prompt && prompt[0]) - SetPrompt (prompt); + if (use_line_numbers) + { + StreamString prompt_stream; + prompt_stream.Printf("%*d%s", m_line_number_digits, m_base_line_number + line_index, + (line_index == 0) ? prompt.c_str() : continuation_prompt.c_str()); + return std::move (prompt_stream.GetString()); + } + return (line_index == 0) ? prompt : continuation_prompt; +} - //::el_set (m_editline, EL_BIND, "^[[A", NULL); // Print binding for up arrow key - //::el_set (m_editline, EL_BIND, "^[[B", NULL); // Print binding for up down key +void +Editline::SetCurrentLine (int line_index) +{ + m_current_line_index = line_index; + m_current_prompt = PromptForIndex (line_index); +} + +int +Editline::GetPromptWidth() +{ + return (int)PromptForIndex (0).length(); +} - assert (m_editline); - ::el_set (m_editline, EL_CLIENTDATA, this); +bool +Editline::IsEmacs() +{ + const char * editor; + el_get (m_editline, EL_EDITOR, &editor); + return editor[0] == 'e'; +} - // only defined for newer versions of editline -#ifdef EL_PROMPT_ESC - ::el_set (m_editline, EL_PROMPT_ESC, GetPromptCallback, k_prompt_escape_char); -#else - // fall back on old prompt setting code - ::el_set (m_editline, EL_PROMPT, GetPromptCallback); -#endif - ::el_set (m_editline, EL_EDITOR, "emacs"); - if (m_history_sp && m_history_sp->IsValid()) +bool +Editline::IsOnlySpaces() +{ + const LineInfoW * info = el_wline (m_editline); + for (const EditLineCharType * character = info->buffer; character < info->lastchar; character++) { - ::el_set (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); + if (*character != ' ') + return false; } - ::el_set (m_editline, EL_ADDFN, "lldb-complete", "Editline completion function", Editline::CallbackComplete); - // Keep old "lldb_complete" mapping for older clients that used this in their .editrc. editline also - // has a bad bug where if you have a bind command that tries to bind to a function name that doesn't - // exist, it will corrupt the heap and probably crash your process later. - ::el_set (m_editline, EL_ADDFN, "lldb_complete", "Editline completion function", Editline::CallbackComplete); - ::el_set (m_editline, EL_ADDFN, "lldb-edit-prev-line", "Editline edit prev line", Editline::CallbackEditPrevLine); - ::el_set (m_editline, EL_ADDFN, "lldb-edit-next-line", "Editline edit next line", Editline::CallbackEditNextLine); + return true; +} - ::el_set (m_editline, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string - ::el_set (m_editline, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash does. - ::el_set (m_editline, EL_BIND, "\033[3~", "ed-delete-next-char", NULL); // Fix the delete key. - ::el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to be auto complete - - if (configure_for_multiline) +int +Editline::GetLineIndexForLocation (CursorLocation location, int cursor_row) +{ + int line = 0; + if (location == CursorLocation::EditingPrompt || location == CursorLocation::BlockEnd || + location == CursorLocation::EditingCursor) { - // Use escape sequences for control characters due to bugs in editline - // where "-k up" and "-k down" don't always work. - ::el_set (m_editline, EL_BIND, "^[[A", "lldb-edit-prev-line", NULL); // Map up arrow - ::el_set (m_editline, EL_BIND, "^[[B", "lldb-edit-next-line", NULL); // Map down arrow - // Bindings for next/prev history - ::el_set (m_editline, EL_BIND, "^P", "ed-prev-history", NULL); // Map up arrow - ::el_set (m_editline, EL_BIND, "^N", "ed-next-history", NULL); // Map down arrow + 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; + } } - else + 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) { - // Use escape sequences for control characters due to bugs in editline - // where "-k up" and "-k down" don't always work. - ::el_set (m_editline, EL_BIND, "^[[A", "ed-prev-history", NULL); // Map up arrow - ::el_set (m_editline, EL_BIND, "^[[B", "ed-next-history", NULL); // Map down arrow + fprintf (m_output_file, (toLine > fromLine) ? ANSI_DOWN_N_ROWS : ANSI_UP_N_ROWS, std::abs (toLine - fromLine)); } - // Source $PWD/.editrc then $HOME/.editrc - ::el_source (m_editline, NULL); - - // Always read through our callback function so we don't read - // stuff we aren't supposed to. This also stops the extra echoing - // that can happen when you have more input than editline can handle - // at once. - SetGetCharCallback(GetCharFromInputFileCallback); - - LoadHistory(); + // 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) + { + toColumn = ((m_input_lines[m_input_lines.size() - 1].length() + GetPromptWidth()) % 80) + 1; + } + fprintf (m_output_file, ANSI_SET_COLUMN_N, toColumn); } -Editline::~Editline() +void +Editline::DisplayInput (int firstIndex) { - // 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(); + fprintf (m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1); + int line_count = (int)m_input_lines.size(); + const char *faint = m_color_prompts ? ANSI_FAINT : ""; + const char *unfaint = m_color_prompts ? ANSI_UNFAINT : ""; - // 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); + for (int index = firstIndex; index < line_count; index++) + { + fprintf (m_output_file, "%s" "%s" "%s" EditLineStringFormatSpec " ", + faint, + PromptForIndex (index).c_str(), + unfaint, + m_input_lines[index].c_str()); + if (index < line_count - 1) + fprintf (m_output_file, "\n"); + } +} - ::el_end(m_editline); - m_editline = NULL; + +int +Editline::CountRowsForLine (const EditLineStringType & content) +{ + auto prompt = PromptForIndex (0); // Prompt width is constant during an edit session + int line_length = (int)(content.length() + prompt.length()); + return (line_length / m_terminal_width) + 1; } void -Editline::SetGetCharCallback (GetCharCallbackType callback) +Editline::SaveEditedLine() { - ::el_set (m_editline, EL_GETCFN, callback); + const LineInfoW * info = el_wline (m_editline); + m_input_lines[m_current_line_index] = EditLineStringType (info->buffer, info->lastchar - info->buffer); } -bool -Editline::LoadHistory () +StringList +Editline::GetInputAsStringList(int line_count) { - if (m_history_sp) - return m_history_sp->Load(); - return false; + 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; } -bool -Editline::SaveHistory () +unsigned char +Editline::RecallHistory (bool earlier) { - if (m_history_sp) - return m_history_sp->Save(); - return false; -} + 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) + { + if (earlier == false) + return CC_ERROR; // Can't go newer than the "live" entry + if (history_w (pHistory, &history_event, H_FIRST) == -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; + } + else + { + if (history_w (pHistory, &history_event, earlier ? H_NEXT : H_PREV) == -1) + { + // Can't move earlier than the earliest entry + if (earlier) + return CC_ERROR; + // ... but moving to newer than the newest yields the "live" entry + new_input_lines = m_live_history_lines; + m_in_history = false; + } + } + + // If we're pulling the lines from history, split them apart + if (m_in_history) + new_input_lines = SplitLines (history_event.str); -Error -Editline::PrivateGetLine(std::string &line) + // 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 + SetCurrentLine (m_current_line_index = earlier ? (int)m_input_lines.size() - 1 : 0); + MoveCursor (CursorLocation::BlockEnd, CursorLocation::EditingPrompt); + return CC_NEWLINE; +} + +int +Editline::GetCharacter (EditLineCharType * c) { - Error error; - if (m_interrupted) + const LineInfoW * info = el_wline (m_editline); + + // Paint a faint version of the desired prompt over the version libedit draws + // (will only be requested if colors are supported) + if (m_needs_prompt_repaint) { - error.SetErrorString("interrupted"); - return error; + MoveCursor (CursorLocation::EditingCursor, CursorLocation::EditingPrompt); + fprintf (m_output_file, "%s" "%s" "%s", ANSI_FAINT, Prompt(), ANSI_UNFAINT); + MoveCursor (CursorLocation::EditingPrompt, CursorLocation::EditingCursor); + m_needs_prompt_repaint = false; } - line.clear(); - if (m_editline != NULL) + if (m_multiline_enabled) { - int line_len = 0; - // Call el_gets to prompt the user and read the user's input. - const char *line_cstr = ::el_gets (m_editline, &line_len); - - static int save_errno = (line_len < 0) ? errno : 0; - - if (save_errno != 0) + // 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) { - error.SetError(save_errno, eErrorTypePOSIX); + // 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); } - else if (line_cstr) + m_current_line_rows = new_line_rows; + } + + // Read an actual character + while (true) + { + lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; + char ch = 0; + m_editor_getting_char = true; + int read_count = m_input_connection.Read(&ch, 1, UINT32_MAX, status, NULL); + m_editor_getting_char = false; + if (read_count) { - // Decrement the length so we don't have newline characters in "line" for when - // we assign the cstr into the std::string - llvm::StringRef line_ref (line_cstr); - line_ref = line_ref.rtrim("\n\r"); - - if (!line_ref.empty() && !m_interrupted) +#if LLDB_EDITLINE_USE_WCHAR + // After the initial interruptible read, this is guaranteed not to block + ungetc (ch, m_input_file); + *c = fgetwc (m_input_file); + if (*c != WEOF) + return 1; +#else + *c = ch; + if(*c != EOF) + return 1; +#endif + } + else + { + switch (status) { - // We didn't strip the newlines, we just adjusted the length, and - // we want to add the history item with the newlines - if (m_history_sp) - m_history_sp->Enter(line_cstr); - - // Copy the part of the c string that we want (removing the newline chars) - line = std::move(line_ref.str()); + case lldb::eConnectionStatusInterrupted: + m_editor_status = EditorStatus::Interrupted; + printf ("^C\n"); + return 0; + + case lldb::eConnectionStatusSuccess: // Success + break; + + 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; } } } - else - { - error.SetErrorString("the EditLine instance has been deleted"); - } - return error; } +const char * +Editline::Prompt() +{ + if (m_color_prompts) + m_needs_prompt_repaint = true; + return m_current_prompt.c_str(); +} -Error -Editline::GetLine(std::string &line, bool &interrupted) +unsigned char +Editline::BreakLineCommand (int ch) { - Error error; - interrupted = false; - line.clear(); + // 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(""); - // Set arrow key bindings for up and down arrows for single line - // mode where up and down arrows do prev/next history - m_interrupted = false; + // Establish the new cursor position at the start of a line when inserting a line break + m_revert_cursor_index = 0; - if (!m_got_eof) + // Don't perform end of input detection or automatic formatting when pasting + if (!IsInputPending (m_input_file)) { - if (m_getting_line) + // If this is the end of the last line, treat this as a potential exit + if (m_current_line_index == m_input_lines.size() - 1 && new_line_fragment.length() == 0) { - error.SetErrorString("already getting a line"); - return error; + bool end_of_input = true; + if (m_is_input_complete_callback) + { + SaveEditedLine(); + auto lines = GetInputAsStringList(); + end_of_input = m_is_input_complete_callback (this, lines, m_is_input_complete_callback_baton); + + // The completion test is allowed to change the input lines when complete + if (end_of_input) + { + 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 + } + } + } + if (end_of_input) + { + fprintf (m_output_file, "\n"); + m_editor_status = EditorStatus::Complete; + return CC_NEWLINE; + } } - if (m_lines_curr_line > 0) + + // Apply smart indentation + if (m_fix_indentation_callback) { - error.SetErrorString("already getting lines"); - return error; + StringList lines = GetInputAsStringList (m_current_line_index + 1); +#if LLDB_EDITLINE_USE_WCHAR + lines.AppendString (m_utf8conv.to_bytes (new_line_fragment)); +#else + lines.AppendString (new_line_fragment); +#endif + + int indent_correction = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton); + new_line_fragment = FixIndentation(new_line_fragment, indent_correction); + m_revert_cursor_index = GetIndentation(new_line_fragment); } - m_getting_line = true; - error = PrivateGetLine(line); - m_getting_line = false; } + + // 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; +} - interrupted = m_interrupted; - - if (m_got_eof && line.empty()) +unsigned char +Editline::DeleteNextCharCommand (int ch) +{ + LineInfoW * info = (LineInfoW *)el_wline (m_editline); + + // Just delete the next character normally if possible + if (info->cursor < info->lastchar) { - // Only set the error if we didn't get an error back from PrivateGetLine() - if (error.Success()) - error.SetErrorString("end of file"); + info->cursor++; + el_deletestr (m_editline, 1); + return CC_REFRESH; } - return error; + // 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; } -size_t -Editline::Push (const char *bytes, size_t len) +unsigned char +Editline::DeletePreviousCharCommand (int ch) { - if (m_editline) + LineInfoW * info = (LineInfoW *)el_wline (m_editline); + + // Just delete the previous character normally when not at the start of a line + if (info->cursor > info->buffer) { - // Must NULL terminate the string for el_push() so we stick it - // into a std::string first - ::el_push(m_editline, - const_cast<char*>(std::string (bytes, len).c_str())); - return len; + el_deletestr (m_editline, 1); + return CC_REFRESH; } - return 0; + + // 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; } - -Error -Editline::GetLines(const std::string &end_line, StringList &lines, bool &interrupted) +unsigned char +Editline::PreviousLineCommand (int ch) { - Error error; - interrupted = false; - if (m_getting_line) - { - error.SetErrorString("already getting a line"); - return error; + SaveEditedLine(); + + if (m_current_line_index == 0) { + return RecallHistory (true); } - if (m_lines_curr_line > 0) + + // 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()) { - error.SetErrorString("already getting lines"); - return error; + m_input_lines.erase (m_input_lines.begin() + m_current_line_index); + fprintf (m_output_file, ANSI_CLEAR_BELOW); } - // Set arrow key bindings for up and down arrows for multiple line - // mode where up and down arrows do edit prev/next line - m_interrupted = false; - - LineStatus line_status = LineStatus::Success; + 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; +} - lines.Clear(); +unsigned char +Editline::NextLineCommand (int ch) +{ + SaveEditedLine(); - FILE *out_file = GetOutputFile(); - FILE *err_file = GetErrorFile(); - m_lines_curr_line = 1; - while (line_status != LineStatus::Done) + // Handle attempts to move down from the last line + if (m_current_line_index == m_input_lines.size() - 1) { - const uint32_t line_idx = m_lines_curr_line-1; - if (line_idx >= lines.GetSize()) - lines.SetSize(m_lines_curr_line); - m_lines_max_line = lines.GetSize(); - m_lines_command = Command::None; - assert(line_idx < m_lines_max_line); - std::string &line = lines[line_idx]; - error = PrivateGetLine(line); - if (error.Fail()) + // Don't add an extra line if the existing last line is blank, move through history instead + if (IsOnlySpaces()) { - line_status = LineStatus::Error; + return RecallHistory (false); } - else if (m_interrupted) + + // Determine indentation for the new line + int indentation = 0; + if (m_fix_indentation_callback) { - interrupted = true; - line_status = LineStatus::Done; + StringList lines = GetInputAsStringList(); + lines.AppendString(""); + indentation = m_fix_indentation_callback (this, lines, 0, m_fix_indentation_callback_baton); } - else - { - switch (m_lines_command) - { - case Command::None: - if (m_line_complete_callback) - { - line_status = m_line_complete_callback (this, - lines, - line_idx, - error, - m_line_complete_callback_baton); - } - else if (line == end_line) - { - line_status = LineStatus::Done; - } + 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; +} - if (line_status == LineStatus::Success) - { - ++m_lines_curr_line; - // If we already have content for the next line because - // we were editing previous lines, then populate the line - // with the appropriate contents - if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) - ::el_push (m_editline, - const_cast<char*>(lines[line_idx+1].c_str())); - } - else if (line_status == LineStatus::Error) - { - // Clear to end of line ("ESC[K"), then print the error, - // then go to the next line ("\n") and then move cursor up - // two lines ("ESC[2A"). - fprintf (err_file, "\033[Kerror: %s\n\033[2A", error.AsCString()); - } - break; - case Command::EditPrevLine: - if (m_lines_curr_line > 1) - { - //::fprintf (out_file, "\033[1A\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); // Make cursor go up a line and clear that line - ::fprintf (out_file, "\033[1A\033[1000D\033[2K"); - if (!lines[line_idx-1].empty()) - ::el_push (m_editline, - const_cast<char*>(lines[line_idx-1].c_str())); - --m_lines_curr_line; - } - break; - case Command::EditNextLine: - // Allow the down arrow to create a new line - ++m_lines_curr_line; - //::fprintf (out_file, "\033[1B\033[%uD\033[2K", (uint32_t)(m_lines_prompt.size() + lines[line_idx].size())); - ::fprintf (out_file, "\033[1B\033[1000D\033[2K"); - if (line_idx+1 < lines.GetSize() && !lines[line_idx+1].empty()) - ::el_push (m_editline, - const_cast<char*>(lines[line_idx+1].c_str())); - break; - } +unsigned char +Editline::FixIndentationCommand (int ch) +{ + if (!m_fix_indentation_callback) + return CC_NORM; + + // Insert the character by hand prior to correction + EditLineCharType inserted[] = { (EditLineCharType)ch, 0 }; + el_winsertstr (m_editline, inserted); + SaveEditedLine(); + StringList lines = GetInputAsStringList (m_current_line_index + 1); + + // Determine the cursor position + LineInfoW * info = (LineInfoW *)el_wline (m_editline); + int cursor_position = info->cursor - info->buffer; + + int indent_correction = m_fix_indentation_callback (this, lines, cursor_position, m_fix_indentation_callback_baton); + + // Adjust the input buffer to correct indentation + if (indent_correction > 0) + { + info->cursor = info->buffer; + el_winsertstr (m_editline, EditLineStringType (indent_correction, EditLineCharType(' ')).c_str()); + } + else if (indent_correction < 0) + { + info->cursor = info->buffer - indent_correction; + el_wdeletestr (m_editline, -indent_correction); + } + info->cursor = info->buffer + cursor_position + indent_correction; + return CC_REFRESH; +} + +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 = (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; } - m_lines_curr_line = 0; - m_lines_command = Command::None; + return CC_REFRESH; +} - // If we have a callback, call it one more time to let the - // user know the lines are complete - if (m_line_complete_callback && !interrupted) - m_line_complete_callback (this, - lines, - UINT32_MAX, - error, - m_line_complete_callback_baton); +unsigned char +Editline::BufferStartCommand (int ch) +{ + SaveEditedLine(); + MoveCursor (CursorLocation::EditingCursor, CursorLocation::BlockStart); + SetCurrentLine (0); + m_revert_cursor_index = 0; + return CC_NEWLINE; +} - return error; +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; } unsigned char -Editline::HandleCompletion (int ch) +Editline::TabCommand (int ch) { - if (m_completion_callback == NULL) + if (m_completion_callback == nullptr) return CC_ERROR; - - const LineInfo *line_info = ::el_line(m_editline); + + const LineInfo *line_info = el_line (m_editline); StringList completions; int page_size = 40; - + const int num_completions = m_completion_callback (line_info->buffer, line_info->cursor, line_info->lastchar, @@ -536,25 +922,25 @@ Editline::HandleCompletion (int ch) completions, m_completion_callback_baton); - FILE *out_file = GetOutputFile(); - -// if (num_completions == -1) -// { -// ::el_insertstr (m_editline, m_completion_key); -// return CC_REDISPLAY; -// } -// else + if (num_completions == 0) + return CC_ERROR; + // if (num_completions == -1) + // { + // el_insertstr (m_editline, m_completion_key); + // return CC_REDISPLAY; + // } + // else if (num_completions == -2) { // Replace the entire line with the first string... - ::el_deletestr (m_editline, line_info->cursor - line_info->buffer); - ::el_insertstr (m_editline, completions.GetStringAtIndex(0)); + el_deletestr (m_editline, line_info->cursor - line_info->buffer); + el_insertstr (m_editline, completions.GetStringAtIndex (0)); return CC_REDISPLAY; } // If we get a longer match display that first. - const char *completion_str = completions.GetStringAtIndex(0); - if (completion_str != NULL && *completion_str != '\0') + const char *completion_str = completions.GetStringAtIndex (0); + if (completion_str != nullptr && *completion_str != '\0') { el_insertstr (m_editline, completion_str); return CC_REDISPLAY; @@ -563,15 +949,15 @@ Editline::HandleCompletion (int ch) if (num_completions > 1) { int num_elements = num_completions + 1; - ::fprintf (out_file, "\nAvailable completions:"); + fprintf (m_output_file, "\n" ANSI_CLEAR_BELOW "Available completions:"); if (num_completions < page_size) { for (int i = 1; i < num_elements; i++) { - completion_str = completions.GetStringAtIndex(i); - ::fprintf (out_file, "\n\t%s", completion_str); + completion_str = completions.GetStringAtIndex (i); + fprintf (m_output_file, "\n\t%s", completion_str); } - ::fprintf (out_file, "\n"); + fprintf (m_output_file, "\n"); } else { @@ -585,17 +971,17 @@ Editline::HandleCompletion (int ch) endpoint = num_elements; for (; cur_pos < endpoint; cur_pos++) { - completion_str = completions.GetStringAtIndex(cur_pos); - ::fprintf (out_file, "\n\t%s", completion_str); + completion_str = completions.GetStringAtIndex (cur_pos); + fprintf (m_output_file, "\n\t%s", completion_str); } if (cur_pos >= num_elements) { - ::fprintf (out_file, "\n"); + fprintf (m_output_file, "\n"); break; } - ::fprintf (out_file, "\nMore (Y/n/a): "); + fprintf (m_output_file, "\nMore (Y/n/a): "); reply = 'n'; got_char = el_getc(m_editline, &reply); if (got_char == -1 || reply == 'n') @@ -604,247 +990,388 @@ Editline::HandleCompletion (int ch) page_size = num_elements - cur_pos; } } - + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); } - - if (num_completions == 0) - return CC_REFRESH_BEEP; - else - return CC_REDISPLAY; + return CC_REDISPLAY; } -Editline * -Editline::GetClientData (::EditLine *e) +void +Editline::ConfigureEditor (bool multiline) { - Editline *editline = NULL; - if (e && ::el_get(e, EL_CLIENTDATA, &editline) == 0) - return editline; - return NULL; -} + if (m_editline && m_multiline_enabled == multiline) + return; + m_multiline_enabled = multiline; + + if (m_editline) + { + // Disable edit mode to stop the terminal from flushing all input + // during the call to el_end() since we expect to have multiple editline + // instances in this program. + el_set (m_editline, EL_EDITMODE, 0); + el_end (m_editline); + } + + m_editline = el_init (m_editor_name.c_str(), m_input_file, m_output_file, m_error_file); + TerminalSizeChanged(); + + if (m_history_sp && m_history_sp->IsValid()) + { + m_history_sp->Load(); + el_wset (m_editline, EL_HIST, history, m_history_sp->GetHistoryPtr()); + } + el_set (m_editline, EL_CLIENTDATA, this); + el_set (m_editline, EL_SIGNAL, 0); + el_set (m_editline, EL_EDITOR, "emacs"); + el_set (m_editline, EL_PROMPT, (EditlinePromptCallbackType)([] (EditLine *editline) { + return Editline::InstanceFor (editline)->Prompt(); + })); -FILE * -Editline::GetInputFile () -{ - return GetFilePointer (m_editline, 0); + el_wset (m_editline, EL_GETCFN, + (EditlineGetCharCallbackType)([] (EditLine * editline, EditLineCharType * c) { + return Editline::InstanceFor (editline)->GetCharacter (c); + })); + + // Commands used for multiline support, registered whether or not they're used + el_set (m_editline, EL_ADDFN, "lldb-break-line", "Insert a line break", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->BreakLineCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-delete-next-char", "Delete next character", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->DeleteNextCharCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-delete-previous-char", "Delete previous character", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->DeletePreviousCharCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-previous-line", "Move to previous line", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->PreviousLineCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-next-line", "Move to next line", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->NextLineCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-buffer-start", "Move to start of buffer", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->BufferStartCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-buffer-end", "Move to end of buffer", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->BufferEndCommand (ch); + })); + el_set (m_editline, EL_ADDFN, "lldb-fix-indentation", "Fix line indentation", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->FixIndentationCommand (ch); + })); + + // Register the complete callback under two names for compatibility with older clients using + // custom .editrc files (largely becuase libedit has a bad bug where if you have a bind command + // that tries to bind to a function name that doesn't exist, it can corrupt the heap and + // crash your process later.) + EditlineCommandCallbackType complete_callback = [] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->TabCommand (ch); + }; + el_set (m_editline, EL_ADDFN, "lldb-complete", "Invoke completion", complete_callback); + el_set (m_editline, EL_ADDFN, "lldb_complete", "Invoke completion", complete_callback); + + // General bindings we don't mind being overridden + if (!multiline) { + el_set (m_editline, EL_BIND, "^r", "em-inc-search-prev", NULL); // Cycle through backwards search, entering string + } + el_set (m_editline, EL_BIND, "^w", "ed-delete-prev-word", NULL); // Delete previous word, behave like bash in emacs mode + el_set (m_editline, EL_BIND, "\t", "lldb-complete", NULL); // Bind TAB to auto complete + + // Allow user-specific customization prior to registering bindings we absolutely require + el_source (m_editline, NULL); + + // Register an internal binding that external developers shouldn't use + el_set (m_editline, EL_ADDFN, "lldb-revert-line", "Revert line to saved state", + (EditlineCommandCallbackType)([] (EditLine * editline, int ch) { + return Editline::InstanceFor (editline)->RevertLineCommand (ch); + })); + + // Register keys that perform auto-indent correction + if (m_fix_indentation_callback && m_fix_indentation_callback_chars) + { + char bind_key[2] = { 0, 0 }; + const char * indent_chars = m_fix_indentation_callback_chars; + while (*indent_chars) + { + bind_key[0] = *indent_chars; + el_set (m_editline, EL_BIND, bind_key, "lldb-fix-indentation", NULL); + ++indent_chars; + } + } + + // Multi-line editor bindings + if (multiline) + { + el_set (m_editline, EL_BIND, "\n", "lldb-break-line", NULL); + el_set (m_editline, EL_BIND, "\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); + } + 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); + } + } } -FILE * -Editline::GetOutputFile () +//------------------------------------------------------------------ +// Editline public methods +//------------------------------------------------------------------ + +Editline * +Editline::InstanceFor (EditLine * editline) { - return GetFilePointer (m_editline, 1); + Editline * editor; + el_get (editline, EL_CLIENTDATA, &editor); + return editor; } -FILE * -Editline::GetErrorFile () +Editline::Editline (const char * editline_name, FILE * input_file, FILE * output_file, FILE * error_file, bool color_prompts) : + m_editor_status (EditorStatus::Complete), + m_color_prompts(color_prompts), + m_input_file (input_file), + m_output_file (output_file), + m_error_file (error_file), + m_input_connection (fileno(input_file), false) { - return GetFilePointer (m_editline, 2); + // Get a shared history instance + m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name; + m_history_sp = EditlineHistory::GetHistory (m_editor_name); } -const char * -Editline::GetPrompt() +Editline::~Editline() { - if (m_prompt_with_line_numbers && m_lines_curr_line > 0) + if (m_editline) { - StreamString strm; - strm.Printf("%3u: ", m_lines_curr_line); - m_lines_prompt = std::move(strm.GetString()); - return m_lines_prompt.c_str(); - } - else - { - return m_prompt.c_str(); + // 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 *p) +Editline::SetPrompt (const char * prompt) { - if (p && p[0]) - m_prompt = p; - else - m_prompt.clear(); - size_t start_pos = 0; - size_t escape_pos; - while ((escape_pos = m_prompt.find('\033', start_pos)) != std::string::npos) - { - m_prompt.insert(escape_pos, 1, k_prompt_escape_char); - start_pos += 2; - } + m_set_prompt = prompt == nullptr ? "" : prompt; } -FILE * -Editline::GetFilePointer (::EditLine *e, int fd) +void +Editline::SetContinuationPrompt (const char * continuation_prompt) { - FILE *file_ptr = NULL; - if (e && ::el_get(e, EL_GETFP, fd, &file_ptr) == 0) - return file_ptr; - return NULL; + m_set_continuation_prompt = continuation_prompt == nullptr ? "" : continuation_prompt; } -unsigned char -Editline::CallbackEditPrevLine (::EditLine *e, int ch) +void +Editline::TerminalSizeChanged() { - Editline *editline = GetClientData (e); - if (editline->m_lines_curr_line > 1) + if (m_editline != nullptr) { - editline->m_lines_command = Command::EditPrevLine; - return CC_NEWLINE; + el_resize (m_editline); + int columns; + // Despite the man page claiming non-zero indicates success, it's actually zero + if (el_get (m_editline, EL_GETTC, "co", &columns) == 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; + } } - return CC_ERROR; } -unsigned char -Editline::CallbackEditNextLine (::EditLine *e, int ch) + +const char * +Editline::GetPrompt() { - Editline *editline = GetClientData (e); - if (editline->m_lines_curr_line < editline->m_lines_max_line) - { - editline->m_lines_command = Command::EditNextLine; - return CC_NEWLINE; - } - return CC_ERROR; + return m_set_prompt.c_str(); } -unsigned char -Editline::CallbackComplete (::EditLine *e, int ch) +uint32_t +Editline::GetCurrentLine() { - Editline *editline = GetClientData (e); - if (editline) - return editline->HandleCompletion (ch); - return CC_ERROR; + return m_current_line_index; } -const char * -Editline::GetPromptCallback (::EditLine *e) +void +Editline::Hide() { - Editline *editline = GetClientData (e); - if (editline) - return editline->GetPrompt(); - return ""; + // Make sure we're at a stable location waiting for input + while (m_editor_status == EditorStatus::Editing && !m_editor_getting_char) + { + usleep(100000); + } + + // Clear the existing input + if (m_editor_status == EditorStatus::Editing) + { + MoveCursor(CursorLocation::EditingCursor, CursorLocation::BlockStart); + fprintf(m_output_file, ANSI_CLEAR_BELOW); + } } -int -Editline::GetCharFromInputFileCallback (EditLine *e, char *c) +void +Editline::Refresh() { - Editline *editline = GetClientData (e); - if (editline && editline->m_got_eof == false) + if (m_editor_status == EditorStatus::Editing) { - FILE *f = editline->GetInputFile(); - if (f == NULL) - { - editline->m_got_eof = true; - return 0; - } - - - while (1) - { - lldb::ConnectionStatus status = eConnectionStatusSuccess; - char ch = 0; - // When we start to call el_gets() the editline library needs to - // output the prompt - editline->m_getting_char.SetValue(true, eBroadcastAlways); - const size_t n = editline->m_file.Read(&ch, 1, UINT32_MAX, status, NULL); - editline->m_getting_char.SetValue(false, eBroadcastAlways); - if (n) - { - if (ch == '\x04') - { - // Only turn a CTRL+D into a EOF if we receive the - // CTRL+D an empty line, otherwise it will forward - // delete the character at the cursor - const LineInfo *line_info = ::el_line(e); - if (line_info != NULL && - line_info->buffer == line_info->cursor && - line_info->cursor == line_info->lastchar) - { - editline->m_got_eof = true; - break; - } - } - - if (status == eConnectionStatusEndOfFile) - { - editline->m_got_eof = true; - break; - } - else - { - *c = ch; - return 1; - } - } - else - { - switch (status) - { - case eConnectionStatusInterrupted: - editline->m_interrupted = true; - *c = '\n'; - return 1; - - case eConnectionStatusSuccess: // Success - break; - - case eConnectionStatusError: // Check GetError() for details - case eConnectionStatusTimedOut: // Request timed out - case eConnectionStatusEndOfFile: // End-of-file encountered - case eConnectionStatusNoConnection: // No connection - case eConnectionStatusLostConnection: // Lost connection while connected to a valid connection - editline->m_got_eof = true; - break; - } - } - } + DisplayInput(); + MoveCursor(CursorLocation::BlockEnd, CursorLocation::EditingCursor); } - return 0; } -void -Editline::Hide () +bool +Editline::Interrupt() { - if (m_getting_line) + if (m_editor_status == EditorStatus::Editing) { - // If we are getting a line, we might have started to call el_gets() and - // it might be printing the prompt. Here we make sure we are actually getting - // a character. This way we know the entire prompt has been printed. - TimeValue timeout = TimeValue::Now(); - timeout.OffsetWithSeconds(1); - if (m_getting_char.WaitForValueEqualTo(true, &timeout)) - { - FILE *out_file = GetOutputFile(); - if (out_file) - { - const LineInfo *line_info = ::el_line(m_editline); - if (line_info) - ::fprintf (out_file, "\033[%uD\033[K", (uint32_t)(strlen(GetPrompt()) + line_info->cursor - line_info->buffer)); - } - } + return m_input_connection.InterruptRead(); } + return false; // Interrupt not handled as we weren't getting a line or lines } +void +Editline::SetAutoCompleteCallback (CompleteCallbackType callback, void * baton) +{ + m_completion_callback = callback; + m_completion_callback_baton = baton; +} void -Editline::Refresh() +Editline::SetIsInputCompleteCallback (IsInputCompleteCallbackType callback, void * baton) +{ + m_is_input_complete_callback = callback; + m_is_input_complete_callback_baton = baton; +} + +bool +Editline::SetFixIndentationCallback (FixIndentationCallbackType callback, + void * baton, + const char * indent_chars) { - if (m_getting_line) + m_fix_indentation_callback = callback; + m_fix_indentation_callback_baton = baton; + m_fix_indentation_callback_chars = indent_chars; + return false; +} + +bool +Editline::GetLine (std::string &line, bool &interrupted) +{ + ConfigureEditor (false); + m_input_lines = std::vector<EditLineStringType>(); + m_input_lines.insert (m_input_lines.begin(), EditLineConstString("")); + + SetCurrentLine (0); + m_in_history = false; + m_editor_status = EditorStatus::Editing; + m_editor_getting_char = false; + m_revert_cursor_index = -1; + + int count; + auto input = el_wgets (m_editline, &count); + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) { - // If we are getting a line, we might have started to call el_gets() and - // it might be printing the prompt. Here we make sure we are actually getting - // a character. This way we know the entire prompt has been printed. - TimeValue timeout = TimeValue::Now(); - timeout.OffsetWithSeconds(1); - if (m_getting_char.WaitForValueEqualTo(true, &timeout)) + if (input == nullptr) { - ::el_set (m_editline, EL_REFRESH); + 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::Interrupt () +Editline::GetLines (int first_line_number, StringList &lines, bool &interrupted) { - m_interrupted = true; - if (m_getting_line || m_lines_curr_line > 0) - return m_file.InterruptRead(); - return false; // Interrupt not handled as we weren't getting a line or lines + 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("")); + + // Begin the line editing loop + DisplayInput(); + SetCurrentLine (0); + MoveCursor (CursorLocation::BlockEnd, CursorLocation::BlockStart); + m_editor_status = EditorStatus::Editing; + m_editor_getting_char = false; + m_in_history = false; + + m_revert_cursor_index = -1; + while (m_editor_status == EditorStatus::Editing) + { + int count; + m_current_line_rows = -1; + el_wpush (m_editline, EditLineConstString("\x1b[^")); // Revert to the existing line content + el_wgets (m_editline, &count); + } + + interrupted = m_editor_status == EditorStatus::Interrupted; + if (!interrupted) + { + // Save the completed entry in history before returning + m_history_sp->Enter (CombineLines (m_input_lines).c_str()); + + lines = GetInputAsStringList(); + } + return m_editor_status != EditorStatus::EndOfInput; } diff --git a/source/Host/common/File.cpp b/source/Host/common/File.cpp index 50513af2dc143..c3c77835ce86a 100644 --- a/source/Host/common/File.cpp +++ b/source/Host/common/File.cpp @@ -896,7 +896,7 @@ File::CalculateInteractiveAndTerminal () { m_is_interactive = eLazyBoolNo; m_is_real_terminal = eLazyBoolNo; -#ifdef _WIN32 +#if (defined(_WIN32) || defined(__ANDROID_NDK__)) if (_isatty(fd)) { m_is_interactive = eLazyBoolYes; diff --git a/source/Host/common/FileSpec.cpp b/source/Host/common/FileSpec.cpp index 8c4014c074f55..0af0556d30c9f 100644 --- a/source/Host/common/FileSpec.cpp +++ b/source/Host/common/FileSpec.cpp @@ -56,7 +56,8 @@ GetFileStats (const FileSpec *file_spec, struct stat *stats_ptr) } // Resolves the username part of a path of the form ~user/other/directories, and -// writes the result into dst_path. +// writes the result into dst_path. This will also resolve "~" to the current user. +// If you want to complete "~" to the list of users, pass it to ResolvePartialUsername. void FileSpec::ResolveUsername (llvm::SmallVectorImpl<char> &path) { @@ -66,9 +67,9 @@ FileSpec::ResolveUsername (llvm::SmallVectorImpl<char> &path) llvm::StringRef path_str(path.data()); size_t slash_pos = path_str.find_first_of("/", 1); - if (slash_pos == 1) + if (slash_pos == 1 || path.size() == 1) { - // A path of the form ~/ resolves to the current user's home dir + // A path of ~/ resolves to the current user's home dir llvm::SmallString<64> home_dir; if (!llvm::sys::path::home_directory(home_dir)) return; @@ -166,10 +167,10 @@ FileSpec::Resolve (llvm::SmallVectorImpl<char> &path) llvm::sys::fs::make_absolute(path); } -FileSpec::FileSpec() - : m_directory() - , m_filename() - , m_syntax(FileSystem::GetNativePathSyntax()) +FileSpec::FileSpec() : + m_directory(), + m_filename(), + m_syntax(FileSystem::GetNativePathSyntax()) { } @@ -180,7 +181,8 @@ FileSpec::FileSpec() FileSpec::FileSpec(const char *pathname, bool resolve_path, PathSyntax syntax) : m_directory(), m_filename(), - m_is_resolved(false) + m_is_resolved(false), + m_syntax(syntax) { if (pathname && pathname[0]) SetFile(pathname, resolve_path, syntax); @@ -266,14 +268,18 @@ FileSpec::SetFile (const char *pathname, bool resolve, PathSyntax syntax) return; llvm::SmallString<64> normalized(pathname); - Normalize(normalized, syntax); if (resolve) { FileSpec::Resolve (normalized); m_is_resolved = true; } - + + // Only normalize after resolving the path. Resolution will modify the path + // string, potentially adding wrong kinds of slashes to the path, that need + // to be re-normalized. + Normalize(normalized, syntax); + llvm::StringRef resolve_path_ref(normalized.c_str()); llvm::StringRef filename_ref = llvm::sys::path::filename(resolve_path_ref); if (!filename_ref.empty()) @@ -446,15 +452,129 @@ FileSpec::Compare(const FileSpec& a, const FileSpec& b, bool full) } bool -FileSpec::Equal (const FileSpec& a, const FileSpec& b, bool full) +FileSpec::Equal (const FileSpec& a, const FileSpec& b, bool full, bool remove_backups) { if (!full && (a.GetDirectory().IsEmpty() || b.GetDirectory().IsEmpty())) return a.m_filename == b.m_filename; - else + else if (remove_backups == false) return a == b; + else + { + if (a.m_filename != b.m_filename) + return false; + if (a.m_directory == b.m_directory) + return true; + ConstString a_without_dots; + ConstString b_without_dots; + + RemoveBackupDots (a.m_directory, a_without_dots); + RemoveBackupDots (b.m_directory, b_without_dots); + return a_without_dots == b_without_dots; + } } +void +FileSpec::RemoveBackupDots (const ConstString &input_const_str, ConstString &result_const_str) +{ + const char *input = input_const_str.GetCString(); + result_const_str.Clear(); + if (!input || input[0] == '\0') + return; + + const char win_sep = '\\'; + const char unix_sep = '/'; + char found_sep; + const char *win_backup = "\\.."; + const char *unix_backup = "/.."; + + bool is_win = false; + + // Determine the platform for the path (win or unix): + + if (input[0] == win_sep) + is_win = true; + else if (input[0] == unix_sep) + is_win = false; + else if (input[1] == ':') + is_win = true; + else if (strchr(input, unix_sep) != nullptr) + is_win = false; + else if (strchr(input, win_sep) != nullptr) + is_win = true; + else + { + // No separators at all, no reason to do any work here. + result_const_str = input_const_str; + return; + } + + llvm::StringRef backup_sep; + if (is_win) + { + found_sep = win_sep; + backup_sep = win_backup; + } + else + { + found_sep = unix_sep; + backup_sep = unix_backup; + } + + llvm::StringRef input_ref(input); + llvm::StringRef curpos(input); + + bool had_dots = false; + std::string result; + + while (1) + { + // Start of loop + llvm::StringRef before_sep; + std::pair<llvm::StringRef, llvm::StringRef> around_sep = curpos.split(backup_sep); + + before_sep = around_sep.first; + curpos = around_sep.second; + + if (curpos.empty()) + { + if (had_dots) + { + if (!before_sep.empty()) + { + result.append(before_sep.data(), before_sep.size()); + } + } + break; + } + had_dots = true; + unsigned num_backups = 1; + while (curpos.startswith(backup_sep)) + { + num_backups++; + curpos = curpos.slice(backup_sep.size(), curpos.size()); + } + + size_t end_pos = before_sep.size(); + while (num_backups-- > 0) + { + end_pos = before_sep.rfind(found_sep, end_pos); + if (end_pos == llvm::StringRef::npos) + { + result_const_str = input_const_str; + return; + } + } + result.append(before_sep.data(), end_pos); + } + + if (had_dots) + result_const_str.SetCString(result.c_str()); + else + result_const_str = input_const_str; + + return; +} //------------------------------------------------------------------ // Dump the object to the supplied stream. If the object contains @@ -502,7 +622,10 @@ FileSpec::ResolveExecutableLocation () if (file_cstr) { const std::string file_str (file_cstr); - std::string path = llvm::sys::FindProgramByName (file_str); + llvm::ErrorOr<std::string> error_or_path = llvm::sys::findProgramByName (file_str); + if (!error_or_path) + return false; + std::string path = error_or_path.get(); llvm::StringRef dir_ref = llvm::sys::path::parent_path(path); if (!dir_ref.empty()) { @@ -946,6 +1069,8 @@ FileSpec::EnumerateDirectory lldb_utility::CleanUp <DIR *, int> dir_path_dir(opendir(dir_path), NULL, closedir); if (dir_path_dir.is_valid()) { + char dir_path_last_char = dir_path[strlen(dir_path) - 1]; + long path_max = fpathconf (dirfd (dir_path_dir.get()), _PC_NAME_MAX); #if defined (__APPLE_) && defined (__DARWIN_MAXPATHLEN) if (path_max < __DARWIN_MAXPATHLEN) @@ -990,7 +1115,14 @@ FileSpec::EnumerateDirectory if (call_callback) { char child_path[PATH_MAX]; - const int child_path_len = ::snprintf (child_path, sizeof(child_path), "%s/%s", dir_path, dp->d_name); + + // Don't make paths with "/foo//bar", that just confuses everybody. + int child_path_len; + if (dir_path_last_char == '/') + child_path_len = ::snprintf (child_path, sizeof(child_path), "%s%s", dir_path, dp->d_name); + else + child_path_len = ::snprintf (child_path, sizeof(child_path), "%s/%s", dir_path, dp->d_name); + if (child_path_len < (int)(sizeof(child_path) - 1)) { // Don't resolve the file type or path @@ -1208,18 +1340,31 @@ FileSpec::IsSourceImplementationFile () const bool FileSpec::IsRelativeToCurrentWorkingDirectory () const { - const char *directory = m_directory.GetCString(); - if (directory && directory[0]) + const char *dir = m_directory.GetCString(); + llvm::StringRef directory(dir ? dir : ""); + + if (directory.size() > 0) { - // If the path doesn't start with '/' or '~', return true - switch (directory[0]) + if (m_syntax == ePathSyntaxWindows) { - case '/': - case '~': - return false; - default: + if (directory.size() >= 2 && directory[1] == ':') + return false; + if (directory[0] == '/') + return false; return true; } + else + { + // If the path doesn't start with '/' or '~', return true + switch (directory[0]) + { + case '/': + case '~': + return false; + default: + return true; + } + } } else if (m_filename) { diff --git a/source/Host/common/Host.cpp b/source/Host/common/Host.cpp index 00c2fa37b3832..c8daa175d1bd4 100644 --- a/source/Host/common/Host.cpp +++ b/source/Host/common/Host.cpp @@ -14,11 +14,7 @@ #include <limits.h> #include <stdlib.h> #include <sys/types.h> -#ifdef _WIN32 -#include "lldb/Host/windows/windows.h" -#include <winsock2.h> -#include <ws2tcpip.h> -#else +#ifndef _WIN32 #include <unistd.h> #include <dlfcn.h> #include <grp.h> @@ -27,11 +23,6 @@ #include <sys/stat.h> #endif -#if !defined (__GNU__) && !defined (_WIN32) -// Does not exist under GNU/HURD or Windows -#include <sys/sysctl.h> -#endif - #if defined (__APPLE__) #include <mach/mach_port.h> #include <mach/mach_init.h> @@ -39,7 +30,9 @@ #endif #if defined (__linux__) || defined (__FreeBSD__) || defined (__FreeBSD_kernel__) || defined (__APPLE__) || defined(__NetBSD__) +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) #include <spawn.h> +#endif #include <sys/wait.h> #include <sys/syscall.h> #endif @@ -51,33 +44,30 @@ // C++ includes #include <limits> +#include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Core/ArchSpec.h" -#include "lldb/Core/ConstString.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Error.h" #include "lldb/Core/Log.h" #include "lldb/Core/Module.h" -#include "lldb/Core/StreamString.h" -#include "lldb/Core/ThreadSafeSTLMap.h" -#include "lldb/Host/Config.h" -#include "lldb/Host/Endian.h" #include "lldb/Host/FileSpec.h" -#include "lldb/Host/FileSystem.h" -#include "lldb/Host/Mutex.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/MonitoringProcessLauncher.h" +#include "lldb/Host/ProcessLauncher.h" +#include "lldb/Host/ThreadLauncher.h" #include "lldb/lldb-private-forward.h" #include "lldb/Target/FileAction.h" -#include "lldb/Target/Process.h" #include "lldb/Target/ProcessLaunchInfo.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/CleanUp.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/Support/Host.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/raw_ostream.h" +#if defined(_WIN32) +#include "lldb/Host/windows/ProcessLauncherWindows.h" +#else +#include "lldb/Host/posix/ProcessLauncherPosix.h" +#endif #if defined (__APPLE__) #ifndef _POSIX_SPAWN_DISABLE_ASLR @@ -95,13 +85,6 @@ extern "C" using namespace lldb; using namespace lldb_private; -// Define maximum thread name length -#if defined (__linux__) || defined (__FreeBSD__) || defined (__FreeBSD_kernel__) || defined (__NetBSD__) -uint32_t const Host::MAX_THREAD_NAME_LENGTH = 16; -#else -uint32_t const Host::MAX_THREAD_NAME_LENGTH = std::numeric_limits<uint32_t>::max (); -#endif - #if !defined (__APPLE__) && !defined (_WIN32) struct MonitorInfo { @@ -114,16 +97,9 @@ struct MonitorInfo static thread_result_t MonitorChildProcessThreadFunction (void *arg); -lldb::thread_t -Host::StartMonitoringChildProcess -( - Host::MonitorChildProcessCallback callback, - void *callback_baton, - lldb::pid_t pid, - bool monitor_signals -) +HostThread +Host::StartMonitoringChildProcess(Host::MonitorChildProcessCallback callback, void *callback_baton, lldb::pid_t pid, bool monitor_signals) { - lldb::thread_t thread = LLDB_INVALID_HOST_THREAD; MonitorInfo * info_ptr = new MonitorInfo(); info_ptr->pid = pid; @@ -132,26 +108,11 @@ Host::StartMonitoringChildProcess info_ptr->monitor_signals = monitor_signals; char thread_name[256]; - - if (Host::MAX_THREAD_NAME_LENGTH <= 16) - { - // On some platforms, the thread name is limited to 16 characters. We need to - // abbreviate there or the pid info would get truncated. - ::snprintf (thread_name, sizeof(thread_name), "wait4(%" PRIu64 ")", pid); - } - else - { - ::snprintf (thread_name, sizeof(thread_name), "<lldb.host.wait4(pid=%" PRIu64 ")>", pid); - } - - thread = ThreadCreate (thread_name, - MonitorChildProcessThreadFunction, - info_ptr, - NULL); - - return thread; + ::snprintf(thread_name, sizeof(thread_name), "<lldb.host.wait4(pid=%" PRIu64 ")>", pid); + return ThreadLauncher::LaunchThread(thread_name, MonitorChildProcessThreadFunction, info_ptr, NULL); } +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) //------------------------------------------------------------------ // Scoped class that will disable thread canceling when it is // constructed, and exception safely restore the previous value it @@ -166,7 +127,6 @@ public: int err = ::pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &m_old_state); if (err != 0) m_old_state = -1; - } ~ScopedPThreadCancelDisabler() @@ -179,6 +139,7 @@ public: private: int m_old_state; // Save the old cancelability state. }; +#endif // __ANDROID_NDK__ static thread_result_t MonitorChildProcessThreadFunction (void *arg) @@ -212,11 +173,14 @@ MonitorChildProcessThreadFunction (void *arg) log->Printf("%s ::wait_pid (pid = %" PRIi32 ", &status, options = %i)...", function, pid, options); // Wait for all child processes +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) ::pthread_testcancel (); +#endif // Get signals from all children with same process group of pid const ::pid_t wait_pid = ::waitpid (pid, &status, options); +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) ::pthread_testcancel (); - +#endif if (wait_pid == -1) { if (errno == EINTR) @@ -261,7 +225,9 @@ MonitorChildProcessThreadFunction (void *arg) // Scope for pthread_cancel_disabler { +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) ScopedPThreadCancelDisabler pthread_cancel_disabler; +#endif log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS); if (log) @@ -349,6 +315,8 @@ Host::GetCurrentThreadID() return thread_self; #elif defined(__FreeBSD__) return lldb::tid_t(pthread_getthreadid_np()); +#elif defined(__ANDROID_NDK__) + return lldb::tid_t(gettid()); #elif defined(__linux__) return lldb::tid_t(syscall(SYS_gettid)); #else @@ -429,11 +397,6 @@ Host::WillTerminate () #if !defined (__APPLE__) && !defined (__FreeBSD__) && !defined (__FreeBSD_kernel__) && !defined (__linux__) // see macosx/Host.mm void -Host::ThreadCreated (const char *thread_name) -{ -} - -void Host::Backtrace (Stream &strm, uint32_t max_frames) { // TODO: Is there a way to backtrace the current process on other systems? @@ -448,101 +411,8 @@ Host::GetEnvironment (StringList &env) #endif // #if !defined (__APPLE__) && !defined (__FreeBSD__) && !defined (__FreeBSD_kernel__) && !defined (__linux__) -struct HostThreadCreateInfo -{ - std::string thread_name; - thread_func_t thread_fptr; - thread_arg_t thread_arg; - - HostThreadCreateInfo (const char *name, thread_func_t fptr, thread_arg_t arg) : - thread_name (name ? name : ""), - thread_fptr (fptr), - thread_arg (arg) - { - } -}; - -static thread_result_t -#ifdef _WIN32 -__stdcall -#endif -ThreadCreateTrampoline (thread_arg_t arg) -{ - HostThreadCreateInfo *info = (HostThreadCreateInfo *)arg; - Host::ThreadCreated (info->thread_name.c_str()); - thread_func_t thread_fptr = info->thread_fptr; - thread_arg_t thread_arg = info->thread_arg; - - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_THREAD)); - if (log) - log->Printf("thread created"); - - delete info; - return thread_fptr (thread_arg); -} - -lldb::thread_t -Host::ThreadCreate -( - const char *thread_name, - thread_func_t thread_fptr, - thread_arg_t thread_arg, - Error *error -) -{ - lldb::thread_t thread = LLDB_INVALID_HOST_THREAD; - - // Host::ThreadCreateTrampoline will delete this pointer for us. - HostThreadCreateInfo *info_ptr = new HostThreadCreateInfo (thread_name, thread_fptr, thread_arg); - -#ifdef _WIN32 - thread = ::_beginthreadex(0, 0, ThreadCreateTrampoline, info_ptr, 0, NULL); - int err = thread <= 0 ? GetLastError() : 0; -#else - int err = ::pthread_create (&thread, NULL, ThreadCreateTrampoline, info_ptr); -#endif - if (err == 0) - { - if (error) - error->Clear(); - return thread; - } - - if (error) - error->SetError (err, eErrorTypePOSIX); - - return LLDB_INVALID_HOST_THREAD; -} - #ifndef _WIN32 -bool -Host::ThreadCancel (lldb::thread_t thread, Error *error) -{ - int err = ::pthread_cancel (thread); - if (error) - error->SetError(err, eErrorTypePOSIX); - return err == 0; -} - -bool -Host::ThreadDetach (lldb::thread_t thread, Error *error) -{ - int err = ::pthread_detach (thread); - if (error) - error->SetError(err, eErrorTypePOSIX); - return err == 0; -} - -bool -Host::ThreadJoin (lldb::thread_t thread, thread_result_t *thread_result_ptr, Error *error) -{ - int err = ::pthread_join (thread, thread_result_ptr); - if (error) - error->SetError(err, eErrorTypePOSIX); - return err == 0; -} - lldb::thread_key_t Host::ThreadLocalStorageCreate(ThreadLocalStorageCleanupCallback callback) { @@ -563,99 +433,6 @@ Host::ThreadLocalStorageSet(lldb::thread_key_t key, void *value) ::pthread_setspecific (key, value); } -bool -Host::SetThreadName (lldb::pid_t pid, lldb::tid_t tid, const char *name) -{ -#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 - lldb::pid_t curr_pid = Host::GetCurrentProcessID(); - lldb::tid_t curr_tid = Host::GetCurrentThreadID(); - if (pid == LLDB_INVALID_PROCESS_ID) - pid = curr_pid; - - if (tid == LLDB_INVALID_THREAD_ID) - tid = curr_tid; - - // Set the pthread name if possible - if (pid == curr_pid && tid == curr_tid) - { - if (::pthread_setname_np (name) == 0) - return true; - } - return false; -#elif defined (__FreeBSD__) - lldb::pid_t curr_pid = Host::GetCurrentProcessID(); - lldb::tid_t curr_tid = Host::GetCurrentThreadID(); - if (pid == LLDB_INVALID_PROCESS_ID) - pid = curr_pid; - - if (tid == LLDB_INVALID_THREAD_ID) - tid = curr_tid; - - // Set the pthread name if possible - if (pid == curr_pid && tid == curr_tid) - { - ::pthread_set_name_np (::pthread_self(), name); - return true; - } - return false; -#elif defined (__linux__) || defined (__GLIBC__) - void *fn = dlsym (RTLD_DEFAULT, "pthread_setname_np"); - if (fn) - { - lldb::pid_t curr_pid = Host::GetCurrentProcessID(); - lldb::tid_t curr_tid = Host::GetCurrentThreadID(); - if (pid == LLDB_INVALID_PROCESS_ID) - pid = curr_pid; - - if (tid == LLDB_INVALID_THREAD_ID) - tid = curr_tid; - - if (pid == curr_pid && tid == curr_tid) - { - int (*pthread_setname_np_func)(pthread_t thread, const char *name); - *reinterpret_cast<void **> (&pthread_setname_np_func) = fn; - - if (pthread_setname_np_func (::pthread_self(), name) == 0) - return true; - } - } - return false; -#else - return false; -#endif -} - -bool -Host::SetShortThreadName (lldb::pid_t pid, lldb::tid_t tid, - const char *thread_name, size_t len) -{ - std::unique_ptr<char[]> namebuf(new char[len+1]); - - // Thread names are coming in like '<lldb.comm.debugger.edit>' and - // '<lldb.comm.debugger.editline>'. So just chopping the end of the string - // off leads to a lot of similar named threads. Go through the thread name - // and search for the last dot and use that. - const char *lastdot = ::strrchr (thread_name, '.'); - - if (lastdot && lastdot != thread_name) - thread_name = lastdot + 1; - ::strncpy (namebuf.get(), thread_name, len); - namebuf[len] = 0; - - int namebuflen = strlen(namebuf.get()); - if (namebuflen > 0) - { - if (namebuf[namebuflen - 1] == '(' || namebuf[namebuflen - 1] == '>') - { - // Trim off trailing '(' and '>' characters for a bit more cleanup. - namebuflen--; - namebuf[namebuflen] = 0; - } - return Host::SetThreadName (pid, tid, namebuf.get()); - } - return false; -} - #endif #if !defined (__APPLE__) // see Host.mm @@ -680,12 +457,16 @@ FileSpec Host::GetModuleFileSpecForHostAddress (const void *host_addr) { FileSpec module_filespec; +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) Dl_info info; if (::dladdr (host_addr, &info)) { if (info.dli_fname) module_filespec.SetFile(info.dli_fname, true); } +#else + assert(false && "dladdr() not supported on Android"); +#endif return module_filespec; } @@ -699,28 +480,6 @@ Host::FindProcessThreads (const lldb::pid_t pid, TidMap &tids_to_attach) } #endif -lldb::TargetSP -Host::GetDummyTarget (lldb_private::Debugger &debugger) -{ - static TargetSP g_dummy_target_sp; - - // FIXME: Maybe the dummy target should be per-Debugger - if (!g_dummy_target_sp || !g_dummy_target_sp->IsValid()) - { - ArchSpec arch(Target::GetDefaultArchitecture()); - if (!arch.IsValid()) - arch = HostInfo::GetArchitecture(); - Error err = debugger.GetTargetList().CreateTarget(debugger, - NULL, - arch.GetTriple().getTriple().c_str(), - false, - NULL, - g_dummy_target_sp); - } - - return g_dummy_target_sp; -} - struct ShellInfo { ShellInfo () : @@ -770,14 +529,15 @@ Host::RunShellCommand (const char *command, int *signo_ptr, std::string *command_output_ptr, uint32_t timeout_sec, - const char *shell) + bool run_in_default_shell) { Error error; ProcessLaunchInfo launch_info; - if (shell && shell[0]) + launch_info.SetArchitecture(HostInfo::GetArchitecture()); + if (run_in_default_shell) { // Run the command in a shell - launch_info.SetShell(shell); + launch_info.SetShell(HostInfo::GetDefaultShell()); launch_info.GetArguments().AppendArgument(command); const bool localhost = true; const bool will_debug = false; @@ -798,9 +558,8 @@ Host::RunShellCommand (const char *command, if (working_dir) launch_info.SetWorkingDirectory(working_dir); - char output_file_path_buffer[PATH_MAX]; - const char *output_file_path = NULL; - + llvm::SmallString<PATH_MAX> output_file_path; + if (command_output_ptr) { // Create a temporary file to get the stdout/stderr and redirect the @@ -809,21 +568,19 @@ Host::RunShellCommand (const char *command, FileSpec tmpdir_file_spec; if (HostInfo::GetLLDBPath(ePathTypeLLDBTempSystemDir, tmpdir_file_spec)) { - tmpdir_file_spec.AppendPathComponent("lldb-shell-output.XXXXXX"); - strncpy(output_file_path_buffer, tmpdir_file_spec.GetPath().c_str(), sizeof(output_file_path_buffer)); + tmpdir_file_spec.AppendPathComponent("lldb-shell-output.%%%%%%"); + llvm::sys::fs::createUniqueFile(tmpdir_file_spec.GetPath().c_str(), output_file_path); } else { - strncpy(output_file_path_buffer, "/tmp/lldb-shell-output.XXXXXX", sizeof(output_file_path_buffer)); + llvm::sys::fs::createTemporaryFile("lldb-shell-output.%%%%%%", "", output_file_path); } - - output_file_path = ::mktemp(output_file_path_buffer); } launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false); - if (output_file_path) + if (!output_file_path.empty()) { - launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_path, false, true); + launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_path.c_str(), false, true); launch_info.AppendDuplicateFileAction(STDOUT_FILENO, STDERR_FILENO); } else @@ -882,7 +639,7 @@ Host::RunShellCommand (const char *command, if (command_output_ptr) { command_output_ptr->clear(); - FileSpec file_spec(output_file_path, File::eOpenOptionRead); + FileSpec file_spec(output_file_path.c_str(), File::eOpenOptionRead); uint64_t file_size = file_spec.GetByteSize(); if (file_size > 0) { @@ -901,8 +658,9 @@ Host::RunShellCommand (const char *command, shell_info->can_delete.SetValue(true, eBroadcastAlways); } - if (output_file_path) - ::unlink (output_file_path); + FileSpec output_file_spec(output_file_path.c_str(), false); + if (FileSystem::GetFileExists(output_file_spec)) + FileSystem::Unlink(output_file_path.c_str()); // Handshake with the monitor thread, or just let it know in advance that // it can delete "shell_info" in case we timed out and were not able to kill // the process... @@ -914,13 +672,13 @@ Host::RunShellCommand (const char *command, // systems #if defined (__APPLE__) || defined (__linux__) || defined (__FreeBSD__) || defined (__GLIBC__) || defined(__NetBSD__) - // this method needs to be visible to macosx/Host.cpp and // common/Host.cpp. short -Host::GetPosixspawnFlags (ProcessLaunchInfo &launch_info) +Host::GetPosixspawnFlags(const ProcessLaunchInfo &launch_info) { +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) short flags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK; #if defined (__APPLE__) @@ -963,12 +721,17 @@ Host::GetPosixspawnFlags (ProcessLaunchInfo &launch_info) #endif #endif // #if defined (__APPLE__) return flags; +#else + assert(false && "Host::GetPosixspawnFlags() not supported on Android"); + return 0; +#endif } Error -Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_info, ::pid_t &pid) +Host::LaunchProcessPosixSpawn(const char *exe_path, const ProcessLaunchInfo &launch_info, lldb::pid_t &pid) { Error error; +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_HOST | LIBLLDB_LOG_PROCESS)); posix_spawnattr_t attr; @@ -1086,6 +849,7 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i #endif } + ::pid_t result_pid = LLDB_INVALID_PROCESS_ID; const size_t num_file_actions = launch_info.GetNumFileActions (); if (num_file_actions > 0) { @@ -1110,21 +874,13 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i } } - error.SetError (::posix_spawnp (&pid, - exe_path, - &file_actions, - &attr, - argv, - envp), - eErrorTypePOSIX); + error.SetError(::posix_spawnp(&result_pid, exe_path, &file_actions, &attr, argv, envp), eErrorTypePOSIX); if (error.Fail() || log) { - error.PutToLog(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", - pid, exe_path, static_cast<void*>(&file_actions), - static_cast<void*>(&attr), - reinterpret_cast<const void*>(argv), - reinterpret_cast<const void*>(envp)); + error.PutToLog(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", result_pid, + exe_path, static_cast<void *>(&file_actions), static_cast<void *>(&attr), reinterpret_cast<const void *>(argv), + reinterpret_cast<const void *>(envp)); if (log) { for (int ii=0; argv[ii]; ++ii) @@ -1135,20 +891,13 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i } else { - error.SetError (::posix_spawnp (&pid, - exe_path, - NULL, - &attr, - argv, - envp), - eErrorTypePOSIX); + error.SetError(::posix_spawnp(&result_pid, exe_path, NULL, &attr, argv, envp), eErrorTypePOSIX); if (error.Fail() || log) { error.PutToLog(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = NULL, attr = %p, argv = %p, envp = %p )", - pid, exe_path, static_cast<void*>(&attr), - reinterpret_cast<const void*>(argv), - reinterpret_cast<const void*>(envp)); + result_pid, exe_path, static_cast<void *>(&attr), reinterpret_cast<const void *>(argv), + reinterpret_cast<const void *>(envp)); if (log) { for (int ii=0; argv[ii]; ++ii) @@ -1156,6 +905,7 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i } } } + pid = result_pid; if (working_dir) { @@ -1171,6 +921,9 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i } #endif } +#else + error.SetErrorString("Host::LaunchProcessPosixSpawn() not supported on Android"); +#endif return error; } @@ -1178,6 +931,7 @@ Host::LaunchProcessPosixSpawn (const char *exe_path, ProcessLaunchInfo &launch_i bool Host::AddPosixSpawnFileAction(void *_file_actions, const FileAction *info, Log *log, Error &error) { +#if !defined(__ANDROID__) && !defined(__ANDROID_NDK__) if (info == NULL) return false; @@ -1240,97 +994,40 @@ Host::AddPosixSpawnFileAction(void *_file_actions, const FileAction *info, Log * break; } return error.Success(); +#else + error.SetErrorString("Host::AddPosixSpawnFileAction() not supported on Android"); + return false; +#endif } - #endif // LaunchProcedssPosixSpawn: Apple, Linux, FreeBSD and other GLIBC systems - -#if defined(__linux__) || defined(__FreeBSD__) || defined(__GLIBC__) || defined(__NetBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__GLIBC__) || defined(__NetBSD__) || defined(_WIN32) // The functions below implement process launching via posix_spawn() for Linux, // FreeBSD and NetBSD. Error Host::LaunchProcess (ProcessLaunchInfo &launch_info) { - Error error; - char exe_path[PATH_MAX]; - - PlatformSP host_platform_sp (Platform::GetDefaultPlatform ()); - - const ArchSpec &arch_spec = launch_info.GetArchitecture(); - - FileSpec exe_spec(launch_info.GetExecutableFile()); - - FileSpec::FileType file_type = exe_spec.GetFileType(); - if (file_type != FileSpec::eFileTypeRegular) - { - lldb::ModuleSP exe_module_sp; - error = host_platform_sp->ResolveExecutable (exe_spec, - arch_spec, - exe_module_sp, - NULL); - - if (error.Fail()) - return error; - - if (exe_module_sp) - exe_spec = exe_module_sp->GetFileSpec(); - } - - if (exe_spec.Exists()) - { - exe_spec.GetPath (exe_path, sizeof(exe_path)); - } - else - { - launch_info.GetExecutableFile().GetPath (exe_path, sizeof(exe_path)); - error.SetErrorStringWithFormat ("executable doesn't exist: '%s'", exe_path); - return error; - } - - assert(!launch_info.GetFlags().Test (eLaunchFlagLaunchInTTY)); - - ::pid_t pid = LLDB_INVALID_PROCESS_ID; - - error = LaunchProcessPosixSpawn(exe_path, launch_info, pid); + std::unique_ptr<ProcessLauncher> delegate_launcher; +#if defined(_WIN32) + delegate_launcher.reset(new ProcessLauncherWindows()); +#else + delegate_launcher.reset(new ProcessLauncherPosix()); +#endif + MonitoringProcessLauncher launcher(std::move(delegate_launcher)); - if (pid != LLDB_INVALID_PROCESS_ID) - { - // If all went well, then set the process ID into the launch info - launch_info.SetProcessID(pid); + Error error; + HostProcess process = launcher.LaunchProcess(launch_info, error); - Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + // TODO(zturner): It would be better if the entire HostProcess were returned instead of writing + // it into this structure. + launch_info.SetProcessID(process.GetProcessId()); - // Make sure we reap any processes we spawn or we will have zombies. - if (!launch_info.MonitorProcess()) - { - const bool monitor_signals = false; - StartMonitoringChildProcess (Process::SetProcessExitStatus, - NULL, - pid, - monitor_signals); - if (log) - log->PutCString ("monitored child process with default Process::SetProcessExitStatus."); - } - else - { - if (log) - log->PutCString ("monitored child process with user-specified process monitor."); - } - } - else - { - // Invalid process ID, something didn't go well - if (error.Success()) - error.SetErrorString ("process launch failed for unknown reasons"); - } return error; } - #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) #ifndef _WIN32 - void Host::Kill(lldb::pid_t pid, int signo) { diff --git a/source/Host/common/HostInfoBase.cpp b/source/Host/common/HostInfoBase.cpp index 4eb43bfaf6ff4..d65b796983842 100644 --- a/source/Host/common/HostInfoBase.cpp +++ b/source/Host/common/HostInfoBase.cpp @@ -18,7 +18,9 @@ #include "lldb/Host/HostInfoBase.h" #include "llvm/ADT/Triple.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Host.h" +#include "llvm/Support/raw_ostream.h" #include <thread> @@ -54,6 +56,7 @@ struct HostInfoBaseFields FileSpec m_lldb_support_exe_dir; FileSpec m_lldb_headers_dir; FileSpec m_lldb_python_dir; + FileSpec m_lldb_clang_resource_dir; FileSpec m_lldb_system_plugin_dir; FileSpec m_lldb_user_plugin_dir; FileSpec m_lldb_tmp_dir; @@ -94,6 +97,12 @@ HostInfoBase::GetNumberCPUS() return g_fields->m_number_cpus; } +uint32_t +HostInfoBase::GetMaxThreadNameLength() +{ + return 0; +} + llvm::StringRef HostInfoBase::GetVendorString() { @@ -190,6 +199,11 @@ HostInfoBase::GetLLDBPath(lldb::PathType type, FileSpec &file_spec) if (log) log->Printf("HostInfoBase::GetLLDBPath(ePathTypePythonDir) => '%s'", g_fields->m_lldb_python_dir.GetPath().c_str()); break; + case lldb::ePathTypeClangDir: + COMPUTE_LLDB_PATH(ComputeClangDirectory, g_fields->m_lldb_clang_resource_dir) + if (log) + log->Printf("HostInfoBase::GetLLDBPath(ePathTypeClangResourceDir) => '%s'", g_fields->m_lldb_clang_resource_dir.GetPath().c_str()); + break; case lldb::ePathTypeLLDBSystemPlugins: COMPUTE_LLDB_PATH(ComputeSystemPluginsDirectory, g_fields->m_lldb_system_plugin_dir) if (log) @@ -252,19 +266,23 @@ HostInfoBase::ComputeTempFileDirectory(FileSpec &file_spec) if (!tmpdir_cstr) return false; - StreamString pid_tmpdir; - pid_tmpdir.Printf("%s/lldb", tmpdir_cstr); - if (!FileSystem::MakeDirectory(pid_tmpdir.GetString().c_str(), eFilePermissionsDirectoryDefault).Success()) + FileSpec temp_file_spec(tmpdir_cstr, false); + temp_file_spec.AppendPathComponent("lldb"); + if (!FileSystem::MakeDirectory(temp_file_spec.GetPath().c_str(), eFilePermissionsDirectoryDefault).Success()) return false; - pid_tmpdir.Printf("/%" PRIu64, Host::GetCurrentProcessID()); - if (!FileSystem::MakeDirectory(pid_tmpdir.GetString().c_str(), eFilePermissionsDirectoryDefault).Success()) + std::string pid_str; + llvm::raw_string_ostream pid_stream(pid_str); + pid_stream << Host::GetCurrentProcessID(); + temp_file_spec.AppendPathComponent(pid_stream.str().c_str()); + std::string final_path = temp_file_spec.GetPath(); + if (!FileSystem::MakeDirectory(final_path.c_str(), eFilePermissionsDirectoryDefault).Success()) return false; // Make an atexit handler to clean up the process specify LLDB temp dir // and all of its contents. ::atexit(CleanupProcessSpecificLLDBTempDir); - file_spec.GetDirectory().SetCStringWithLength(pid_tmpdir.GetString().c_str(), pid_tmpdir.GetString().size()); + file_spec.GetDirectory().SetCStringWithLength(final_path.c_str(), final_path.size()); return true; } @@ -283,6 +301,12 @@ HostInfoBase::ComputeSystemPluginsDirectory(FileSpec &file_spec) } bool +HostInfoBase::ComputeClangDirectory(FileSpec &file_spec) +{ + return false; +} + +bool HostInfoBase::ComputeUserPluginsDirectory(FileSpec &file_spec) { // TODO(zturner): Figure out how to compute the user plugins directory for all platforms. diff --git a/source/Host/common/HostNativeThreadBase.cpp b/source/Host/common/HostNativeThreadBase.cpp new file mode 100644 index 0000000000000..9fea54d1e3faa --- /dev/null +++ b/source/Host/common/HostNativeThreadBase.cpp @@ -0,0 +1,82 @@ +//===-- HostNativeThreadBase.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Log.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/HostNativeThreadBase.h" +#include "lldb/Host/ThisThread.h" +#include "lldb/Host/ThreadLauncher.h" +#include "llvm/ADT/StringExtras.h" + +using namespace lldb; +using namespace lldb_private; + +HostNativeThreadBase::HostNativeThreadBase() + : m_thread(LLDB_INVALID_HOST_THREAD) + , m_result(0) +{ +} + +HostNativeThreadBase::HostNativeThreadBase(thread_t thread) + : m_thread(thread) + , m_result(0) +{ +} + +lldb::thread_t +HostNativeThreadBase::GetSystemHandle() const +{ + return m_thread; +} + +lldb::thread_result_t +HostNativeThreadBase::GetResult() const +{ + return m_result; +} + +bool +HostNativeThreadBase::IsJoinable() const +{ + return m_thread != LLDB_INVALID_HOST_THREAD; +} + +void +HostNativeThreadBase::Reset() +{ + m_thread = LLDB_INVALID_HOST_THREAD; + m_result = 0; +} + +lldb::thread_t +HostNativeThreadBase::Release() +{ + lldb::thread_t result = m_thread; + m_thread = LLDB_INVALID_HOST_THREAD; + m_result = 0; + + return result; +} + +lldb::thread_result_t +HostNativeThreadBase::ThreadCreateTrampoline(lldb::thread_arg_t arg) +{ + ThreadLauncher::HostThreadCreateInfo *info = (ThreadLauncher::HostThreadCreateInfo *)arg; + ThisThread::SetName(info->thread_name.c_str(), HostInfo::GetMaxThreadNameLength()); + + thread_func_t thread_fptr = info->thread_fptr; + thread_arg_t thread_arg = info->thread_arg; + + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD)); + if (log) + log->Printf("thread created"); + + delete info; + return thread_fptr(thread_arg); +} diff --git a/source/Host/common/HostProcess.cpp b/source/Host/common/HostProcess.cpp new file mode 100644 index 0000000000000..58a214693a521 --- /dev/null +++ b/source/Host/common/HostProcess.cpp @@ -0,0 +1,65 @@ +//===-- HostProcess.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostNativeProcess.h" +#include "lldb/Host/HostProcess.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() +{ +} + +Error HostProcess::Terminate() +{ + return m_native_process->Terminate(); +} + +Error HostProcess::GetMainModule(FileSpec &file_spec) const +{ + return m_native_process->GetMainModule(file_spec); +} + +lldb::pid_t HostProcess::GetProcessId() const +{ + return m_native_process->GetProcessId(); +} + +bool HostProcess::IsRunning() const +{ + return m_native_process->IsRunning(); +} + +HostThread +HostProcess::StartMonitoring(HostProcess::MonitorCallback callback, void *callback_baton, bool monitor_signals) +{ + return m_native_process->StartMonitoring(callback, callback_baton, monitor_signals); +} + +HostNativeProcessBase &HostProcess::GetNativeProcess() +{ + return *m_native_process; +} + +const HostNativeProcessBase &HostProcess::GetNativeProcess() const +{ + return *m_native_process; +} diff --git a/source/Host/common/HostThread.cpp b/source/Host/common/HostThread.cpp new file mode 100644 index 0000000000000..7757477126c40 --- /dev/null +++ b/source/Host/common/HostThread.cpp @@ -0,0 +1,78 @@ +//===-- HostThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostNativeThread.h" +#include "lldb/Host/HostThread.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)) +{ +} + +Error +HostThread::Join(lldb::thread_result_t *result) +{ + return m_native_thread->Join(result); +} + +Error +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->GetSystemHandle() == thread; +} diff --git a/source/Host/common/MonitoringProcessLauncher.cpp b/source/Host/common/MonitoringProcessLauncher.cpp new file mode 100644 index 0000000000000..0fad44a9ec089 --- /dev/null +++ b/source/Host/common/MonitoringProcessLauncher.cpp @@ -0,0 +1,102 @@ +//===-- ProcessLauncherWindows.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/HostProcess.h" +#include "lldb/Host/MonitoringProcessLauncher.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/ProcessLaunchInfo.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, Error &error) +{ + ProcessLaunchInfo resolved_info(launch_info); + + error.Clear(); + char exe_path[PATH_MAX]; + + PlatformSP host_platform_sp(Platform::GetHostPlatform()); + + const ArchSpec &arch_spec = resolved_info.GetArchitecture(); + + FileSpec exe_spec(resolved_info.GetExecutableFile()); + + FileSpec::FileType file_type = exe_spec.GetFileType(); + if (file_type != FileSpec::eFileTypeRegular) + { + ModuleSpec module_spec(exe_spec, arch_spec); + lldb::ModuleSP exe_module_sp; + error = host_platform_sp->ResolveExecutable(module_spec, exe_module_sp, NULL); + + if (error.Fail()) + return HostProcess(); + + if (exe_module_sp) + exe_spec = exe_module_sp->GetFileSpec(); + } + + if (exe_spec.Exists()) + { + exe_spec.GetPath(exe_path, sizeof(exe_path)); + } + else + { + resolved_info.GetExecutableFile().GetPath(exe_path, sizeof(exe_path)); + error.SetErrorStringWithFormat("executable doesn't exist: '%s'", exe_path); + return HostProcess(); + } + + resolved_info.SetExecutableFile(exe_spec, false); + assert(!resolved_info.GetFlags().Test(eLaunchFlagLaunchInTTY)); + + HostProcess process = m_delegate_launcher->LaunchProcess(resolved_info, error); + + if (process.GetProcessId() != LLDB_INVALID_PROCESS_ID) + { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + Host::MonitorChildProcessCallback callback = launch_info.GetMonitorProcessCallback(); + + void *baton = nullptr; + bool monitor_signals = false; + if (callback) + { + // If the ProcessLaunchInfo specified a callback, use that. + baton = launch_info.GetMonitorProcessBaton(); + monitor_signals = launch_info.GetMonitorSignals(); + } + else + { + callback = Process::SetProcessExitStatus; + } + + process.StartMonitoring(callback, baton, monitor_signals); + 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/source/Host/common/NativeProcessProtocol.cpp b/source/Host/common/NativeProcessProtocol.cpp index b7a77266c58c6..e192f19a8896e 100644 --- a/source/Host/common/NativeProcessProtocol.cpp +++ b/source/Host/common/NativeProcessProtocol.cpp @@ -13,6 +13,7 @@ #include "lldb/Core/ArchSpec.h" #include "lldb/Core/Log.h" #include "lldb/Core/State.h" +#include "lldb/Host/Host.h" #include "lldb/Target/NativeRegisterContext.h" #include "NativeThreadProtocol.h" @@ -44,6 +45,18 @@ NativeProcessProtocol::NativeProcessProtocol (lldb::pid_t pid) : } lldb_private::Error +NativeProcessProtocol::Interrupt () +{ + Error error; +#if !defined (SIGSTOP) + error.SetErrorString ("local host does not support signaling"); + return error; +#else + return Signal (SIGSTOP); +#endif +} + +lldb_private::Error NativeProcessProtocol::GetMemoryRegionInfo (lldb::addr_t load_addr, MemoryRegionInfo &range_info) { // Default: not implemented. @@ -110,9 +123,8 @@ NativeProcessProtocol::GetThreadAtIndex (uint32_t idx) } NativeThreadProtocolSP -NativeProcessProtocol::GetThreadByID (lldb::tid_t tid) +NativeProcessProtocol::GetThreadByIDUnlocked (lldb::tid_t tid) { - Mutex::Locker locker (m_threads_mutex); for (auto thread_sp : m_threads) { if (thread_sp->GetID() == tid) @@ -121,6 +133,13 @@ NativeProcessProtocol::GetThreadByID (lldb::tid_t tid) return NativeThreadProtocolSP (); } +NativeThreadProtocolSP +NativeProcessProtocol::GetThreadByID (lldb::tid_t tid) +{ + Mutex::Locker locker (m_threads_mutex); + return GetThreadByIDUnlocked (tid); +} + bool NativeProcessProtocol::IsAlive () const { diff --git a/source/Host/common/NativeProcessProtocol.h b/source/Host/common/NativeProcessProtocol.h index 035a264e172e1..19d8f353b26fe 100644 --- a/source/Host/common/NativeProcessProtocol.h +++ b/source/Host/common/NativeProcessProtocol.h @@ -59,17 +59,25 @@ namespace lldb_private //------------------------------------------------------------------ /// Sends a process a UNIX signal \a signal. /// - /// Implementer note: the WillSignal ()/DidSignal () calls - /// from the Process class are not replicated here since no - /// concrete classes implemented any behavior for those and - /// put all the work in DoSignal (...). - /// /// @return /// Returns an error object. //------------------------------------------------------------------ virtual Error Signal (int signo) = 0; + //------------------------------------------------------------------ + /// Tells a process to interrupt all operations as if by a Ctrl-C. + /// + /// The default implementation will send a local host's equivalent of + /// a SIGSTOP to the process via the NativeProcessProtocol::Signal() + /// operation. + /// + /// @return + /// Returns an error object. + //------------------------------------------------------------------ + virtual Error + Interrupt (); + virtual Error Kill () = 0; @@ -296,7 +304,7 @@ namespace lldb_private void SetState (lldb::StateType state, bool notify_delegates = true); - // Derived classes need not impelment this. It can be used as a + // Derived classes need not implement this. It can be used as a // hook to clear internal caches that should be invalidated when // stop ids change. // @@ -323,6 +331,9 @@ namespace lldb_private void NotifyDidExec (); + NativeThreadProtocolSP + GetThreadByIDUnlocked (lldb::tid_t tid); + private: void diff --git a/source/Host/common/NativeThreadProtocol.h b/source/Host/common/NativeThreadProtocol.h index 9b404be500b97..15ecffe8b82df 100644 --- a/source/Host/common/NativeThreadProtocol.h +++ b/source/Host/common/NativeThreadProtocol.h @@ -31,7 +31,7 @@ namespace lldb_private { } - virtual const char * + virtual std::string GetName() = 0; virtual lldb::StateType diff --git a/source/Host/common/Pipe.cpp b/source/Host/common/Pipe.cpp deleted file mode 100644 index 4db0e32c93b7f..0000000000000 --- a/source/Host/common/Pipe.cpp +++ /dev/null @@ -1,171 +0,0 @@ -//===-- Pipe.cpp ------------------------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "lldb/Host/Pipe.h" - -#if defined(_WIN32) -#include <io.h> -#include <fcntl.h> -#else -#include <unistd.h> -#endif - -using namespace lldb_private; - -int Pipe::kInvalidDescriptor = -1; - -enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE - -Pipe::Pipe() -{ - m_fds[READ] = Pipe::kInvalidDescriptor; - m_fds[WRITE] = Pipe::kInvalidDescriptor; -} - -Pipe::~Pipe() -{ - Close(); -} - -bool -Pipe::Open() -{ - if (IsValid()) - return true; - -#ifdef _WIN32 - if (::_pipe(m_fds, 256, O_BINARY) == 0) - return true; -#else - if (::pipe(m_fds) == 0) - return true; -#endif - m_fds[READ] = Pipe::kInvalidDescriptor; - m_fds[WRITE] = Pipe::kInvalidDescriptor; - return false; -} - -int -Pipe::GetReadFileDescriptor() const -{ - return m_fds[READ]; -} - -int -Pipe::GetWriteFileDescriptor() const -{ - return m_fds[WRITE]; -} - -int -Pipe::ReleaseReadFileDescriptor() -{ - const int fd = m_fds[READ]; - m_fds[READ] = Pipe::kInvalidDescriptor; - return fd; -} - -int -Pipe::ReleaseWriteFileDescriptor() -{ - const int fd = m_fds[WRITE]; - m_fds[WRITE] = Pipe::kInvalidDescriptor; - return fd; -} - -void -Pipe::Close() -{ - CloseReadFileDescriptor(); - CloseWriteFileDescriptor(); -} - -bool -Pipe::ReadDescriptorIsValid() const -{ - return m_fds[READ] != Pipe::kInvalidDescriptor; -} - -bool -Pipe::WriteDescriptorIsValid() const -{ - return m_fds[WRITE] != Pipe::kInvalidDescriptor; -} - -bool -Pipe::IsValid() const -{ - return ReadDescriptorIsValid() && WriteDescriptorIsValid(); -} - -bool -Pipe::CloseReadFileDescriptor() -{ - if (ReadDescriptorIsValid()) - { - int err; -#ifdef _WIN32 - err = _close(m_fds[READ]); -#else - err = close(m_fds[READ]); -#endif - m_fds[READ] = Pipe::kInvalidDescriptor; - return err == 0; - } - return true; -} - -bool -Pipe::CloseWriteFileDescriptor() -{ - if (WriteDescriptorIsValid()) - { - int err; -#ifdef _WIN32 - err = _close(m_fds[WRITE]); -#else - err = close(m_fds[WRITE]); -#endif - m_fds[WRITE] = Pipe::kInvalidDescriptor; - return err == 0; - } - return true; -} - - -size_t -Pipe::Read (void *buf, size_t num_bytes) -{ - if (ReadDescriptorIsValid()) - { - const int fd = GetReadFileDescriptor(); -#ifdef _WIN32 - return _read (fd, (char *)buf, num_bytes); -#else - return read (fd, buf, num_bytes); -#endif - } - return 0; // Return 0 since errno won't be set if we didn't call read -} - -size_t -Pipe::Write (const void *buf, size_t num_bytes) -{ - if (WriteDescriptorIsValid()) - { - const int fd = GetWriteFileDescriptor(); -#ifdef _WIN32 - return _write (fd, (char *)buf, num_bytes); -#else - return write (fd, buf, num_bytes); -#endif - } - return 0; // Return 0 since errno won't be set if we didn't call write -} - diff --git a/source/Host/common/PipeBase.cpp b/source/Host/common/PipeBase.cpp new file mode 100644 index 0000000000000..a9d6e6f46c86e --- /dev/null +++ b/source/Host/common/PipeBase.cpp @@ -0,0 +1,27 @@ +//===-- source/Host/common/PipeBase.cpp -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/PipeBase.h" + +using namespace lldb_private; + + +PipeBase::~PipeBase() = default; + +Error +PipeBase::OpenAsWriter(llvm::StringRef name, bool child_process_inherit) +{ + return OpenAsWriterWithTimeout(name, child_process_inherit, std::chrono::microseconds::zero()); +} + +Error +PipeBase::Read(void *buf, size_t size, size_t &bytes_read) +{ + return ReadWithTimeout(buf, size, std::chrono::microseconds::zero(), bytes_read); +} diff --git a/source/Host/common/Socket.cpp b/source/Host/common/Socket.cpp index 31e3228497ec4..a6118eef7b79e 100644 --- a/source/Host/common/Socket.cpp +++ b/source/Host/common/Socket.cpp @@ -18,6 +18,14 @@ #include "lldb/Host/TimeValue.h" #include "lldb/Interpreter/Args.h" +#ifdef __ANDROID_NDK__ +#include <linux/tcp.h> +#include <bits/error_constants.h> +#include <asm-generic/errno-base.h> +#include <errno.h> +#include <arpa/inet.h> +#endif + #ifndef LLDB_DISABLE_POSIX #include <arpa/inet.h> #include <netdb.h> @@ -40,6 +48,40 @@ typedef void * get_socket_option_arg_type; const NativeSocket Socket::kInvalidSocketValue = -1; #endif // #if defined(_WIN32) +#ifdef __ANDROID__ +// Android does not have SUN_LEN +#ifndef SUN_LEN +#define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path)) +#endif +#endif // #ifdef __ANDROID__ + +namespace { + +NativeSocket CreateSocket(const int domain, const int type, const int protocol, bool child_processes_inherit) +{ + auto socketType = type; +#ifdef SOCK_CLOEXEC + if (!child_processes_inherit) { + socketType |= SOCK_CLOEXEC; + } +#endif + return ::socket (domain, socketType, protocol); +} + +NativeSocket Accept(NativeSocket sockfd, struct sockaddr *addr, socklen_t *addrlen, bool child_processes_inherit) +{ +#ifdef SOCK_CLOEXEC + int flags = 0; + if (!child_processes_inherit) { + flags |= SOCK_CLOEXEC; + } + return ::accept4 (sockfd, addr, addrlen, flags); +#else + return ::accept (sockfd, addr, addrlen); +#endif +} +} + Socket::Socket(NativeSocket socket, SocketProtocol protocol, bool should_close) : IOObject(eFDTypeSocket, should_close) , m_protocol(protocol) @@ -53,7 +95,7 @@ Socket::~Socket() Close(); } -Error Socket::TcpConnect(llvm::StringRef host_and_port, Socket *&socket) +Error Socket::TcpConnect(llvm::StringRef host_and_port, bool child_processes_inherit, Socket *&socket) { // Store the result in a unique_ptr in case we error out, the memory will get correctly freed. std::unique_ptr<Socket> final_socket; @@ -71,7 +113,7 @@ Error Socket::TcpConnect(llvm::StringRef host_and_port, Socket *&socket) return error; // Create the socket - sock = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + sock = CreateSocket (AF_INET, SOCK_STREAM, IPPROTO_TCP, child_processes_inherit); if (sock == kInvalidSocketValue) { // TODO: On Windows, use WSAGetLastError(). @@ -125,7 +167,7 @@ Error Socket::TcpConnect(llvm::StringRef host_and_port, Socket *&socket) return error; } -Error Socket::TcpListen(llvm::StringRef host_and_port, Socket *&socket, Predicate<uint16_t>* predicate) +Error Socket::TcpListen(llvm::StringRef host_and_port, bool child_processes_inherit, Socket *&socket, Predicate<uint16_t>* predicate) { std::unique_ptr<Socket> listen_socket; NativeSocket listen_sock = kInvalidSocketValue; @@ -134,7 +176,7 @@ Error Socket::TcpListen(llvm::StringRef host_and_port, Socket *&socket, Predicat const sa_family_t family = AF_INET; const int socktype = SOCK_STREAM; const int protocol = IPPROTO_TCP; - listen_sock = ::socket (family, socktype, protocol); + listen_sock = ::CreateSocket (family, socktype, protocol, child_processes_inherit); if (listen_sock == kInvalidSocketValue) { error.SetErrorToErrno(); @@ -196,7 +238,7 @@ Error Socket::TcpListen(llvm::StringRef host_and_port, Socket *&socket, Predicat return error; } -Error Socket::BlockingAccept(llvm::StringRef host_and_port, Socket *&socket) +Error Socket::BlockingAccept(llvm::StringRef host_and_port, bool child_processes_inherit, Socket *&socket) { Error error; std::string host_str; @@ -235,7 +277,10 @@ Error Socket::BlockingAccept(llvm::StringRef host_and_port, Socket *&socket) #endif socklen_t accept_addr_len = sizeof accept_addr; - int sock = ::accept (this->GetNativeSocket(), (struct sockaddr *)&accept_addr, &accept_addr_len); + int sock = Accept (this->GetNativeSocket(), + (struct sockaddr *)&accept_addr, + &accept_addr_len, + child_processes_inherit); if (sock == kInvalidSocketValue) { @@ -280,7 +325,7 @@ Error Socket::BlockingAccept(llvm::StringRef host_and_port, Socket *&socket) } -Error Socket::UdpConnect(llvm::StringRef host_and_port, Socket *&send_socket, Socket *&recv_socket) +Error Socket::UdpConnect(llvm::StringRef host_and_port, bool child_processes_inherit, Socket *&send_socket, Socket *&recv_socket) { std::unique_ptr<Socket> final_send_socket; std::unique_ptr<Socket> final_recv_socket; @@ -300,7 +345,7 @@ Error Socket::UdpConnect(llvm::StringRef host_and_port, Socket *&send_socket, So // Setup the receiving end of the UDP connection on this localhost // on port zero. After we bind to port zero we can read the port. - final_recv_fd = ::socket (AF_INET, SOCK_DGRAM, 0); + final_recv_fd = ::CreateSocket (AF_INET, SOCK_DGRAM, 0, child_processes_inherit); if (final_recv_fd == kInvalidSocketValue) { // Socket creation failed... @@ -351,9 +396,10 @@ Error Socket::UdpConnect(llvm::StringRef host_and_port, Socket *&send_socket, So service_info_ptr != NULL; service_info_ptr = service_info_ptr->ai_next) { - final_send_fd = ::socket (service_info_ptr->ai_family, - service_info_ptr->ai_socktype, - service_info_ptr->ai_protocol); + final_send_fd = ::CreateSocket (service_info_ptr->ai_family, + service_info_ptr->ai_socktype, + service_info_ptr->ai_protocol, + child_processes_inherit); if (final_send_fd != kInvalidSocketValue) { @@ -380,7 +426,7 @@ Error Socket::UdpConnect(llvm::StringRef host_and_port, Socket *&send_socket, So return error; } -Error Socket::UnixDomainConnect(llvm::StringRef name, Socket *&socket) +Error Socket::UnixDomainConnect(llvm::StringRef name, bool child_processes_inherit, Socket *&socket) { Error error; #ifndef LLDB_DISABLE_POSIX @@ -388,7 +434,7 @@ Error Socket::UnixDomainConnect(llvm::StringRef name, Socket *&socket) // Open the socket that was passed in as an option struct sockaddr_un saddr_un; - int fd = ::socket (AF_UNIX, SOCK_STREAM, 0); + int fd = ::CreateSocket (AF_UNIX, SOCK_STREAM, 0, child_processes_inherit); if (fd == kInvalidSocketValue) { error.SetErrorToErrno(); @@ -417,7 +463,7 @@ Error Socket::UnixDomainConnect(llvm::StringRef name, Socket *&socket) return error; } -Error Socket::UnixDomainAccept(llvm::StringRef name, Socket *&socket) +Error Socket::UnixDomainAccept(llvm::StringRef name, bool child_processes_inherit, Socket *&socket) { Error error; #ifndef LLDB_DISABLE_POSIX @@ -427,7 +473,7 @@ Error Socket::UnixDomainAccept(llvm::StringRef name, Socket *&socket) NativeSocket listen_fd = kInvalidSocketValue; NativeSocket socket_fd = kInvalidSocketValue; - listen_fd = ::socket (AF_UNIX, SOCK_STREAM, 0); + listen_fd = ::CreateSocket (AF_UNIX, SOCK_STREAM, 0, child_processes_inherit); if (listen_fd == kInvalidSocketValue) { error.SetErrorToErrno(); @@ -449,7 +495,7 @@ Error Socket::UnixDomainAccept(llvm::StringRef name, Socket *&socket) { if (::listen (listen_fd, 5) == 0) { - socket_fd = ::accept (listen_fd, NULL, 0); + socket_fd = Accept (listen_fd, NULL, 0, child_processes_inherit); if (socket_fd > 0) { final_socket.reset(new Socket(socket_fd, ProtocolUnixDomain, true)); diff --git a/source/Host/common/SocketAddress.cpp b/source/Host/common/SocketAddress.cpp index a952a83185fb2..6231631934dfe 100644 --- a/source/Host/common/SocketAddress.cpp +++ b/source/Host/common/SocketAddress.cpp @@ -214,6 +214,8 @@ SocketAddress::getaddrinfo (const char *host, int ai_protocol, int ai_flags) { + Clear (); + struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = ai_family; @@ -221,15 +223,17 @@ SocketAddress::getaddrinfo (const char *host, hints.ai_protocol = ai_protocol; hints.ai_flags = ai_flags; + bool result = false; struct addrinfo *service_info_list = NULL; int err = ::getaddrinfo (host, service, &hints, &service_info_list); if (err == 0 && service_info_list) + { *this = service_info_list; - else - Clear(); + result = IsValid (); + } :: freeaddrinfo (service_info_list); - return IsValid(); + return result; } diff --git a/source/Host/common/SoftwareBreakpoint.cpp b/source/Host/common/SoftwareBreakpoint.cpp index fe2f504ebc719..d9d1fa67156f9 100644 --- a/source/Host/common/SoftwareBreakpoint.cpp +++ b/source/Host/common/SoftwareBreakpoint.cpp @@ -119,6 +119,16 @@ SoftwareBreakpoint::EnableSoftwareBreakpoint (NativeProcessProtocol &process, ll return Error ("SoftwareBreakpoint::%s failed to read memory while attempting to set breakpoint: attempted to read %lu bytes but only read %" PRIu64, __FUNCTION__, bp_opcode_size, bytes_read); } + // Log what we read. + if (log) + { + int i = 0; + for (const uint8_t *read_byte = saved_opcode_bytes; read_byte < saved_opcode_bytes + bp_opcode_size; ++read_byte) + { + log->Printf ("SoftwareBreakpoint::%s addr = 0x%" PRIx64 " ovewriting byte index %d (was 0x%x)", __FUNCTION__, addr, i++, static_cast<int> (*read_byte)); + } + } + // Write a software breakpoint in place of the original opcode. lldb::addr_t bytes_written = 0; error = process.WriteMemory (addr, bp_opcode_bytes, static_cast<lldb::addr_t> (bp_opcode_size), bytes_written); @@ -207,7 +217,7 @@ SoftwareBreakpoint::DoDisable () if (m_opcode_size > 0) { - // Clear a software breakoint instruction + // Clear a software breakpoint instruction uint8_t curr_break_op [MAX_TRAP_OPCODE_SIZE]; bool break_op_found = false; assert (m_opcode_size <= sizeof (curr_break_op)); @@ -265,7 +275,14 @@ SoftwareBreakpoint::DoDisable () { // SUCCESS if (log) + { + int i = 0; + for (const uint8_t *verify_byte = verify_opcode; verify_byte < verify_opcode + m_opcode_size; ++verify_byte) + { + log->Printf ("SoftwareBreakpoint::%s addr = 0x%" PRIx64 " replaced byte index %d with 0x%x", __FUNCTION__, m_addr, i++, static_cast<int> (*verify_byte)); + } log->Printf ("SoftwareBreakpoint::%s addr = 0x%" PRIx64 " -- SUCCESS", __FUNCTION__, m_addr); + } return error; } else diff --git a/source/Host/common/ThisThread.cpp b/source/Host/common/ThisThread.cpp new file mode 100644 index 0000000000000..289ec780e9fb3 --- /dev/null +++ b/source/Host/common/ThisThread.cpp @@ -0,0 +1,52 @@ +//===-- ThisThread.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Core/Error.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/ThisThread.h" + +#include "llvm/ADT/STLExtras.h" + +#include <algorithm> + +using namespace lldb; +using namespace lldb_private; + +void +ThisThread::SetName(llvm::StringRef name, int max_length) +{ + std::string truncated_name(name.data()); + + // Thread names are coming in like '<lldb.comm.debugger.edit>' and + // '<lldb.comm.debugger.editline>'. So just chopping the end of the string + // off leads to a lot of similar named threads. Go through the thread name + // and search for the last dot and use that. + + if (max_length > 0 && truncated_name.length() > static_cast<size_t>(max_length)) + { + // First see if we can get lucky by removing any initial or final braces. + std::string::size_type begin = truncated_name.find_first_not_of("(<"); + std::string::size_type end = truncated_name.find_last_not_of(")>."); + if (end - begin > static_cast<size_t>(max_length)) + { + // We're still too long. Since this is a dotted component, use everything after the last + // dot, up to a maximum of |length| characters. + std::string::size_type last_dot = truncated_name.find_last_of("."); + if (last_dot != std::string::npos) + begin = last_dot + 1; + + end = std::min(end, begin + max_length); + } + + std::string::size_type count = end - begin + 1; + truncated_name = truncated_name.substr(begin, count); + } + + SetName(truncated_name.c_str()); +} diff --git a/source/Host/common/ThreadLauncher.cpp b/source/Host/common/ThreadLauncher.cpp new file mode 100644 index 0000000000000..ec7da325bf92b --- /dev/null +++ b/source/Host/common/ThreadLauncher.cpp @@ -0,0 +1,74 @@ +//===-- ThreadLauncher.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// lldb Includes +#include "lldb/Core/Log.h" +#include "lldb/Host/HostNativeThread.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Host/ThisThread.h" +#include "lldb/Host/ThreadLauncher.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/windows.h" +#endif + +using namespace lldb; +using namespace lldb_private; + +HostThread +ThreadLauncher::LaunchThread(llvm::StringRef name, lldb::thread_func_t thread_function, lldb::thread_arg_t thread_arg, Error *error_ptr, size_t min_stack_byte_size) +{ + Error error; + if (error_ptr) + error_ptr->Clear(); + + // Host::ThreadCreateTrampoline will delete this pointer for us. + HostThreadCreateInfo *info_ptr = new HostThreadCreateInfo(name.data(), thread_function, thread_arg); + lldb::thread_t thread; +#ifdef _WIN32 + thread = + (lldb::thread_t)::_beginthreadex(0, (unsigned)min_stack_byte_size, HostNativeThread::ThreadCreateTrampoline, info_ptr, 0, NULL); + if (thread == (lldb::thread_t)(-1L)) + error.SetError(::GetLastError(), eErrorTypeWin32); +#else + + pthread_attr_t *thread_attr_ptr = NULL; + pthread_attr_t thread_attr; + bool destroy_attr = false; + if (min_stack_byte_size > 0) + { + if (::pthread_attr_init (&thread_attr) == 0) + { + destroy_attr = true; + size_t default_min_stack_byte_size = 0; + if (::pthread_attr_getstacksize(&thread_attr, &default_min_stack_byte_size) == 0) + { + if (default_min_stack_byte_size < min_stack_byte_size) + { + if (::pthread_attr_setstacksize (&thread_attr, min_stack_byte_size) == 0) + thread_attr_ptr = &thread_attr; + } + } + + } + } + int err = ::pthread_create(&thread, thread_attr_ptr, HostNativeThread::ThreadCreateTrampoline, info_ptr); + + if (destroy_attr) + ::pthread_attr_destroy(&thread_attr); + + error.SetError(err, eErrorTypePOSIX); +#endif + if (error_ptr) + *error_ptr = error; + if (!error.Success()) + thread = LLDB_INVALID_HOST_THREAD; + + return HostThread(thread); +} |