diff options
Diffstat (limited to 'source/Host/windows')
-rw-r--r-- | source/Host/windows/Condition.cpp | 98 | ||||
-rw-r--r-- | source/Host/windows/ConnectionGenericFileWindows.cpp | 354 | ||||
-rw-r--r-- | source/Host/windows/EditLineWin.cpp | 435 | ||||
-rw-r--r-- | source/Host/windows/FileSystem.cpp | 221 | ||||
-rw-r--r-- | source/Host/windows/Host.cpp | 326 | ||||
-rw-r--r-- | source/Host/windows/HostInfoWindows.cpp | 118 | ||||
-rw-r--r-- | source/Host/windows/HostProcessWindows.cpp | 137 | ||||
-rw-r--r-- | source/Host/windows/HostThreadWindows.cpp | 98 | ||||
-rw-r--r-- | source/Host/windows/LockFileWindows.cpp | 93 | ||||
-rw-r--r-- | source/Host/windows/Mutex.cpp | 109 | ||||
-rw-r--r-- | source/Host/windows/PipeWindows.cpp | 336 | ||||
-rw-r--r-- | source/Host/windows/ProcessLauncherWindows.cpp | 105 | ||||
-rw-r--r-- | source/Host/windows/ProcessRunLock.cpp | 106 | ||||
-rw-r--r-- | source/Host/windows/ThisThread.cpp | 66 | ||||
-rw-r--r-- | source/Host/windows/Windows.cpp | 231 |
15 files changed, 2833 insertions, 0 deletions
diff --git a/source/Host/windows/Condition.cpp b/source/Host/windows/Condition.cpp new file mode 100644 index 0000000000000..2f16ad77d7a3b --- /dev/null +++ b/source/Host/windows/Condition.cpp @@ -0,0 +1,98 @@ +//===-- Condition.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <errno.h> + +#include "lldb/Host/Condition.h" +#include "lldb/Host/TimeValue.h" +#include "lldb/Host/windows/windows.h" + + +using namespace lldb_private; + +//---------------------------------------------------------------------- +// Default constructor +// +// The default constructor will initialize a new pthread condition +// and maintain the condition in the object state. +//---------------------------------------------------------------------- +Condition::Condition () : + m_condition() +{ + m_condition = static_cast<PCONDITION_VARIABLE>(malloc(sizeof(CONDITION_VARIABLE))); + InitializeConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); +} + +//---------------------------------------------------------------------- +// Destructor +// +// Destroys the pthread condition that the object owns. +//---------------------------------------------------------------------- +Condition::~Condition () +{ + free(m_condition); +} + +//---------------------------------------------------------------------- +// Unblock all threads waiting for a condition variable +//---------------------------------------------------------------------- +int +Condition::Broadcast () +{ + WakeAllConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); + return 0; +} + +//---------------------------------------------------------------------- +// Unblocks one thread waiting for the condition variable +//---------------------------------------------------------------------- +int +Condition::Signal () +{ + WakeConditionVariable(static_cast<PCONDITION_VARIABLE>(m_condition)); + return 0; +} + +//---------------------------------------------------------------------- +// The Wait() function atomically blocks the current thread +// waiting on the owned condition variable, and unblocks the mutex +// specified by "mutex". The waiting thread unblocks only after +// another thread calls Signal(), or Broadcast() with the same +// condition variable, or if "abstime" is valid (non-NULL) this +// function will return when the system time reaches the time +// specified in "abstime". If "abstime" is NULL this function will +// wait for an infinite amount of time for the condition variable +// to be signaled or broadcasted. +// +// The current thread re-acquires the lock on "mutex". +//---------------------------------------------------------------------- +int +Condition::Wait (Mutex &mutex, const TimeValue *abstime, bool *timed_out) +{ + DWORD wait = INFINITE; + if (abstime != NULL) { + int wval = (*abstime - TimeValue::Now()) / 1000000; + if (wval < 0) wval = 0; + + wait = wval; + } + + int err = SleepConditionVariableCS(static_cast<PCONDITION_VARIABLE>(m_condition), static_cast<PCRITICAL_SECTION>(mutex.m_mutex), wait); + + if (timed_out != NULL) + { + if ((err == 0) && GetLastError() == ERROR_TIMEOUT) + *timed_out = true; + else + *timed_out = false; + } + + return err == 0; +} + diff --git a/source/Host/windows/ConnectionGenericFileWindows.cpp b/source/Host/windows/ConnectionGenericFileWindows.cpp new file mode 100644 index 0000000000000..eebf3d4f633c8 --- /dev/null +++ b/source/Host/windows/ConnectionGenericFileWindows.cpp @@ -0,0 +1,354 @@ +//===-- ConnectionGenericFileWindows.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/Host/TimeValue.h" +#include "lldb/Host/windows/ConnectionGenericFileWindows.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +// This is a simple helper class to package up the information needed to return from a Read/Write +// operation function. Since there is a lot of code to be run before exit regardless of whether the +// operation succeeded or failed, combined with many possible return paths, this is the cleanest +// way to represent it. +class ReturnInfo +{ + public: + void + Set(size_t bytes, ConnectionStatus status, DWORD error_code) + { + m_error.SetError(error_code, eErrorTypeWin32); + m_bytes = bytes; + m_status = status; + } + + void + Set(size_t bytes, ConnectionStatus status, llvm::StringRef error_msg) + { + m_error.SetErrorString(error_msg.data()); + m_bytes = bytes; + m_status = status; + } + + size_t + GetBytes() const + { + return m_bytes; + } + ConnectionStatus + GetStatus() const + { + return m_status; + } + const Error & + GetError() const + { + return m_error; + } + + private: + Error m_error; + size_t m_bytes; + ConnectionStatus m_status; +}; +} + +ConnectionGenericFile::ConnectionGenericFile() + : m_file(INVALID_HANDLE_VALUE) + , m_owns_file(false) +{ + ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + InitializeEventHandles(); +} + +ConnectionGenericFile::ConnectionGenericFile(lldb::file_t file, bool owns_file) + : m_file(file) + , m_owns_file(owns_file) +{ + ::ZeroMemory(&m_overlapped, sizeof(m_overlapped)); + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + InitializeEventHandles(); +} + +ConnectionGenericFile::~ConnectionGenericFile() +{ + if (m_owns_file && IsConnected()) + ::CloseHandle(m_file); + + ::CloseHandle(m_event_handles[kBytesAvailableEvent]); + ::CloseHandle(m_event_handles[kInterruptEvent]); +} + +void +ConnectionGenericFile::InitializeEventHandles() +{ + m_event_handles[kInterruptEvent] = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Note, we should use a manual reset event for the hEvent argument of the OVERLAPPED. This + // is because both WaitForMultipleObjects and GetOverlappedResult (if you set the bWait + // argument to TRUE) will wait for the event to be signalled. If we use an auto-reset event, + // WaitForMultipleObjects will reset the event, return successfully, and then + // GetOverlappedResult will block since the event is no longer signalled. + m_event_handles[kBytesAvailableEvent] = ::CreateEvent(NULL, TRUE, FALSE, NULL); +} + +bool +ConnectionGenericFile::IsConnected() const +{ + return m_file && (m_file != INVALID_HANDLE_VALUE); +} + +lldb::ConnectionStatus +ConnectionGenericFile::Connect(const char *s, Error *error_ptr) +{ + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + log->Printf("%p ConnectionGenericFile::Connect (url = '%s')", static_cast<void *>(this), s); + + if (strstr(s, "file://") != s) + { + if (error_ptr) + error_ptr->SetErrorStringWithFormat("unsupported connection URL: '%s'", s); + return eConnectionStatusError; + } + + if (IsConnected()) + { + ConnectionStatus status = Disconnect(error_ptr); + if (status != eConnectionStatusSuccess) + return status; + } + + // file://PATH + const char *path = s + strlen("file://"); + // Open the file for overlapped access. If it does not exist, create it. We open it overlapped + // so that we can issue asynchronous reads and then use WaitForMultipleObjects to allow the read + // to be interrupted by an event object. + m_file = ::CreateFile(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); + if (m_file == INVALID_HANDLE_VALUE) + { + if (error_ptr) + error_ptr->SetError(::GetLastError(), eErrorTypeWin32); + return eConnectionStatusError; + } + + m_owns_file = true; + m_uri.assign(s); + return eConnectionStatusSuccess; +} + +lldb::ConnectionStatus +ConnectionGenericFile::Disconnect(Error *error_ptr) +{ + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + log->Printf("%p ConnectionGenericFile::Disconnect ()", static_cast<void *>(this)); + + if (!IsConnected()) + return eConnectionStatusSuccess; + + // Reset the handle so that after we unblock any pending reads, subsequent calls to Read() will + // see a disconnected state. + HANDLE old_file = m_file; + m_file = INVALID_HANDLE_VALUE; + + // Set the disconnect event so that any blocking reads unblock, then cancel any pending IO operations. + ::CancelIoEx(old_file, &m_overlapped); + + // Close the file handle if we owned it, but don't close the event handles. We could always + // reconnect with the same Connection instance. + if (m_owns_file) + ::CloseHandle(old_file); + + ::ZeroMemory(&m_file_position, sizeof(m_file_position)); + m_owns_file = false; + m_uri.clear(); + return eConnectionStatusSuccess; +} + +size_t +ConnectionGenericFile::Read(void *dst, size_t dst_len, uint32_t timeout_usec, lldb::ConnectionStatus &status, Error *error_ptr) +{ + ReturnInfo return_info; + BOOL result = 0; + DWORD bytes_read = 0; + + if (error_ptr) + error_ptr->Clear(); + + if (!IsConnected()) + { + return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); + goto finish; + } + + m_overlapped.hEvent = m_event_handles[kBytesAvailableEvent]; + + result = ::ReadFile(m_file, dst, dst_len, NULL, &m_overlapped); + if (result || ::GetLastError() == ERROR_IO_PENDING) + { + if (!result) + { + // The expected return path. The operation is pending. Wait for the operation to complete + // or be interrupted. + TimeValue time_value; + time_value.OffsetWithMicroSeconds(timeout_usec); + DWORD milliseconds = time_value.milliseconds(); + DWORD wait_result = ::WaitForMultipleObjects(llvm::array_lengthof(m_event_handles), m_event_handles, FALSE, milliseconds); + // All of the events are manual reset events, so make sure we reset them to non-signalled. + switch (wait_result) + { + case WAIT_OBJECT_0 + kBytesAvailableEvent: + break; + case WAIT_OBJECT_0 + kInterruptEvent: + return_info.Set(0, eConnectionStatusInterrupted, 0); + goto finish; + case WAIT_TIMEOUT: + return_info.Set(0, eConnectionStatusTimedOut, 0); + goto finish; + case WAIT_FAILED: + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + goto finish; + } + } + // The data is ready. Figure out how much was read and return; + if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_read, FALSE)) + { + DWORD result_error = ::GetLastError(); + // ERROR_OPERATION_ABORTED occurs when someone calls Disconnect() during a blocking read. + // This triggers a call to CancelIoEx, which causes the operation to complete and the + // result to be ERROR_OPERATION_ABORTED. + if (result_error == ERROR_HANDLE_EOF || result_error == ERROR_OPERATION_ABORTED || result_error == ERROR_BROKEN_PIPE) + return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); + else + return_info.Set(bytes_read, eConnectionStatusError, result_error); + } + else if (bytes_read == 0) + return_info.Set(bytes_read, eConnectionStatusEndOfFile, 0); + else + return_info.Set(bytes_read, eConnectionStatusSuccess, 0); + + goto finish; + } + else if (::GetLastError() == ERROR_BROKEN_PIPE) + { + // The write end of a pipe was closed. This is equivalent to EOF. + return_info.Set(0, eConnectionStatusEndOfFile, 0); + } + else + { + // An unknown error occurred. Fail out. + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + } + goto finish; + +finish: + status = return_info.GetStatus(); + if (error_ptr) + *error_ptr = return_info.GetError(); + + // kBytesAvailableEvent is a manual reset event. Make sure it gets reset here so that any + // subsequent operations don't immediately see bytes available. + ResetEvent(m_event_handles[kBytesAvailableEvent]); + + IncrementFilePointer(return_info.GetBytes()); + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + { + log->Printf("%" PRIxPTR " ConnectionGenericFile::Read() handle = %" PRIxPTR ", dst = %" PRIxPTR ", dst_len = %" PRIu64 + ") => %" PRIu64 ", error = %s", + this, m_file, dst, static_cast<uint64_t>(dst_len), static_cast<uint64_t>(return_info.GetBytes()), + return_info.GetError().AsCString()); + } + + return return_info.GetBytes(); +} + +size_t +ConnectionGenericFile::Write(const void *src, size_t src_len, lldb::ConnectionStatus &status, Error *error_ptr) +{ + ReturnInfo return_info; + DWORD bytes_written = 0; + BOOL result = 0; + + if (error_ptr) + error_ptr->Clear(); + + if (!IsConnected()) + { + return_info.Set(0, eConnectionStatusNoConnection, ERROR_INVALID_HANDLE); + goto finish; + } + + m_overlapped.hEvent = NULL; + + // Writes are not interruptible like reads are, so just block until it's done. + result = ::WriteFile(m_file, src, src_len, NULL, &m_overlapped); + if (!result && ::GetLastError() != ERROR_IO_PENDING) + { + return_info.Set(0, eConnectionStatusError, ::GetLastError()); + goto finish; + } + + if (!::GetOverlappedResult(m_file, &m_overlapped, &bytes_written, TRUE)) + { + return_info.Set(bytes_written, eConnectionStatusError, ::GetLastError()); + goto finish; + } + + return_info.Set(bytes_written, eConnectionStatusSuccess, 0); + goto finish; + +finish: + status = return_info.GetStatus(); + if (error_ptr) + *error_ptr = return_info.GetError(); + + IncrementFilePointer(return_info.GetBytes()); + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_CONNECTION)); + if (log) + { + log->Printf("%" PRIxPTR " ConnectionGenericFile::Write() handle = %" PRIxPTR ", src = %" PRIxPTR ", src_len = %" PRIu64 + ") => %" PRIu64 ", error = %s", + this, m_file, src, static_cast<uint64_t>(src_len), static_cast<uint64_t>(return_info.GetBytes()), + return_info.GetError().AsCString()); + } + return return_info.GetBytes(); +} + +std::string +ConnectionGenericFile::GetURI() +{ + return m_uri; +} + +bool +ConnectionGenericFile::InterruptRead() +{ + return ::SetEvent(m_event_handles[kInterruptEvent]); +} + +void +ConnectionGenericFile::IncrementFilePointer(DWORD amount) +{ + LARGE_INTEGER old_pos; + old_pos.HighPart = m_overlapped.OffsetHigh; + old_pos.LowPart = m_overlapped.Offset; + old_pos.QuadPart += amount; + m_overlapped.Offset = old_pos.LowPart; + m_overlapped.OffsetHigh = old_pos.HighPart; +} diff --git a/source/Host/windows/EditLineWin.cpp b/source/Host/windows/EditLineWin.cpp new file mode 100644 index 0000000000000..55fe52dc8ccd1 --- /dev/null +++ b/source/Host/windows/EditLineWin.cpp @@ -0,0 +1,435 @@ +//===-- EditLineWin.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// this file is only relevant for Visual C++ +#if defined( _WIN32 ) + +#include "lldb/Host/windows/windows.h" + +#include "lldb/Host/windows/editlinewin.h" +#include <vector> +#include <assert.h> + +// edit line EL_ADDFN function pointer type +typedef unsigned char(*el_addfn_func)(EditLine *e, int ch); +typedef const char* (*el_prompt_func)(EditLine *); + +// edit line wrapper binding container +struct el_binding +{ + // + const char *name; + const char *help; + // function pointer to callback routine + el_addfn_func func; + // ascii key this function is bound to + const char *key; +}; + +// stored key bindings +static std::vector<el_binding*> _bindings; + +//TODO: this should in fact be related to the exact edit line context we create +static void *clientData = NULL; + +// store the current prompt string +// default to what we expect to receive anyway +static const char *_prompt = "(lldb) "; + +#if !defined( _WIP_INPUT_METHOD ) + +static char * +el_get_s (char *buffer, int chars) +{ + return gets_s(buffer, chars); +} +#else + +static void +con_output (char _in) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get the cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // output this char + WriteConsoleOutputCharacterA( hout, &_in, 1, info.dwCursorPosition, &written ); + // advance cursor position + info.dwCursorPosition.X++; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); +} + +static void +con_backspace (void) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // nudge cursor backwards + info.dwCursorPosition.X--; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); + // blank out the last character + WriteConsoleOutputCharacterA( hout, " ", 1, info.dwCursorPosition, &written ); +} + +static void +con_return (void) +{ + HANDLE hout = GetStdHandle( STD_OUTPUT_HANDLE ); + DWORD written = 0; + // get cursor position + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo( hout, &info ); + // move onto the new line + info.dwCursorPosition.X = 0; + info.dwCursorPosition.Y++; + SetConsoleCursorPosition( hout, info.dwCursorPosition ); +} + +static bool +runBind (char _key) +{ + for ( int i=0; i<_bindings.size(); i++ ) + { + el_binding *bind = _bindings[i]; + if ( bind->key[0] == _key ) + { + bind->func( (EditLine*) -1, _key ); + return true; + } + } + return false; +} + +// replacement get_s which is EL_BIND aware +static char * +el_get_s (char *buffer, int chars) +{ + // + char *head = buffer; + // + for ( ;; Sleep( 10 ) ) + { + // + INPUT_RECORD _record; + // + DWORD _read = 0; + if ( ReadConsoleInputA( GetStdHandle( STD_INPUT_HANDLE ), &_record, 1, &_read ) == FALSE ) + break; + // if we didn't read a key + if ( _read == 0 ) + continue; + // only interested in key events + if ( _record.EventType != KEY_EVENT ) + continue; + // is the key down + if (! _record.Event.KeyEvent.bKeyDown ) + continue; + // read the ascii key character + char _key = _record.Event.KeyEvent.uChar.AsciiChar; + // non ascii conformant key press + if ( _key == 0 ) + { + // check the scan code + // if VK_UP scroll back through history + // if VK_DOWN scroll forward through history + continue; + } + // try to execute any bind this key may have + if ( runBind( _key ) ) + continue; + // if we read a return key + if ( _key == '\n' || _key == '\r' ) + { + con_return( ); + break; + } + // key is backspace + if ( _key == 0x8 ) + { + // avoid deleting past beginning + if ( head > buffer ) + { + con_backspace( ); + head--; + } + continue; + } + + // add this key to the input buffer + if ( (head-buffer) < (chars-1) ) + { + con_output( _key ); + *(head++) = _key; + } + } + // insert end of line character + *head = '\0'; + + return buffer; +} +#endif + +// edit line initialize +EditLine * +el_init (const char *, FILE *, FILE *, FILE *) +{ + // + SetConsoleTitleA( "lldb" ); + // return dummy handle + return (EditLine*) -1; +} + +const char * +el_gets (EditLine *el, int *length) +{ + // print the prompt if we have one + if ( _prompt != NULL ) + printf("%s", _prompt); + // create a buffer for the user input + char *buffer = new char[ MAX_PATH ]; + // try to get user input string + if ( el_get_s( buffer, MAX_PATH ) ) + { + // get the string length in 'length' + while ( buffer[ *length ] != '\0' ) + (*length)++; + // return the input buffer + // remember that this memory has the be free'd somewhere + return buffer; + } + else + { + // on error + delete [] buffer; + return NULL; + } +} + +int +el_set (EditLine *el, int code, ...) +{ + va_list vl; + va_start(vl, code); + // + switch ( code ) + { + // edit line set prompt message + case ( EL_PROMPT ): + { + // EL_PROMPT, char *(*f)( EditLine *) + // define a prompt printing function as 'f', which is to return a string that + // contains the prompt. + + // get the function pointer from the arg list + void *func_vp = (void*)va_arg(vl, el_prompt_func); + // cast to suitable prototype + el_prompt_func func_fp = (el_prompt_func)func_vp; + // call to get the prompt as a string + _prompt = func_fp( el ); + } + break; + + case (EL_PROMPT_ESC) : + { + // EL_PROMPT, char *(*f)( EditLine *) + // define a prompt printing function as 'f', which is to return a string that + // contains the prompt. + + // get the function pointer from the arg list + void *func_vp = (void*)va_arg(vl, el_prompt_func); + va_arg(vl, int); + // call to get the prompt as a string + el_prompt_func func_fp = (el_prompt_func)func_vp; + _prompt = func_fp(el); + } + break; + + case ( EL_EDITOR ): + { + // EL_EDITOR, const char *mode + // set editing mode to "emacs" or "vi" + } + break; + case ( EL_HIST ): + { + // EL_HIST, History *(*fun)(History *, int op, ... ), const char *ptr + // defines which history function to use, which is usually history(). Ptr should be the + // value returned by history_init(). + } + break; + case ( EL_ADDFN ): + { + // EL_ADDFN, const char *name, const char *help, unsigned char (*func)(EditLine *e, int ch) + // add a user defined function, func), referred to as 'name' which is invoked when a key which is bound to 'name' is + // entered. 'help' is a description of 'name'. at invocation time, 'ch' is the key which caused the invocation. the + // return value of 'func()' should be one of: + // CC_NORM add a normal character + // CC_NEWLINE end of line was entered + // CC_EOF EOF was entered + // CC_ARGHACK expecting further command input as arguments, do nothing visually. + // CC_REFRESH refresh display. + // CC_REFRESH_BEEP refresh display and beep. + // CC_CURSOR cursor moved so update and perform CC_REFRESH + // CC_REDISPLAY redisplay entire input line. this is useful if a key binding outputs extra information. + // CC_ERROR an error occurred. beep and flush tty. + // CC_FATAL fatal error, reset tty to known state. + + el_binding *binding = new el_binding; + binding->name = va_arg( vl, const char *); + binding->help = va_arg( vl, const char *); + binding->func = va_arg( vl, el_addfn_func ); + binding->key = 0; + // add this to the bindings list + _bindings.push_back( binding ); + } + break; + case ( EL_BIND ): + { + // EL_BIND, const char *, ..., NULL + // perform the BIND built-in command. Refer to editrc(5) for more information. + + const char *name = va_arg( vl, const char* ); + + for ( int i=0; i<_bindings.size(); i++ ) + { + el_binding *bind = _bindings[i]; + if ( strcmp( bind->name, name ) == 0 ) + { + bind->key = va_arg( vl, const char * ); + break; + } + } + + } + break; + case ( EL_CLIENTDATA ): + { + clientData = va_arg(vl, void*); + } + break; + } + return 0; +} + +void +el_end (EditLine *el) +{ + //assert( !"Not implemented!" ); +} + +void +el_reset (EditLine *) +{ + assert( !"Not implemented!" ); +} + +int +el_getc (EditLine *, char *) +{ + assert( !"Not implemented!" ); + return 0; +} + +void +el_push (EditLine *, const char *) +{ +} + +void +el_beep (EditLine *) +{ + Beep( 1000, 500 ); +} + +int +el_parse (EditLine *, int, const char **) +{ + assert( !"Not implemented!" ); + return 0; +} + +int +el_get (EditLine *el, int code, ...) +{ + va_list vl; + va_start( vl, code ); + + switch ( code ) + { + case ( EL_CLIENTDATA ): + { + void **dout = va_arg( vl, void** ); + *dout = clientData; + } + break; + default: + assert( !"Not implemented!" ); + } + return 0; +} + +int +el_source (EditLine *el, const char *file) +{ + // init edit line by reading the contents of 'file' + // nothing to do here on windows... + return 0; +} + +void +el_resize (EditLine *) +{ + assert( !"Not implemented!" ); +} + +const LineInfo * +el_line (EditLine *el) +{ + return 0; +} + +int +el_insertstr (EditLine *, const char *) +{ +// assert( !"Not implemented!" ); + return 0; +} + +void +el_deletestr (EditLine *, int) +{ + assert( !"Not implemented!" ); +} + +History * +history_init (void) +{ + // return dummy handle + return (History*) -1; +} + +void +history_end (History *) +{ +// assert( !"Not implemented!" ); +} + +int +history (History *, HistEvent *, int op, ...) +{ + // perform operation 'op' on the history list with + // optional arguments as needed by the operation. + return 0; +} + +#endif diff --git a/source/Host/windows/FileSystem.cpp b/source/Host/windows/FileSystem.cpp new file mode 100644 index 0000000000000..2cca12b92711b --- /dev/null +++ b/source/Host/windows/FileSystem.cpp @@ -0,0 +1,221 @@ +//===-- FileSystem.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/windows/windows.h" + +#include <shellapi.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/windows/AutoHandle.h" +#include "llvm/Support/FileSystem.h" + +using namespace lldb_private; + +const char * +FileSystem::DEV_NULL = "nul"; + +FileSpec::PathSyntax +FileSystem::GetNativePathSyntax() +{ + return FileSpec::ePathSyntaxWindows; +} + +Error +FileSystem::MakeDirectory(const FileSpec &file_spec, uint32_t file_permissions) +{ + // On Win32, the mode parameter is ignored, as Windows files and directories support a + // different permission model than POSIX. + Error error; + const auto err_code = llvm::sys::fs::create_directories(file_spec.GetPath(), true); + if (err_code) + { + error.SetErrorString(err_code.message().c_str()); + } + + return error; +} + +Error +FileSystem::DeleteDirectory(const FileSpec &file_spec, bool recurse) +{ + Error error; + if (!recurse) + { + BOOL result = ::RemoveDirectory(file_spec.GetCString()); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + } + else + { + // SHFileOperation() accepts a list of paths, and so must be double-null-terminated to + // indicate the end of the list. + std::string path_buffer{file_spec.GetPath()}; + path_buffer.push_back(0); + + SHFILEOPSTRUCT shfos = {0}; + shfos.wFunc = FO_DELETE; + shfos.pFrom = path_buffer.c_str(); + shfos.fFlags = FOF_NO_UI; + + int result = ::SHFileOperation(&shfos); + // TODO(zturner): Correctly handle the intricacies of SHFileOperation return values. + if (result != 0) + error.SetErrorStringWithFormat("SHFileOperation failed"); + } + return error; +} + +Error +FileSystem::GetFilePermissions(const FileSpec &file_spec, uint32_t &file_permissions) +{ + Error error; + // Beware that Windows's permission model is different from Unix's, and it's + // not clear if this API is supposed to check ACLs. To match the caller's + // expectations as closely as possible, we'll use Microsoft's _stat, which + // attempts to emulate POSIX stat. This should be good enough for basic + // checks like FileSpec::Readable. + struct _stat file_stats; + if (::_stat(file_spec.GetCString(), &file_stats) == 0) + { + // The owner permission bits in "st_mode" currently match the definitions + // for the owner file mode bits. + file_permissions = file_stats.st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC); + } + else + { + error.SetErrorToErrno(); + } + + return error; +} + +Error +FileSystem::SetFilePermissions(const FileSpec &file_spec, uint32_t file_permissions) +{ + Error error; + error.SetErrorStringWithFormat("%s is not supported on this host", __PRETTY_FUNCTION__); + return error; +} + +lldb::user_id_t +FileSystem::GetFileSize(const FileSpec &file_spec) +{ + return file_spec.GetByteSize(); +} + +bool +FileSystem::GetFileExists(const FileSpec &file_spec) +{ + return file_spec.Exists(); +} + +Error +FileSystem::Hardlink(const FileSpec &src, const FileSpec &dst) +{ + Error error; + if (!::CreateHardLink(src.GetCString(), dst.GetCString(), nullptr)) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +int +FileSystem::GetHardlinkCount(const FileSpec &file_spec) +{ + HANDLE file_handle = ::CreateFile(file_spec.GetCString(), + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (file_handle == INVALID_HANDLE_VALUE) + return -1; + + AutoHandle auto_file_handle(file_handle); + BY_HANDLE_FILE_INFORMATION file_info; + if (::GetFileInformationByHandle(file_handle, &file_info)) + return file_info.nNumberOfLinks; + + return -1; +} + +Error +FileSystem::Symlink(const FileSpec &src, const FileSpec &dst) +{ + Error error; + DWORD attrib = ::GetFileAttributes(dst.GetCString()); + if (attrib == INVALID_FILE_ATTRIBUTES) + { + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; + } + bool is_directory = !!(attrib & FILE_ATTRIBUTE_DIRECTORY); + DWORD flag = is_directory ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; + BOOL result = ::CreateSymbolicLink(src.GetCString(), dst.GetCString(), flag); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +Error +FileSystem::Unlink(const FileSpec &file_spec) +{ + Error error; + BOOL result = ::DeleteFile(file_spec.GetCString()); + if (!result) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; +} + +Error +FileSystem::Readlink(const FileSpec &src, FileSpec &dst) +{ + Error error; + HANDLE h = ::CreateFile(src.GetCString(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (h == INVALID_HANDLE_VALUE) + { + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + return error; + } + + char buf[PATH_MAX]; + // Subtract 1 from the path length since this function does not add a null terminator. + DWORD result = ::GetFinalPathNameByHandle(h, buf, sizeof(buf) - 1, + FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (result == 0) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + else + dst.SetFile(buf, false); + + ::CloseHandle(h); + return error; +} + +Error +FileSystem::ResolveSymbolicLink(const FileSpec &src, FileSpec &dst) +{ + return Error("ResolveSymbolicLink() isn't implemented on Windows"); +} + +bool +FileSystem::IsLocal(const FileSpec &spec) +{ + if (spec) + { + // TODO: return true if the file is on a locally mounted file system + return true; + } + + return false; +} diff --git a/source/Host/windows/Host.cpp b/source/Host/windows/Host.cpp new file mode 100644 index 0000000000000..2c9a139df256c --- /dev/null +++ b/source/Host/windows/Host.cpp @@ -0,0 +1,326 @@ +//===-- source/Host/windows/Host.cpp ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +#include <stdio.h> +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/AutoHandle.h" + +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Target/Process.h" + +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/StructuredData.h" + +// Windows includes +#include <TlHelp32.h> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ + bool GetTripleForProcess(const FileSpec &executable, llvm::Triple &triple) + { + // Open the PE File as a binary file, and parse just enough information to determine the + // machine type. + File imageBinary( + executable.GetPath().c_str(), + File::eOpenOptionRead, + lldb::eFilePermissionsUserRead); + imageBinary.SeekFromStart(0x3c); + int32_t peOffset = 0; + uint32_t peHead = 0; + uint16_t machineType = 0; + size_t readSize = sizeof(peOffset); + imageBinary.Read(&peOffset, readSize); + imageBinary.SeekFromStart(peOffset); + imageBinary.Read(&peHead, readSize); + if (peHead != 0x00004550) // "PE\0\0", little-endian + return false; // Error: Can't find PE header + readSize = 2; + imageBinary.Read(&machineType, readSize); + triple.setVendor(llvm::Triple::PC); + triple.setOS(llvm::Triple::Win32); + triple.setArch(llvm::Triple::UnknownArch); + if (machineType == 0x8664) + triple.setArch(llvm::Triple::x86_64); + else if (machineType == 0x14c) + triple.setArch(llvm::Triple::x86); + + return true; + } + + bool GetExecutableForProcess(const AutoHandle &handle, std::string &path) + { + // Get the process image path. MAX_PATH isn't long enough, paths can actually be up to 32KB. + std::vector<char> buffer(32768); + DWORD dwSize = buffer.size(); + if (!::QueryFullProcessImageNameA(handle.get(), 0, &buffer[0], &dwSize)) + return false; + path.assign(&buffer[0]); + return true; + } + + void GetProcessExecutableAndTriple(const AutoHandle &handle, ProcessInstanceInfo &process) + { + // We may not have permissions to read the path from the process. So start off by + // setting the executable file to whatever Toolhelp32 gives us, and then try to + // enhance this with more detailed information, but fail gracefully. + std::string executable; + llvm::Triple triple; + triple.setVendor(llvm::Triple::PC); + triple.setOS(llvm::Triple::Win32); + triple.setArch(llvm::Triple::UnknownArch); + if (GetExecutableForProcess(handle, executable)) + { + FileSpec executableFile(executable.c_str(), false); + process.SetExecutableFile(executableFile, true); + GetTripleForProcess(executableFile, triple); + } + process.SetArchitecture(ArchSpec(triple)); + + // TODO(zturner): Add the ability to get the process user name. + } +} + +lldb::DataBufferSP +Host::GetAuxvData(lldb_private::Process *process) +{ + return 0; +} + +lldb::tid_t +Host::GetCurrentThreadID() +{ + return lldb::tid_t(::GetCurrentThreadId()); +} + +lldb::thread_t +Host::GetCurrentThread () +{ + return lldb::thread_t(::GetCurrentThread()); +} + +lldb::thread_key_t +Host::ThreadLocalStorageCreate(ThreadLocalStorageCleanupCallback callback) +{ + return TlsAlloc(); +} + +void* +Host::ThreadLocalStorageGet(lldb::thread_key_t key) +{ + return ::TlsGetValue (key); +} + +void +Host::ThreadLocalStorageSet(lldb::thread_key_t key, void *value) +{ + ::TlsSetValue (key, value); +} + +void +Host::Kill(lldb::pid_t pid, int signo) +{ + TerminateProcess((HANDLE) pid, 1); +} + + +const char * +Host::GetSignalAsCString(int signo) +{ + return NULL; +} + +FileSpec +Host::GetModuleFileSpecForHostAddress (const void *host_addr) +{ + FileSpec module_filespec; + + HMODULE hmodule = NULL; + if (!::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)host_addr, &hmodule)) + return module_filespec; + + std::vector<char> buffer(MAX_PATH); + DWORD chars_copied = 0; + do { + chars_copied = ::GetModuleFileName(hmodule, &buffer[0], buffer.size()); + if (chars_copied == buffer.size() && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) + buffer.resize(buffer.size() * 2); + } while (chars_copied >= buffer.size()); + + module_filespec.SetFile(&buffer[0], false); + return module_filespec; +} + +uint32_t +Host::FindProcesses (const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) +{ + process_infos.Clear(); + + AutoHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + if (!snapshot.IsValid()) + return 0; + + PROCESSENTRY32 pe = {0}; + pe.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(snapshot.get(), &pe)) + { + do + { + AutoHandle handle(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID), nullptr); + + ProcessInstanceInfo process; + process.SetExecutableFile(FileSpec(pe.szExeFile, false), true); + process.SetProcessID(pe.th32ProcessID); + process.SetParentProcessID(pe.th32ParentProcessID); + GetProcessExecutableAndTriple(handle, process); + + if (match_info.MatchAllProcesses() || match_info.Matches(process)) + process_infos.Append(process); + } while (Process32Next(snapshot.get(), &pe)); + } + return process_infos.GetSize(); +} + +bool +Host::GetProcessInfo (lldb::pid_t pid, ProcessInstanceInfo &process_info) +{ + process_info.Clear(); + + AutoHandle handle(::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid), + nullptr); + if (!handle.IsValid()) + return false; + + process_info.SetProcessID(pid); + GetProcessExecutableAndTriple(handle, process_info); + + // Need to read the PEB to get parent process and command line arguments. + return true; +} + +HostThread +Host::StartMonitoringChildProcess(Host::MonitorChildProcessCallback callback, void *callback_baton, lldb::pid_t pid, bool monitor_signals) +{ + return HostThread(); +} + +Error +Host::ShellExpandArguments (ProcessLaunchInfo &launch_info) +{ + Error error; + if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) + { + FileSpec expand_tool_spec; + if (!HostInfo::GetLLDBPath(lldb::ePathTypeSupportExecutableDir, expand_tool_spec)) + { + error.SetErrorString("could not find support executable directory for the lldb-argdumper tool"); + return error; + } + expand_tool_spec.AppendPathComponent("lldb-argdumper.exe"); + if (!expand_tool_spec.Exists()) + { + error.SetErrorString("could not find the lldb-argdumper tool"); + return error; + } + + std::string quoted_cmd_string; + launch_info.GetArguments().GetQuotedCommandString(quoted_cmd_string); + std::replace(quoted_cmd_string.begin(), quoted_cmd_string.end(), '\\', '/'); + StreamString expand_command; + + expand_command.Printf("\"%s\" %s", + expand_tool_spec.GetPath().c_str(), + quoted_cmd_string.c_str()); + + int status; + std::string output; + RunShellCommand(expand_command.GetData(), launch_info.GetWorkingDirectory(), &status, nullptr, &output, 10); + + if (status != 0) + { + error.SetErrorStringWithFormat("lldb-argdumper exited with error %d", status); + return error; + } + + auto data_sp = StructuredData::ParseJSON(output); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto dict_sp = data_sp->GetAsDictionary(); + if (!data_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_sp = dict_sp->GetObjectForDotSeparatedPath("arguments"); + if (!args_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + auto args_array_sp = args_sp->GetAsArray(); + if (!args_array_sp) + { + error.SetErrorString("invalid JSON"); + return error; + } + + launch_info.GetArguments().Clear(); + + for (size_t i = 0; + i < args_array_sp->GetSize(); + i++) + { + auto item_sp = args_array_sp->GetItemAtIndex(i); + if (!item_sp) + continue; + auto str_sp = item_sp->GetAsString(); + if (!str_sp) + continue; + + launch_info.GetArguments().AppendArgument(str_sp->GetValue().c_str()); + } + } + + return error; +} + +size_t +Host::GetEnvironment(StringList &env) +{ + // The environment block on Windows is a contiguous buffer of NULL terminated strings, + // where the end of the environment block is indicated by two consecutive NULLs. + LPCH environment_block = ::GetEnvironmentStrings(); + env.Clear(); + while (*environment_block != '\0') + { + llvm::StringRef current_var(environment_block); + if (current_var[0] != '=') + env.AppendString(current_var); + + environment_block += current_var.size()+1; + } + return env.GetSize(); +} diff --git a/source/Host/windows/HostInfoWindows.cpp b/source/Host/windows/HostInfoWindows.cpp new file mode 100644 index 0000000000000..6dce71d9172ad --- /dev/null +++ b/source/Host/windows/HostInfoWindows.cpp @@ -0,0 +1,118 @@ +//===-- HostInfoWindows.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/windows/windows.h" + +#include <mutex> // std::once + +#include "lldb/Host/windows/HostInfoWindows.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Path.h" + +using namespace lldb_private; + +FileSpec HostInfoWindows::m_program_filespec; + +size_t +HostInfoWindows::GetPageSize() +{ + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + return systemInfo.dwPageSize; +} + +bool +HostInfoWindows::GetOSVersion(uint32_t &major, uint32_t &minor, uint32_t &update) +{ + OSVERSIONINFOEX info; + + ZeroMemory(&info, sizeof(OSVERSIONINFOEX)); + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); +#pragma warning(push) +#pragma warning(disable : 4996) + // Starting with Microsoft SDK for Windows 8.1, this function is deprecated in favor of the + // new Windows Version Helper APIs. Since we don't specify a minimum SDK version, it's easier + // to simply disable the warning rather than try to support both APIs. + if (GetVersionEx((LPOSVERSIONINFO)&info) == 0) + { + return false; + } +#pragma warning(pop) + + major = info.dwMajorVersion; + minor = info.dwMinorVersion; + update = info.wServicePackMajor; + + return true; +} + +bool +HostInfoWindows::GetOSBuildString(std::string &s) +{ + s.clear(); + uint32_t major, minor, update; + if (!GetOSVersion(major, minor, update)) + return false; + + llvm::raw_string_ostream stream(s); + stream << "Windows NT " << major << "." << minor << "." << update; + return true; +} + +bool +HostInfoWindows::GetOSKernelDescription(std::string &s) +{ + return GetOSBuildString(s); +} + +bool +HostInfoWindows::GetHostname(std::string &s) +{ + char buffer[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1; + if (!::GetComputerName(buffer, &dwSize)) + return false; + + s.assign(buffer, buffer + dwSize); + return true; +} + +FileSpec +HostInfoWindows::GetProgramFileSpec() +{ + static std::once_flag g_once_flag; + std::call_once(g_once_flag, []() { + char buffer[PATH_MAX]; + ::GetModuleFileName(NULL, buffer, sizeof(buffer)); + m_program_filespec.SetFile(buffer, false); + }); + return m_program_filespec; +} + +FileSpec +HostInfoWindows::GetDefaultShell() +{ + return FileSpec(::getenv("ComSpec"), false); +} + +bool +HostInfoWindows::ComputePythonDirectory(FileSpec &file_spec) +{ + FileSpec lldb_file_spec; + if (!GetLLDBPath(lldb::ePathTypeLLDBShlibDir, lldb_file_spec)) + return false; + llvm::SmallString<64> path(lldb_file_spec.GetDirectory().AsCString()); + llvm::sys::path::remove_filename(path); + llvm::sys::path::append(path, "lib", "site-packages"); + std::replace(path.begin(), path.end(), '\\', '/'); + file_spec.GetDirectory().SetString(path.c_str()); + return true; +} diff --git a/source/Host/windows/HostProcessWindows.cpp b/source/Host/windows/HostProcessWindows.cpp new file mode 100644 index 0000000000000..0f81c18d34afb --- /dev/null +++ b/source/Host/windows/HostProcessWindows.cpp @@ -0,0 +1,137 @@ +//===-- HostProcessWindows.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/FileSpec.h" +#include "lldb/Host/HostThread.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/HostProcessWindows.h" + +#include "llvm/ADT/STLExtras.h" + +#include <Psapi.h> + +using namespace lldb_private; + +namespace +{ +struct MonitorInfo +{ + HostProcess::MonitorCallback callback; + void *baton; + HANDLE process_handle; +}; +} + +HostProcessWindows::HostProcessWindows() + : HostNativeProcessBase() + , m_owns_handle(true) +{ +} + +HostProcessWindows::HostProcessWindows(lldb::process_t process) + : HostNativeProcessBase(process) + , m_owns_handle(true) +{ +} + +HostProcessWindows::~HostProcessWindows() +{ + Close(); +} + +void +HostProcessWindows::SetOwnsHandle(bool owns) +{ + m_owns_handle = owns; +} + +Error HostProcessWindows::Terminate() +{ + Error error; + if (m_process == nullptr) + error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32); + + if (!::TerminateProcess(m_process, 0)) + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + + return error; +} + +Error HostProcessWindows::GetMainModule(FileSpec &file_spec) const +{ + Error error; + if (m_process == nullptr) + error.SetError(ERROR_INVALID_HANDLE, lldb::eErrorTypeWin32); + + char path[MAX_PATH] = { 0 }; + if (::GetProcessImageFileName(m_process, path, llvm::array_lengthof(path))) + file_spec.SetFile(path, false); + else + error.SetError(::GetLastError(), lldb::eErrorTypeWin32); + + return error; +} + +lldb::pid_t HostProcessWindows::GetProcessId() const +{ + return (m_process == LLDB_INVALID_PROCESS) ? -1 : ::GetProcessId(m_process); +} + +bool HostProcessWindows::IsRunning() const +{ + if (m_process == nullptr) + return false; + + DWORD code = 0; + if (!::GetExitCodeProcess(m_process, &code)) + return false; + + return (code == STILL_ACTIVE); +} + +HostThread +HostProcessWindows::StartMonitoring(HostProcess::MonitorCallback callback, void *callback_baton, bool monitor_signals) +{ + HostThread monitor_thread; + MonitorInfo *info = new MonitorInfo; + info->callback = callback; + info->baton = callback_baton; + + // Since the life of this HostProcessWindows instance and the life of the process may be different, duplicate the handle so that + // the monitor thread can have ownership over its own copy of the handle. + HostThread result; + if (::DuplicateHandle(GetCurrentProcess(), m_process, GetCurrentProcess(), &info->process_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) + result = ThreadLauncher::LaunchThread("ChildProcessMonitor", HostProcessWindows::MonitorThread, info, nullptr); + return result; +} + +lldb::thread_result_t +HostProcessWindows::MonitorThread(void *thread_arg) +{ + DWORD exit_code; + + MonitorInfo *info = static_cast<MonitorInfo *>(thread_arg); + if (info) + { + ::WaitForSingleObject(info->process_handle, INFINITE); + ::GetExitCodeProcess(info->process_handle, &exit_code); + info->callback(info->baton, ::GetProcessId(info->process_handle), true, 0, exit_code); + ::CloseHandle(info->process_handle); + delete (info); + } + return 0; +} + +void HostProcessWindows::Close() +{ + if (m_owns_handle && m_process != LLDB_INVALID_PROCESS) + ::CloseHandle(m_process); + m_process = nullptr; +} diff --git a/source/Host/windows/HostThreadWindows.cpp b/source/Host/windows/HostThreadWindows.cpp new file mode 100644 index 0000000000000..d59064fb1c156 --- /dev/null +++ b/source/Host/windows/HostThreadWindows.cpp @@ -0,0 +1,98 @@ +//===-- HostThreadWindows.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/windows/windows.h" +#include "lldb/Host/windows/HostThreadWindows.h" + +#include "llvm/ADT/STLExtras.h" + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +void __stdcall ExitThreadProxy(ULONG_PTR dwExitCode) +{ + ::ExitThread(dwExitCode); +} +} + +HostThreadWindows::HostThreadWindows() + : HostNativeThreadBase() + , m_owns_handle(true) +{ +} + +HostThreadWindows::HostThreadWindows(lldb::thread_t thread) + : HostNativeThreadBase(thread) + , m_owns_handle(true) +{ +} + +HostThreadWindows::~HostThreadWindows() +{ + Reset(); +} + +void +HostThreadWindows::SetOwnsHandle(bool owns) +{ + m_owns_handle = owns; +} + +Error +HostThreadWindows::Join(lldb::thread_result_t *result) +{ + Error error; + if (IsJoinable()) + { + DWORD wait_result = ::WaitForSingleObject(m_thread, INFINITE); + if (WAIT_OBJECT_0 == wait_result && result) + { + DWORD exit_code = 0; + if (!::GetExitCodeThread(m_thread, &exit_code)) + *result = 0; + *result = exit_code; + } + else if (WAIT_OBJECT_0 != wait_result) + error.SetError(::GetLastError(), eErrorTypeWin32); + } + else + error.SetError(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + Reset (); + return error; +} + +Error +HostThreadWindows::Cancel() +{ + Error error; + + DWORD result = ::QueueUserAPC(::ExitThreadProxy, m_thread, 0); + error.SetError(result, eErrorTypeWin32); + return error; +} + +lldb::tid_t +HostThreadWindows::GetThreadId() const +{ + return ::GetThreadId(m_thread); +} + +void +HostThreadWindows::Reset() +{ + if (m_owns_handle && m_thread != LLDB_INVALID_HOST_THREAD) + ::CloseHandle(m_thread); + + HostNativeThreadBase::Reset(); +} diff --git a/source/Host/windows/LockFileWindows.cpp b/source/Host/windows/LockFileWindows.cpp new file mode 100644 index 0000000000000..161d3dbd82604 --- /dev/null +++ b/source/Host/windows/LockFileWindows.cpp @@ -0,0 +1,93 @@ +//===-- LockFileWindows.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/windows/LockFileWindows.h" + +#include <io.h> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ + +Error fileLock (HANDLE file_handle, DWORD flags, const uint64_t start, const uint64_t len) +{ + if (start != 0) + return Error ("Non-zero start lock regions are not supported"); + + OVERLAPPED overlapped = {0}; + + if (!::LockFileEx (file_handle, flags, 0, len, 0, &overlapped) && ::GetLastError () != ERROR_IO_PENDING) + return Error (::GetLastError (), eErrorTypeWin32); + + DWORD bytes; + if (!::GetOverlappedResult (file_handle, &overlapped, &bytes, TRUE)) + return Error (::GetLastError (), eErrorTypeWin32); + + return Error (); +} + +} // namespace + +LockFileWindows::LockFileWindows (int fd) + : LockFileBase (fd), + m_file (reinterpret_cast<HANDLE> (_get_osfhandle (fd))) +{ +} + +LockFileWindows::~LockFileWindows () +{ + Unlock (); +} + +bool +LockFileWindows::IsValidFile () const +{ + return LockFileBase::IsValidFile() && m_file != INVALID_HANDLE_VALUE; +} + +Error +LockFileWindows::DoWriteLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_EXCLUSIVE_LOCK, start, len); +} + +Error +LockFileWindows::DoTryWriteLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, start, len); +} + +Error +LockFileWindows::DoReadLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, 0, start, len); +} + +Error +LockFileWindows::DoTryReadLock (const uint64_t start, const uint64_t len) +{ + return fileLock (m_file, LOCKFILE_FAIL_IMMEDIATELY, start, len); +} + +Error +LockFileWindows::DoUnlock () +{ + OVERLAPPED overlapped = {0}; + + if (!::UnlockFileEx (m_file, 0, m_len, 0, &overlapped) && ::GetLastError () != ERROR_IO_PENDING) + return Error (::GetLastError (), eErrorTypeWin32); + + DWORD bytes; + if (!::GetOverlappedResult (m_file, &overlapped, &bytes, TRUE)) + return Error (::GetLastError (), eErrorTypeWin32); + + return Error (); +} diff --git a/source/Host/windows/Mutex.cpp b/source/Host/windows/Mutex.cpp new file mode 100644 index 0000000000000..ed90f46eb54bd --- /dev/null +++ b/source/Host/windows/Mutex.cpp @@ -0,0 +1,109 @@ +//===-- Mutex.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/Mutex.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/windows/windows.h" + +#include <string.h> +#include <stdio.h> + +#if 0 +// This logging is way too verbose to enable even for a log channel. +// This logging can be enabled by changing the "#if 0", but should be +// reverted prior to checking in. +#include <cstdio> +#define DEBUG_LOG(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_LOG(fmt, ...) +#endif + +using namespace lldb_private; + +//---------------------------------------------------------------------- +// Default constructor. +// +// Creates a pthread mutex with no attributes. +//---------------------------------------------------------------------- +Mutex::Mutex () : + m_mutex() +{ + m_mutex = static_cast<PCRITICAL_SECTION>(malloc(sizeof(CRITICAL_SECTION))); + InitializeCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); +} + +//---------------------------------------------------------------------- +// Default constructor. +// +// Creates a pthread mutex with "type" as the mutex type. +//---------------------------------------------------------------------- +Mutex::Mutex (Mutex::Type type) : + m_mutex() +{ + m_mutex = static_cast<PCRITICAL_SECTION>(malloc(sizeof(CRITICAL_SECTION))); + InitializeCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); +} + +//---------------------------------------------------------------------- +// Destructor. +// +// Destroys the mutex owned by this object. +//---------------------------------------------------------------------- +Mutex::~Mutex() +{ + DeleteCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + free(m_mutex); +} + +//---------------------------------------------------------------------- +// Locks the mutex owned by this object, if the mutex is already +// locked, the calling thread will block until the mutex becomes +// available. +// +// RETURNS +// The error code from the pthread_mutex_lock() function call. +//---------------------------------------------------------------------- +int +Mutex::Lock() +{ + DEBUG_LOG ("[%4.4" PRIx64 "/%4.4" PRIx64 "] pthread_mutex_lock (%p)...\n", Host::GetCurrentProcessID(), Host::GetCurrentThreadID(), m_mutex); + + EnterCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + return 0; +} + +//---------------------------------------------------------------------- +// Attempts to lock the mutex owned by this object without blocking. +// If the mutex is already locked, TryLock() will not block waiting +// for the mutex, but will return an error condition. +// +// RETURNS +// The error code from the pthread_mutex_trylock() function call. +//---------------------------------------------------------------------- +int +Mutex::TryLock(const char *failure_message) +{ + return TryEnterCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)) == 0; +} + +//---------------------------------------------------------------------- +// If the current thread holds the lock on the owned mutex, then +// Unlock() will unlock the mutex. Calling Unlock() on this object +// that the calling thread does not hold will result in undefined +// behavior. +// +// RETURNS +// The error code from the pthread_mutex_unlock() function call. +//---------------------------------------------------------------------- +int +Mutex::Unlock() +{ + LeaveCriticalSection(static_cast<PCRITICAL_SECTION>(m_mutex)); + return 0; +} diff --git a/source/Host/windows/PipeWindows.cpp b/source/Host/windows/PipeWindows.cpp new file mode 100644 index 0000000000000..d4afd6e749436 --- /dev/null +++ b/source/Host/windows/PipeWindows.cpp @@ -0,0 +1,336 @@ +//===-- PipeWindows.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/windows/PipeWindows.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/raw_ostream.h" + +#include <fcntl.h> +#include <io.h> +#include <rpc.h> + +#include <atomic> +#include <string> + +using namespace lldb; +using namespace lldb_private; + +namespace +{ +std::atomic<uint32_t> g_pipe_serial(0); +} + +PipeWindows::PipeWindows() +{ + m_read = INVALID_HANDLE_VALUE; + m_write = INVALID_HANDLE_VALUE; + + m_read_fd = -1; + m_write_fd = -1; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); +} + +PipeWindows::~PipeWindows() +{ + Close(); +} + +Error +PipeWindows::CreateNew(bool child_process_inherit) +{ + // Even for anonymous pipes, we open a named pipe. This is because you cannot get + // overlapped i/o on Windows without using a named pipe. So we synthesize a unique + // name. + uint32_t serial = g_pipe_serial.fetch_add(1); + std::string pipe_name; + llvm::raw_string_ostream pipe_name_stream(pipe_name); + pipe_name_stream << "lldb.pipe." << ::GetCurrentProcessId() << "." << serial; + pipe_name_stream.flush(); + + return CreateNew(pipe_name.c_str(), child_process_inherit); +} + +Error +PipeWindows::CreateNew(llvm::StringRef name, bool child_process_inherit) +{ + if (name.empty()) + return Error(ERROR_INVALID_PARAMETER, eErrorTypeWin32); + + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + std::string pipe_path = "\\\\.\\Pipe\\"; + pipe_path.append(name); + + // Always open for overlapped i/o. We implement blocking manually in Read and Write. + DWORD read_mode = FILE_FLAG_OVERLAPPED; + m_read = + ::CreateNamedPipe(pipe_path.c_str(), PIPE_ACCESS_INBOUND | read_mode, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 1024, 1024, 120 * 1000, NULL); + if (INVALID_HANDLE_VALUE == m_read) + return Error(::GetLastError(), eErrorTypeWin32); + m_read_fd = _open_osfhandle((intptr_t)m_read, _O_RDONLY); + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + m_read_overlapped.hEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + + // Open the write end of the pipe. + Error result = OpenNamedPipe(name, child_process_inherit, false); + if (!result.Success()) + { + CloseReadFileDescriptor(); + return result; + } + + return result; +} + +Error +PipeWindows::CreateWithUniqueName(llvm::StringRef prefix, bool child_process_inherit, llvm::SmallVectorImpl<char>& name) +{ + llvm::SmallString<128> pipe_name; + Error error; + ::UUID unique_id; + RPC_CSTR unique_string; + RPC_STATUS status = ::UuidCreate(&unique_id); + if (status == RPC_S_OK || status == RPC_S_UUID_LOCAL_ONLY) + status = ::UuidToStringA(&unique_id, &unique_string); + if (status == RPC_S_OK) + { + pipe_name = prefix; + pipe_name += "-"; + pipe_name += reinterpret_cast<char *>(unique_string); + ::RpcStringFreeA(&unique_string); + error = CreateNew(pipe_name, child_process_inherit); + } + else + { + error.SetError(status, eErrorTypeWin32); + } + if (error.Success()) + name = pipe_name; + return error; +} + +Error +PipeWindows::OpenAsReader(llvm::StringRef name, bool child_process_inherit) +{ + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + return OpenNamedPipe(name, child_process_inherit, true); +} + +Error +PipeWindows::OpenAsWriterWithTimeout(llvm::StringRef name, bool child_process_inherit, const std::chrono::microseconds &timeout) +{ + if (CanRead() || CanWrite()) + return Error(ERROR_ALREADY_EXISTS, eErrorTypeWin32); + + return OpenNamedPipe(name, child_process_inherit, false); +} + +Error +PipeWindows::OpenNamedPipe(llvm::StringRef name, bool child_process_inherit, bool is_read) +{ + if (name.empty()) + return Error(ERROR_INVALID_PARAMETER, eErrorTypeWin32); + + assert(is_read ? !CanRead() : !CanWrite()); + + SECURITY_ATTRIBUTES attributes = {0}; + attributes.bInheritHandle = child_process_inherit; + + std::string pipe_path = "\\\\.\\Pipe\\"; + pipe_path.append(name); + + if (is_read) + { + m_read = ::CreateFile(pipe_path.c_str(), GENERIC_READ, 0, &attributes, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (INVALID_HANDLE_VALUE == m_read) + return Error(::GetLastError(), eErrorTypeWin32); + + m_read_fd = _open_osfhandle((intptr_t)m_read, _O_RDONLY); + + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + m_read_overlapped.hEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + } + else + { + m_write = ::CreateFile(pipe_path.c_str(), GENERIC_WRITE, 0, &attributes, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (INVALID_HANDLE_VALUE == m_write) + return Error(::GetLastError(), eErrorTypeWin32); + + m_write_fd = _open_osfhandle((intptr_t)m_write, _O_WRONLY); + + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); + } + + return Error(); +} + +int +PipeWindows::GetReadFileDescriptor() const +{ + return m_read_fd; +} + +int +PipeWindows::GetWriteFileDescriptor() const +{ + return m_write_fd; +} + +int +PipeWindows::ReleaseReadFileDescriptor() +{ + if (!CanRead()) + return -1; + int result = m_read_fd; + m_read_fd = -1; + if (m_read_overlapped.hEvent) + ::CloseHandle(m_read_overlapped.hEvent); + m_read = INVALID_HANDLE_VALUE; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); + return result; +} + +int +PipeWindows::ReleaseWriteFileDescriptor() +{ + if (!CanWrite()) + return -1; + int result = m_write_fd; + m_write_fd = -1; + m_write = INVALID_HANDLE_VALUE; + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); + return result; +} + +void +PipeWindows::CloseReadFileDescriptor() +{ + if (!CanRead()) + return; + + if (m_read_overlapped.hEvent) + ::CloseHandle(m_read_overlapped.hEvent); + _close(m_read_fd); + m_read = INVALID_HANDLE_VALUE; + m_read_fd = -1; + ZeroMemory(&m_read_overlapped, sizeof(m_read_overlapped)); +} + +void +PipeWindows::CloseWriteFileDescriptor() +{ + if (!CanWrite()) + return; + + _close(m_write_fd); + m_write = INVALID_HANDLE_VALUE; + m_write_fd = -1; + ZeroMemory(&m_write_overlapped, sizeof(m_write_overlapped)); +} + +void +PipeWindows::Close() +{ + CloseReadFileDescriptor(); + CloseWriteFileDescriptor(); +} + +Error +PipeWindows::Delete(llvm::StringRef name) +{ + return Error(); +} + +bool +PipeWindows::CanRead() const +{ + return (m_read != INVALID_HANDLE_VALUE); +} + +bool +PipeWindows::CanWrite() const +{ + return (m_write != INVALID_HANDLE_VALUE); +} + +HANDLE +PipeWindows::GetReadNativeHandle() +{ + return m_read; +} + +HANDLE +PipeWindows::GetWriteNativeHandle() +{ + return m_write; +} + +Error +PipeWindows::ReadWithTimeout(void *buf, size_t size, const std::chrono::microseconds &duration, size_t &bytes_read) +{ + if (!CanRead()) + return Error(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + bytes_read = 0; + DWORD sys_bytes_read = size; + BOOL result = ::ReadFile(m_read, buf, sys_bytes_read, &sys_bytes_read, &m_read_overlapped); + if (!result && GetLastError() != ERROR_IO_PENDING) + return Error(::GetLastError(), eErrorTypeWin32); + + DWORD timeout = (duration == std::chrono::microseconds::zero()) ? INFINITE : duration.count() * 1000; + DWORD wait_result = ::WaitForSingleObject(m_read_overlapped.hEvent, timeout); + if (wait_result != WAIT_OBJECT_0) + { + // The operation probably failed. However, if it timed out, we need to cancel the I/O. + // Between the time we returned from WaitForSingleObject and the time we call CancelIoEx, + // the operation may complete. If that hapens, CancelIoEx will fail and return ERROR_NOT_FOUND. + // If that happens, the original operation should be considered to have been successful. + bool failed = true; + DWORD failure_error = ::GetLastError(); + if (wait_result == WAIT_TIMEOUT) + { + BOOL cancel_result = CancelIoEx(m_read, &m_read_overlapped); + if (!cancel_result && GetLastError() == ERROR_NOT_FOUND) + failed = false; + } + if (failed) + return Error(failure_error, eErrorTypeWin32); + } + + // Now we call GetOverlappedResult setting bWait to false, since we've already waited + // as long as we're willing to. + if (!GetOverlappedResult(m_read, &m_read_overlapped, &sys_bytes_read, FALSE)) + return Error(::GetLastError(), eErrorTypeWin32); + + bytes_read = sys_bytes_read; + return Error(); +} + +Error +PipeWindows::Write(const void *buf, size_t num_bytes, size_t &bytes_written) +{ + if (!CanWrite()) + return Error(ERROR_INVALID_HANDLE, eErrorTypeWin32); + + DWORD sys_bytes_written = 0; + BOOL write_result = ::WriteFile(m_write, buf, num_bytes, &sys_bytes_written, &m_write_overlapped); + if (!write_result && GetLastError() != ERROR_IO_PENDING) + return Error(::GetLastError(), eErrorTypeWin32); + + BOOL result = GetOverlappedResult(m_write, &m_write_overlapped, &sys_bytes_written, TRUE); + if (!result) + return Error(::GetLastError(), eErrorTypeWin32); + return Error(); +} diff --git a/source/Host/windows/ProcessLauncherWindows.cpp b/source/Host/windows/ProcessLauncherWindows.cpp new file mode 100644 index 0000000000000..355dd40544f9a --- /dev/null +++ b/source/Host/windows/ProcessLauncherWindows.cpp @@ -0,0 +1,105 @@ +//===-- 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/Host/HostProcess.h" +#include "lldb/Host/windows/ProcessLauncherWindows.h" +#include "lldb/Target/ProcessLaunchInfo.h" + +#include <string> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +HostProcess +ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, Error &error) +{ + error.Clear(); + + std::string executable; + std::string commandLine; + std::vector<char> environment; + STARTUPINFO startupinfo = {0}; + PROCESS_INFORMATION pi = {0}; + + HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); + HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); + HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO); + + startupinfo.cb = sizeof(startupinfo); + startupinfo.dwFlags |= STARTF_USESTDHANDLES; + startupinfo.hStdError = stderr_handle ? stderr_handle : ::GetStdHandle(STD_ERROR_HANDLE); + startupinfo.hStdInput = stdin_handle ? stdin_handle : ::GetStdHandle(STD_INPUT_HANDLE); + startupinfo.hStdOutput = stdout_handle ? stdout_handle : ::GetStdHandle(STD_OUTPUT_HANDLE); + + const char *hide_console_var = getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"); + if (hide_console_var && llvm::StringRef(hide_console_var).equals_lower("true")) + { + startupinfo.dwFlags |= STARTF_USESHOWWINDOW; + startupinfo.wShowWindow = SW_HIDE; + } + + DWORD flags = CREATE_NEW_CONSOLE; + if (launch_info.GetFlags().Test(eLaunchFlagDebug)) + flags |= DEBUG_ONLY_THIS_PROCESS; + + executable = launch_info.GetExecutableFile().GetPath(); + launch_info.GetArguments().GetQuotedCommandString(commandLine); + BOOL result = ::CreateProcessA(executable.c_str(), const_cast<char *>(commandLine.c_str()), NULL, NULL, TRUE, flags, NULL, + launch_info.GetWorkingDirectory().GetCString(), &startupinfo, &pi); + if (result) + { + // Do not call CloseHandle on pi.hProcess, since we want to pass that back through the HostProcess. + ::CloseHandle(pi.hThread); + } + + if (stdin_handle) + ::CloseHandle(stdin_handle); + if (stdout_handle) + ::CloseHandle(stdout_handle); + if (stderr_handle) + ::CloseHandle(stderr_handle); + + if (!result) + error.SetError(::GetLastError(), eErrorTypeWin32); + return HostProcess(pi.hProcess); +} + +HANDLE +ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd) +{ + const FileAction *action = launch_info.GetFileActionForFD(fd); + if (action == nullptr) + return NULL; + SECURITY_ATTRIBUTES secattr = {0}; + secattr.nLength = sizeof(SECURITY_ATTRIBUTES); + secattr.bInheritHandle = TRUE; + + const char *path = action->GetPath(); + DWORD access = 0; + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + DWORD create = 0; + DWORD flags = 0; + if (fd == STDIN_FILENO) + { + access = GENERIC_READ; + create = OPEN_EXISTING; + flags = FILE_ATTRIBUTE_READONLY; + } + if (fd == STDOUT_FILENO || fd == STDERR_FILENO) + { + access = GENERIC_WRITE; + create = CREATE_ALWAYS; + if (fd == STDERR_FILENO) + flags = FILE_FLAG_WRITE_THROUGH; + } + + HANDLE result = ::CreateFile(path, access, share, &secattr, create, flags, NULL); + return (result == INVALID_HANDLE_VALUE) ? NULL : result; +} diff --git a/source/Host/windows/ProcessRunLock.cpp b/source/Host/windows/ProcessRunLock.cpp new file mode 100644 index 0000000000000..1f21552a30623 --- /dev/null +++ b/source/Host/windows/ProcessRunLock.cpp @@ -0,0 +1,106 @@ +#include "lldb/Host/ProcessRunLock.h" +#include "lldb/Host/windows/windows.h" + +namespace +{ +#if defined(__MINGW32__) +// Taken from WinNT.h +typedef struct _RTL_SRWLOCK { + PVOID Ptr; +} RTL_SRWLOCK, *PRTL_SRWLOCK; + +// Taken from WinBase.h +typedef RTL_SRWLOCK SRWLOCK, *PSRWLOCK; +#endif +} + + +static PSRWLOCK GetLock(lldb::rwlock_t lock) +{ + return static_cast<PSRWLOCK>(lock); +} + +static bool ReadLock(lldb::rwlock_t rwlock) +{ + ::AcquireSRWLockShared(GetLock(rwlock)); + return true; +} + +static bool ReadUnlock(lldb::rwlock_t rwlock) +{ + ::ReleaseSRWLockShared(GetLock(rwlock)); + return true; +} + +static bool WriteLock(lldb::rwlock_t rwlock) +{ + ::AcquireSRWLockExclusive(GetLock(rwlock)); + return true; +} + +static bool WriteTryLock(lldb::rwlock_t rwlock) +{ + return !!::TryAcquireSRWLockExclusive(GetLock(rwlock)); +} + +static bool WriteUnlock(lldb::rwlock_t rwlock) +{ + ::ReleaseSRWLockExclusive(GetLock(rwlock)); + return true; +} + +using namespace lldb_private; + +ProcessRunLock::ProcessRunLock() + : m_running(false) +{ + m_rwlock = new SRWLOCK; + InitializeSRWLock(GetLock(m_rwlock)); +} + +ProcessRunLock::~ProcessRunLock() +{ + delete m_rwlock; +} + +bool ProcessRunLock::ReadTryLock() +{ + ::ReadLock(m_rwlock); + if (m_running == false) + return true; + ::ReadUnlock(m_rwlock); + return false; +} + +bool ProcessRunLock::ReadUnlock() +{ + return ::ReadUnlock(m_rwlock); +} + +bool ProcessRunLock::SetRunning () +{ + WriteLock(m_rwlock); + m_running = true; + WriteUnlock(m_rwlock); + return true; +} + +bool ProcessRunLock::TrySetRunning () +{ + if (WriteTryLock(m_rwlock)) + { + bool was_running = m_running; + m_running = true; + WriteUnlock(m_rwlock); + return !was_running; + } + return false; +} + +bool ProcessRunLock::SetStopped () +{ + WriteLock(m_rwlock); + m_running = false; + WriteUnlock(m_rwlock); + return true; +} diff --git a/source/Host/windows/ThisThread.cpp b/source/Host/windows/ThisThread.cpp new file mode 100644 index 0000000000000..bcd5b8d1c1d09 --- /dev/null +++ b/source/Host/windows/ThisThread.cpp @@ -0,0 +1,66 @@ +//===-- 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/windows/windows.h" +#include "lldb/Host/ThisThread.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +using namespace lldb; +using namespace lldb_private; + +#if defined(_MSC_VER) && !defined(__clang__) + +namespace +{ +static const DWORD MS_VC_EXCEPTION = 0x406D1388; + +#pragma pack(push, 8) +struct THREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to thread name + DWORD dwThreadId; // Thread ID (-1 == current thread) + DWORD dwFlags; // Reserved. Do not use. +}; +#pragma pack(pop) +} + +#endif + +void +ThisThread::SetName(llvm::StringRef name) +{ +// Other compilers don't yet support SEH, so we can only set the thread if compiling with MSVC. +// TODO(zturner): Once clang-cl supports SEH, relax this conditional. +#if defined(_MSC_VER) && !defined(__clang__) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name.data(); + info.dwThreadId = ::GetCurrentThreadId(); + info.dwFlags = 0; + + __try { + ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)&info); + } + __except(EXCEPTION_EXECUTE_HANDLER) {} +#endif +} + +void +ThisThread::GetName(llvm::SmallVectorImpl<char> &name) +{ + // Getting the thread name is not supported on Windows. + // TODO(zturner): In SetName(), make a TLS entry that contains the thread's name, and in this function + // try to extract that TLS entry. + name.clear(); +} diff --git a/source/Host/windows/Windows.cpp b/source/Host/windows/Windows.cpp new file mode 100644 index 0000000000000..71bff7a9d2e2f --- /dev/null +++ b/source/Host/windows/Windows.cpp @@ -0,0 +1,231 @@ +//===-- Windows.cpp ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This file provides Windows support functions + +#include "lldb/Host/windows/windows.h" +#include "lldb/Host/windows/win32.h" + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <io.h> +#include <cerrno> +#include <ctype.h> + +// These prototypes are defined in <direct.h>, but it also defines chdir() and getcwd(), giving multiply defined errors +extern "C" +{ + char *_getcwd(char *buffer, int maxlen); + int _chdir(const char *path); +} + +int vasprintf(char **ret, const char *fmt, va_list ap) +{ + char *buf; + int len; + size_t buflen; + va_list ap2; + +#if defined(_MSC_VER) || defined(__MINGW64) + ap2 = ap; + len = _vscprintf(fmt, ap2); +#else + va_copy(ap2, ap); + len = vsnprintf(NULL, 0, fmt, ap2); +#endif + + if (len >= 0 && (buf = (char*) malloc ((buflen = (size_t) (len + 1)))) != NULL) { + len = vsnprintf(buf, buflen, fmt, ap); + *ret = buf; + } else { + *ret = NULL; + len = -1; + } + + va_end(ap2); + return len; +} + +char* strcasestr(const char *s, const char* find) +{ + char c, sc; + size_t len; + + if ((c = *find++) != 0) { + c = tolower((unsigned char) c); + len = strlen(find); + do { + do { + if ((sc = *s++) == 0) + return 0; + } while ((char) tolower((unsigned char) sc) != c); + } while (strncasecmp(s, find, len) != 0); + s--; + } + return ((char *) s); +} + +char* realpath(const char * name, char * resolved) +{ + char *retname = NULL; /* we will return this, if we fail */ + + /* SUSv3 says we must set `errno = EINVAL', and return NULL, + * if `name' is passed as a NULL pointer. + */ + + if (name == NULL) + errno = EINVAL; + + /* Otherwise, `name' must refer to a readable filesystem object, + * if we are going to resolve its absolute path name. + */ + + else if (access(name, 4) == 0) + { + /* If `name' didn't point to an existing entity, + * then we don't get to here; we simply fall past this block, + * returning NULL, with `errno' appropriately set by `access'. + * + * When we _do_ get to here, then we can use `_fullpath' to + * resolve the full path for `name' into `resolved', but first, + * check that we have a suitable buffer, in which to return it. + */ + + if ((retname = resolved) == NULL) + { + /* Caller didn't give us a buffer, so we'll exercise the + * option granted by SUSv3, and allocate one. + * + * `_fullpath' would do this for us, but it uses `malloc', and + * Microsoft's implementation doesn't set `errno' on failure. + * If we don't do this explicitly ourselves, then we will not + * know if `_fullpath' fails on `malloc' failure, or for some + * other reason, and we want to set `errno = ENOMEM' for the + * `malloc' failure case. + */ + + retname = (char*) malloc(_MAX_PATH); + } + + /* By now, we should have a valid buffer. + * If we don't, then we know that `malloc' failed, + * so we can set `errno = ENOMEM' appropriately. + */ + + if (retname == NULL) + errno = ENOMEM; + + /* Otherwise, when we do have a valid buffer, + * `_fullpath' should only fail if the path name is too long. + */ + + else if ((retname = _fullpath(retname, name, _MAX_PATH)) == NULL) + errno = ENAMETOOLONG; + } + + /* By the time we get to here, + * `retname' either points to the required resolved path name, + * or it is NULL, with `errno' set appropriately, either of which + * is our required return condition. + */ + + if (retname != NULL) + { + // Do a LongPath<->ShortPath roundtrip so that case is resolved by OS + int initialLength = strlen(retname); + TCHAR buffer[MAX_PATH]; + GetShortPathName(retname, buffer, MAX_PATH); + GetLongPathName(buffer, retname, initialLength + 1); + + // Force drive to be upper case + if (retname[1] == ':') + retname[0] = toupper(retname[0]); + } + + return retname; +} + +#ifdef _MSC_VER + +char* basename(char *path) +{ + char* l1 = strrchr(path, '\\'); + char* l2 = strrchr(path, '/'); + if (l2 > l1) l1 = l2; + if (!l1) return path; // no base name + return &l1[1]; +} + +// use _getcwd() instead of GetCurrentDirectory() because it updates errno +char* getcwd(char* path, int max) +{ + return _getcwd(path, max); +} + +// use _chdir() instead of SetCurrentDirectory() because it updates errno +int chdir(const char* path) +{ + return _chdir(path); +} + +char *dirname(char *path) +{ + char* l1 = strrchr(path, '\\'); + char* l2 = strrchr(path, '/'); + if (l2 > l1) l1 = l2; + if (!l1) return NULL; // no dir name + *l1 = 0; + return path; +} + +int strcasecmp(const char* s1, const char* s2) +{ + return stricmp(s1, s2); +} + +int strncasecmp(const char* s1, const char* s2, size_t n) +{ + return strnicmp(s1, s2, n); +} + +int usleep(uint32_t useconds) +{ + Sleep(useconds / 1000); + return 0; +} + +#if _MSC_VER < 1900 +namespace lldb_private { +int vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ + int old_errno = errno; + int r = ::vsnprintf(buffer, count, format, argptr); + int new_errno = errno; + buffer[count-1] = '\0'; + if (r == -1 || r == count) + { + FILE *nul = fopen("nul", "w"); + int bytes_written = ::vfprintf(nul, format, argptr); + fclose(nul); + if (bytes_written < count) + errno = new_errno; + else + { + errno = old_errno; + r = bytes_written; + } + } + return r; +} +} // namespace lldb_private +#endif + +#endif // _MSC_VER |