//===-- MIDriver.cpp --------------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // Third party headers: #include "lldb/API/SBError.h" #include #include #include // In-house headers: #include "MICmdArgValFile.h" #include "MICmdArgValString.h" #include "MICmdMgr.h" #include "MICmnConfig.h" #include "MICmnLLDBDebugSessionInfo.h" #include "MICmnLLDBDebugger.h" #include "MICmnLog.h" #include "MICmnMIResultRecord.h" #include "MICmnMIValueConst.h" #include "MICmnResources.h" #include "MICmnStreamStderr.h" #include "MICmnStreamStdout.h" #include "MICmnThreadMgrStd.h" #include "MIDriver.h" #include "MIUtilDebug.h" #include "MIUtilSingletonHelper.h" // Instantiations: #if _DEBUG const CMIUtilString CMIDriver::ms_constMIVersion = MIRSRC(IDS_MI_VERSION_DESCRIPTION_DEBUG); #else const CMIUtilString CMIDriver::ms_constMIVersion = MIRSRC(IDS_MI_VERSION_DESCRIPTION); // Matches version in resources file #endif // _DEBUG const CMIUtilString CMIDriver::ms_constAppNameShort(MIRSRC(IDS_MI_APPNAME_SHORT)); const CMIUtilString CMIDriver::ms_constAppNameLong(MIRSRC(IDS_MI_APPNAME_LONG)); //++ //------------------------------------------------------------------------------------ // Details: CMIDriver constructor. // Type: Method. // Args: None. // Return: None. // Throws: None. //-- CMIDriver::CMIDriver() : m_bFallThruToOtherDriverEnabled(false), m_bDriverIsExiting(false), m_handleMainThread(0), m_rStdin(CMICmnStreamStdin::Instance()), m_rLldbDebugger(CMICmnLLDBDebugger::Instance()), m_rStdOut(CMICmnStreamStdout::Instance()), m_eCurrentDriverState(eDriverState_NotRunning), m_bHaveExecutableFileNamePathOnCmdLine(false), m_bDriverDebuggingArgExecutable(false), m_bHaveCommandFileNamePathOnCmdLine(false) {} //++ //------------------------------------------------------------------------------------ // Details: CMIDriver destructor. // Type: Overridden. // Args: None. // Return: None. // Throws: None. //-- CMIDriver::~CMIDriver() {} //++ //------------------------------------------------------------------------------------ // Details: Set whether *this driver (the parent) is enabled to pass a command // to its // fall through (child) driver to interpret the command and do work // instead // (if *this driver decides it can't handle the command). // Type: Method. // Args: vbYes - (R) True = yes fall through, false = do not pass on // command. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::SetEnableFallThru(const bool vbYes) { m_bFallThruToOtherDriverEnabled = vbYes; return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Get whether *this driver (the parent) is enabled to pass a command // to its // fall through (child) driver to interpret the command and do work // instead // (if *this driver decides it can't handle the command). // Type: Method. // Args: None. // Return: bool - True = yes fall through, false = do not pass on command. // Throws: None. //-- bool CMIDriver::GetEnableFallThru() const { return m_bFallThruToOtherDriverEnabled; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve MI's application name of itself. // Type: Method. // Args: None. // Return: CMIUtilString & - Text description. // Throws: None. //-- const CMIUtilString &CMIDriver::GetAppNameShort() const { return ms_constAppNameShort; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve MI's application name of itself. // Type: Method. // Args: None. // Return: CMIUtilString & - Text description. // Throws: None. //-- const CMIUtilString &CMIDriver::GetAppNameLong() const { return ms_constAppNameLong; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve MI's version description of itself. // Type: Method. // Args: None. // Return: CMIUtilString & - Text description. // Throws: None. //-- const CMIUtilString &CMIDriver::GetVersionDescription() const { return ms_constMIVersion; } //++ //------------------------------------------------------------------------------------ // Details: Initialize setup *this driver ready for use. // Type: Method. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::Initialize() { m_eCurrentDriverState = eDriverState_Initialising; m_clientUsageRefCnt++; ClrErrorDescription(); if (m_bInitialized) return MIstatus::success; bool bOk = MIstatus::success; CMIUtilString errMsg; // Initialize all of the modules we depend on MI::ModuleInit(IDS_MI_INIT_ERR_LOG, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_STREAMSTDOUT, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_STREAMSTDERR, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_RESOURCES, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_THREADMANAGER, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_STREAMSTDIN, bOk, errMsg); MI::ModuleInit(IDS_MI_INIT_ERR_CMDMGR, bOk, errMsg); bOk &= m_rLldbDebugger.SetDriver(*this); MI::ModuleInit(IDS_MI_INIT_ERR_LLDBDEBUGGER, bOk, errMsg); m_bExitApp = false; m_bInitialized = bOk; if (!bOk) { const CMIUtilString msg = CMIUtilString::Format(MIRSRC(IDS_MI_INIT_ERR_DRIVER), errMsg.c_str()); SetErrorDescription(msg); return MIstatus::failure; } m_eCurrentDriverState = eDriverState_RunningNotDebugging; return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Unbind detach or release resources used by *this driver. // Type: Method. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::Shutdown() { if (--m_clientUsageRefCnt > 0) return MIstatus::success; if (!m_bInitialized) return MIstatus::success; m_eCurrentDriverState = eDriverState_ShuttingDown; ClrErrorDescription(); bool bOk = MIstatus::success; CMIUtilString errMsg; // Shutdown all of the modules we depend on MI::ModuleShutdown(IDS_MI_INIT_ERR_LLDBDEBUGGER, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_CMDMGR, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_STREAMSTDIN, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_THREADMANAGER, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_RESOURCES, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_STREAMSTDERR, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_STREAMSTDOUT, bOk, errMsg); MI::ModuleShutdown(IDS_MI_INIT_ERR_LOG, bOk, errMsg); if (!bOk) { SetErrorDescriptionn(MIRSRC(IDS_MI_SHUTDOWN_ERR), errMsg.c_str()); } m_eCurrentDriverState = eDriverState_NotRunning; return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Work function. Client (the driver's user) is able to append their // own message // in to the MI's Log trace file. // Type: Method. // Args: vMessage - (R) Client's text message. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::WriteMessageToLog(const CMIUtilString &vMessage) { CMIUtilString msg; msg = CMIUtilString::Format(MIRSRC(IDS_MI_CLIENT_MSG), vMessage.c_str()); return m_pLog->Write(msg, CMICmnLog::eLogVerbosity_ClientMsg); } //++ //------------------------------------------------------------------------------------ // Details: CDriverMgr calls *this driver initialize setup ready for use. // Type: Overridden. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::DoInitialize() { return CMIDriver::Instance().Initialize(); } //++ //------------------------------------------------------------------------------------ // Details: CDriverMgr calls *this driver to unbind detach or release resources // used by // *this driver. // Type: Overridden. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::DoShutdown() { return CMIDriver::Instance().Shutdown(); } //++ //------------------------------------------------------------------------------------ // Details: Retrieve the name for *this driver. // Type: Overridden. // Args: None. // Return: CMIUtilString & - Driver name. // Throws: None. //-- const CMIUtilString &CMIDriver::GetName() const { const CMIUtilString &rName = GetAppNameLong(); const CMIUtilString &rVsn = GetVersionDescription(); static CMIUtilString strName = CMIUtilString::Format("%s %s", rName.c_str(), rVsn.c_str()); return strName; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve *this driver's last error condition. // Type: Overridden. // Args: None. // Return: CMIUtilString - Text description. // Throws: None. //-- CMIUtilString CMIDriver::GetError() const { return GetErrorDescription(); } //++ //------------------------------------------------------------------------------------ // Details: Call *this driver to return it's debugger. // Type: Overridden. // Args: None. // Return: lldb::SBDebugger & - LLDB debugger object reference. // Throws: None. //-- lldb::SBDebugger &CMIDriver::GetTheDebugger() { return m_rLldbDebugger.GetTheDebugger(); } //++ //------------------------------------------------------------------------------------ // Details: Specify another driver *this driver can call should this driver not // be able // to handle the client data input. DoFallThruToAnotherDriver() makes // the call. // Type: Overridden. // Args: vrOtherDriver - (R) Reference to another driver object. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::SetDriverToFallThruTo(const CMIDriverBase &vrOtherDriver) { m_pDriverFallThru = const_cast(&vrOtherDriver); return m_pDriverFallThru->SetDriverParent(*this); } //++ //------------------------------------------------------------------------------------ // Details: Proxy function CMIDriverMgr IDriver interface implementation. *this // driver's // implementation called from here to match the existing function name // of the // original LLDB driver class (the extra indirection is not necessarily // required). // Check the arguments that were passed to this program to make sure // they are // valid and to get their argument values (if any). // Type: Overridden. // Args: argc - (R) An integer that contains the count of arguments // that follow in // argv. The argc parameter is always greater than // or equal to 1. // argv - (R) An array of null-terminated strings representing // command-line // arguments entered by the user of the program. By // convention, // argv[0] is the command with which the program is // invoked. // vpStdOut - (R) Pointer to a standard output stream. // vwbExiting - (W) True = *this want to exit, Reasons: help, // invalid arg(s), // version information only. // False = Continue to work, start debugger i.e. // Command // interpreter. // Return: lldb::SBError - LLDB current error status. // Throws: None. //-- lldb::SBError CMIDriver::DoParseArgs(const int argc, const char *argv[], FILE *vpStdOut, bool &vwbExiting) { return ParseArgs(argc, argv, vpStdOut, vwbExiting); } //++ //------------------------------------------------------------------------------------ // Details: Check the arguments that were passed to this program to make sure // they are // valid and to get their argument values (if any). The following are // options // that are only handled by *this driver: // --executable // --source or -s // --synchronous // The application's options --interpreter and --executable in code act // very similar. // The --executable is necessary to differentiate whether the MI Driver // is being // used by a client (e.g. Eclipse) or from the command line. Eclipse // issues the option // --interpreter and also passes additional arguments which can be // interpreted as an // executable if called from the command line. Using --executable tells // the MI Driver // it is being called from the command line and to prepare to launch // the executable // argument for a debug session. Using --interpreter on the command // line does not // issue additional commands to initialise a debug session. // Option --synchronous disables an asynchronous mode in the lldb-mi driver. // Type: Overridden. // Args: argc - (R) An integer that contains the count of arguments // that follow in // argv. The argc parameter is always greater than // or equal to 1. // argv - (R) An array of null-terminated strings representing // command-line // arguments entered by the user of the program. By // convention, // argv[0] is the command with which the program is // invoked. // vpStdOut - (R) Pointer to a standard output stream. // vwbExiting - (W) True = *this want to exit, Reasons: help, // invalid arg(s), // version information only. // False = Continue to work, start debugger i.e. // Command // interpreter. // Return: lldb::SBError - LLDB current error status. // Throws: None. //-- lldb::SBError CMIDriver::ParseArgs(const int argc, const char *argv[], FILE *vpStdOut, bool &vwbExiting) { lldb::SBError errStatus; const bool bHaveArgs(argc >= 2); // *** Add any args handled here to GetHelpOnCmdLineArgOptions() *** // CODETAG_MIDRIVE_CMD_LINE_ARG_HANDLING // Look for the command line options bool bHaveExecutableFileNamePath = false; bool bHaveExecutableLongOption = false; if (bHaveArgs) { // Search right to left to look for filenames for (MIint i = argc - 1; i > 0; i--) { const CMIUtilString strArg(argv[i]); const CMICmdArgValFile argFile; // Check for a filename if (argFile.IsFilePath(strArg) || CMICmdArgValString(true, false, true).IsStringArg(strArg)) { // Is this the command file for the '-s' or '--source' options? const CMIUtilString strPrevArg(argv[i - 1]); if (strPrevArg == "-s" || strPrevArg == "--source") { m_strCmdLineArgCommandFileNamePath = strArg; m_bHaveCommandFileNamePathOnCmdLine = true; i--; // skip '-s' on the next loop continue; } // Else, must be the executable bHaveExecutableFileNamePath = true; m_strCmdLineArgExecuteableFileNamePath = strArg; m_bHaveExecutableFileNamePathOnCmdLine = true; } // Report error if no command file was specified for the '-s' or // '--source' options else if (strArg == "-s" || strArg == "--source") { vwbExiting = true; const CMIUtilString errMsg = CMIUtilString::Format( MIRSRC(IDS_CMD_ARGS_ERR_VALIDATION_MISSING_INF), strArg.c_str()); errStatus.SetErrorString(errMsg.c_str()); break; } // This argument is also checked for in CMIDriverMgr::ParseArgs() else if (strArg == "--executable") // Used to specify that // there is executable // argument also on the // command line { // See fn description. bHaveExecutableLongOption = true; } else if (strArg == "--synchronous") { CMICmnLLDBDebugSessionInfo::Instance().GetDebugger().SetAsync(false); } } } if (bHaveExecutableFileNamePath && bHaveExecutableLongOption) { SetDriverDebuggingArgExecutable(); } return errStatus; } //++ //------------------------------------------------------------------------------------ // Details: A client can ask if *this driver is GDB/MI compatible. // Type: Overridden. // Args: None. // Return: True - GBD/MI compatible LLDB front end. // False - Not GBD/MI compatible LLDB front end. // Throws: None. //-- bool CMIDriver::GetDriverIsGDBMICompatibleDriver() const { return true; } //++ //------------------------------------------------------------------------------------ // Details: Start worker threads for the driver. // Type: Method. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::StartWorkerThreads() { bool bOk = MIstatus::success; // Grab the thread manager CMICmnThreadMgrStd &rThreadMgr = CMICmnThreadMgrStd::Instance(); // Start the event polling thread if (bOk && !rThreadMgr.ThreadStart(m_rLldbDebugger)) { const CMIUtilString errMsg = CMIUtilString::Format( MIRSRC(IDS_THREADMGR_ERR_THREAD_FAIL_CREATE), CMICmnThreadMgrStd::Instance().GetErrorDescription().c_str()); SetErrorDescription(errMsg); return MIstatus::failure; } return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Stop worker threads for the driver. // Type: Method. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::StopWorkerThreads() { CMICmnThreadMgrStd &rThreadMgr = CMICmnThreadMgrStd::Instance(); return rThreadMgr.ThreadAllTerminate(); } //++ //------------------------------------------------------------------------------------ // Details: Call this function puts *this driver to work. // This function is used by the application's main thread. // Type: Overridden. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::DoMainLoop() { if (!InitClientIDEToMIDriver()) // Init Eclipse IDE { SetErrorDescriptionn(MIRSRC(IDS_MI_INIT_ERR_CLIENT_USING_DRIVER)); return MIstatus::failure; } if (!StartWorkerThreads()) return MIstatus::failure; bool bOk = MIstatus::success; if (HaveExecutableFileNamePathOnCmdLine()) { if (!LocalDebugSessionStartupExecuteCommands()) { SetErrorDescription(MIRSRC(IDS_MI_INIT_ERR_LOCAL_DEBUG_SESSION)); bOk = MIstatus::failure; } } // App is not quitting currently m_bExitApp = false; // Handle source file if (m_bHaveCommandFileNamePathOnCmdLine) { const bool bAsyncMode = false; ExecuteCommandFile(bAsyncMode); } // While the app is active while (bOk && !m_bExitApp) { CMIUtilString errorText; const char *pCmd = m_rStdin.ReadLine(errorText); if (pCmd != nullptr) { CMIUtilString lineText(pCmd); if (!lineText.empty()) { // Check that the handler thread is alive (otherwise we stuck here) assert(CMICmnLLDBDebugger::Instance().ThreadIsActive()); { // Lock Mutex before processing commands so that we don't disturb an // event // being processed CMIUtilThreadLock lock( CMICmnLLDBDebugSessionInfo::Instance().GetSessionMutex()); bOk = InterpretCommand(lineText); } // Draw prompt if desired bOk = bOk && CMICmnStreamStdout::WritePrompt(); // Wait while the handler thread handles incoming events CMICmnLLDBDebugger::Instance().WaitForHandleEvent(); } } } // Signal that the application is shutting down DoAppQuit(); // Close and wait for the workers to stop StopWorkerThreads(); return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Set things in motion, set state etc that brings *this driver (and // the // application) to a tidy shutdown. // This function is used by the application's main thread. // Type: Method. // Args: None. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::DoAppQuit() { bool bYesQuit = true; // Shutdown stuff, ready app for exit { CMIUtilThreadLock lock(m_threadMutex); m_bDriverIsExiting = true; } return bYesQuit; } //++ //------------------------------------------------------------------------------------ // Details: *this driver passes text commands to a fall through driver is it // does not // understand them (the LLDB driver). // This function is used by the application's main thread. // Type: Method. // Args: vTextLine - (R) Text data representing a possible command. // vwbCmdYesValid - (W) True = Command valid, false = command not // handled. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::InterpretCommandFallThruDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid) { MIunused(vTextLine); MIunused(vwbCmdYesValid); // ToDo: Implement when less urgent work to be done or decide remove as not // required // bool bOk = MIstatus::success; // bool bCmdNotUnderstood = true; // if( bCmdNotUnderstood && GetEnableFallThru() ) //{ // CMIUtilString errMsg; // bOk = DoFallThruToAnotherDriver( vStdInBuffer, errMsg ); // if( !bOk ) // { // errMsg = errMsg.StripCREndOfLine(); // errMsg = errMsg.StripCRAll(); // const CMIDriverBase * pOtherDriver = GetDriverToFallThruTo(); // const char * pName = pOtherDriver->GetDriverName().c_str(); // const char * pId = pOtherDriver->GetDriverId().c_str(); // const CMIUtilString msg( CMIUtilString::Format( MIRSRC( // IDS_DRIVER_ERR_FALLTHRU_DRIVER_ERR ), pName, pId, errMsg.c_str() ) //); // m_pLog->WriteMsg( msg ); // } //} // // vwbCmdYesValid = bOk; // CMIUtilString strNot; // if( vwbCmdYesValid) // strNot = CMIUtilString::Format( "%s ", MIRSRC( IDS_WORD_NOT ) ); // const CMIUtilString msg( CMIUtilString::Format( MIRSRC( // IDS_FALLTHRU_DRIVER_CMD_RECEIVED ), vTextLine.c_str(), strNot.c_str() ) ); // m_pLog->WriteLog( msg ); return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve the name for *this driver. // Type: Overridden. // Args: None. // Return: CMIUtilString & - Driver name. // Throws: None. //-- const CMIUtilString &CMIDriver::GetDriverName() const { return GetName(); } //++ //------------------------------------------------------------------------------------ // Details: Get the unique ID for *this driver. // Type: Overridden. // Args: None. // Return: CMIUtilString & - Text description. // Throws: None. //-- const CMIUtilString &CMIDriver::GetDriverId() const { return GetId(); } //++ //------------------------------------------------------------------------------------ // Details: This function allows *this driver to call on another driver to // perform work // should this driver not be able to handle the client data input. // SetDriverToFallThruTo() specifies the fall through to driver. // Check the error message if the function returns a failure. // Type: Overridden. // Args: vCmd - (R) Command instruction to interpret. // vwErrMsg - (W) Status description on command failing. // Return: MIstatus::success - Command succeeded. // MIstatus::failure - Command failed. // Throws: None. //-- bool CMIDriver::DoFallThruToAnotherDriver(const CMIUtilString &vCmd, CMIUtilString &vwErrMsg) { bool bOk = MIstatus::success; CMIDriverBase *pOtherDriver = GetDriverToFallThruTo(); if (pOtherDriver == nullptr) return bOk; return pOtherDriver->DoFallThruToAnotherDriver(vCmd, vwErrMsg); } //++ //------------------------------------------------------------------------------------ // Details: *this driver provides a file stream to other drivers on which *this // driver // write's out to and they read as expected input. *this driver is // passing // through commands to the (child) pass through assigned driver. // Type: Overrdidden. // Args: None. // Return: FILE * - Pointer to stream. // Throws: None. //-- FILE *CMIDriver::GetStdin() const { // Note this fn is called on CMIDriverMgr register driver so stream has to be // available before *this driver has been initialized! Flaw? // This very likely to change later to a stream that the pass thru driver // will read and we write to give it 'input' return stdin; } //++ //------------------------------------------------------------------------------------ // Details: *this driver provides a file stream to other pass through assigned // drivers // so they know what to write to. // Type: Overidden. // Args: None. // Return: FILE * - Pointer to stream. // Throws: None. //-- FILE *CMIDriver::GetStdout() const { // Note this fn is called on CMIDriverMgr register driver so stream has to be // available before *this driver has been initialized! Flaw? // Do not want to pass through driver to write to stdout return NULL; } //++ //------------------------------------------------------------------------------------ // Details: *this driver provides a error file stream to other pass through // assigned drivers // so they know what to write to. // Type: Overidden. // Args: None. // Return: FILE * - Pointer to stream. // Throws: None. //-- FILE *CMIDriver::GetStderr() const { // Note this fn is called on CMIDriverMgr register driver so stream has to be // available before *this driver has been initialized! Flaw? // This very likely to change later to a stream that the pass thru driver // will write to and *this driver reads from to pass on the CMICmnLog object return stderr; } //++ //------------------------------------------------------------------------------------ // Details: Set a unique ID for *this driver. It cannot be empty. // Type: Overridden. // Args: vId - (R) Text description. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::SetId(const CMIUtilString &vId) { if (vId.empty()) { SetErrorDescriptionn(MIRSRC(IDS_DRIVER_ERR_ID_INVALID), GetName().c_str(), vId.c_str()); return MIstatus::failure; } m_strDriverId = vId; return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Get the unique ID for *this driver. // Type: Overridden. // Args: None. // Return: CMIUtilString & - Text description. // Throws: None. //-- const CMIUtilString &CMIDriver::GetId() const { return m_strDriverId; } //++ //------------------------------------------------------------------------------------ // Details: Interpret the text data and match against current commands to see if // there // is a match. If a match then the command is issued and actioned on. // The // text data if not understood by *this driver is past on to the Fall // Thru // driver. // This function is used by the application's main thread. // Type: Method. // Args: vTextLine - (R) Text data representing a possible command. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::InterpretCommand(const CMIUtilString &vTextLine) { const bool bNeedToRebroadcastStopEvent = m_rLldbDebugger.CheckIfNeedToRebroadcastStopEvent(); bool bCmdYesValid = false; bool bOk = InterpretCommandThisDriver(vTextLine, bCmdYesValid); if (bOk && !bCmdYesValid) bOk = InterpretCommandFallThruDriver(vTextLine, bCmdYesValid); if (bNeedToRebroadcastStopEvent) m_rLldbDebugger.RebroadcastStopEvent(); return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Helper function for CMIDriver::InterpretCommandThisDriver. // Convert a CLI command to MI command (just wrap any CLI command // into "-interpreter-exec command \"\""). // Type: Method. // Args: vTextLine - (R) Text data representing a possible command. // Return: CMIUtilString - The original MI command or converted CLI command. // MIstatus::failure - Functional failed. // Throws: None. //-- CMIUtilString CMIDriver::WrapCLICommandIntoMICommand(const CMIUtilString &vTextLine) const { // Tokens contain following digits static const CMIUtilString digits("0123456789"); // Consider an algorithm on the following example: // 001-file-exec-and-symbols "/path/to/file" // // 1. Skip a command token // For example: // 001-file-exec-and-symbols "/path/to/file" // 001target create "/path/to/file" // ^ -- command starts here (in both cases) // Also possible case when command not found: // 001 // ^ -- i.e. only tokens are present (or empty string at all) const size_t nCommandOffset = vTextLine.find_first_not_of(digits); // 2. Check if command is empty // For example: // 001-file-exec-and-symbols "/path/to/file" // 001target create "/path/to/file" // ^ -- command not empty (in both cases) // or: // 001 // ^ -- command wasn't found const bool bIsEmptyCommand = (nCommandOffset == CMIUtilString::npos); // 3. Check and exit if it isn't a CLI command // For example: // 001-file-exec-and-symbols "/path/to/file" // 001 // ^ -- it isn't CLI command (in both cases) // or: // 001target create "/path/to/file" // ^ -- it's CLI command const bool bIsCliCommand = !bIsEmptyCommand && (vTextLine.at(nCommandOffset) != '-'); if (!bIsCliCommand) return vTextLine; // 4. Wrap CLI command to make it MI-compatible // // 001target create "/path/to/file" // ^^^ -- token const std::string vToken(vTextLine.begin(), vTextLine.begin() + nCommandOffset); // 001target create "/path/to/file" // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- CLI command const CMIUtilString vCliCommand(std::string(vTextLine, nCommandOffset)); // 5. Escape special characters and embed the command in a string // Result: it looks like -- target create \"/path/to/file\". const std::string vShieldedCliCommand(vCliCommand.AddSlashes()); // 6. Turn the CLI command into an MI command, as in: // 001-interpreter-exec command "target create \"/path/to/file\"" // ^^^ -- token // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ -- wrapper // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- shielded // CLI command return CMIUtilString::Format("%s-interpreter-exec command \"%s\"", vToken.c_str(), vShieldedCliCommand.c_str()); } //++ //------------------------------------------------------------------------------------ // Details: Interpret the text data and match against current commands to see if // there // is a match. If a match then the command is issued and actioned on. // If a // command cannot be found to match then vwbCmdYesValid is set to false // and // nothing else is done here. // This function is used by the application's main thread. // Type: Method. // Args: vTextLine - (R) Text data representing a possible command. // vwbCmdYesValid - (W) True = Command valid, false = command not // handled. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::InterpretCommandThisDriver(const CMIUtilString &vTextLine, bool &vwbCmdYesValid) { // Convert any CLI commands into MI commands const CMIUtilString vMITextLine(WrapCLICommandIntoMICommand(vTextLine)); vwbCmdYesValid = false; bool bCmdNotInCmdFactor = false; SMICmdData cmdData; CMICmdMgr &rCmdMgr = CMICmdMgr::Instance(); if (!rCmdMgr.CmdInterpret(vMITextLine, vwbCmdYesValid, bCmdNotInCmdFactor, cmdData)) return MIstatus::failure; if (vwbCmdYesValid) { // For debugging only // m_pLog->WriteLog( cmdData.strMiCmdAll.c_str() ); return ExecuteCommand(cmdData); } // Check for escape character, may be cursor control characters // This code is not necessary for application operation, just want to keep // tabs on what // has been given to the driver to try and interpret. if (vMITextLine.at(0) == 27) { CMIUtilString logInput(MIRSRC(IDS_STDIN_INPUT_CTRL_CHARS)); for (MIuint i = 0; i < vMITextLine.length(); i++) { logInput += CMIUtilString::Format("%d ", vMITextLine.at(i)); } m_pLog->WriteLog(logInput); return MIstatus::success; } // Write to the Log that a 'command' was not valid. // Report back to the MI client via MI result record. CMIUtilString strNotInCmdFactory; if (bCmdNotInCmdFactor) strNotInCmdFactory = CMIUtilString::Format( MIRSRC(IDS_DRIVER_CMD_NOT_IN_FACTORY), cmdData.strMiCmd.c_str()); const CMIUtilString strNot( CMIUtilString::Format("%s ", MIRSRC(IDS_WORD_NOT))); const CMIUtilString msg(CMIUtilString::Format( MIRSRC(IDS_DRIVER_CMD_RECEIVED), vMITextLine.c_str(), strNot.c_str(), strNotInCmdFactory.c_str())); const CMICmnMIValueConst vconst = CMICmnMIValueConst(msg); const CMICmnMIValueResult valueResult("msg", vconst); const CMICmnMIResultRecord miResultRecord( cmdData.strMiCmdToken, CMICmnMIResultRecord::eResultClass_Error, valueResult); const bool bOk = m_rStdOut.WriteMIResponse(miResultRecord.GetString()); // Proceed to wait for or execute next command return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Having previously had the potential command validated and found // valid now // get the command executed. // This function is used by the application's main thread. // Type: Method. // Args: vCmdData - (RW) Command meta data. // Return: MIstatus::success - Functional succeeded. // MIstatus::failure - Functional failed. // Throws: None. //-- bool CMIDriver::ExecuteCommand(const SMICmdData &vCmdData) { CMICmdMgr &rCmdMgr = CMICmdMgr::Instance(); return rCmdMgr.CmdExecute(vCmdData); } //++ //------------------------------------------------------------------------------------ // Details: Set the MI Driver's exit application flag. The application checks // this flag // after every stdin line is read so the exit may not be instantaneous. // If vbForceExit is false the MI Driver queries its state and // determines if is // should exit or continue operating depending on that running state. // This is related to the running state of the MI driver. // Type: Overridden. // Args: None. // Return: None. // Throws: None. //-- void CMIDriver::SetExitApplicationFlag(const bool vbForceExit) { if (vbForceExit) { CMIUtilThreadLock lock(m_threadMutex); m_bExitApp = true; return; } // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM // Did we receive a SIGINT from the client during a running debug program, if // so then SIGINT is not to be taken as meaning kill the MI driver application // but halt the inferior program being debugged instead if (m_eCurrentDriverState == eDriverState_RunningDebugging) { InterpretCommand("-exec-interrupt"); return; } m_bExitApp = true; } //++ //------------------------------------------------------------------------------------ // Details: Get the MI Driver's exit exit application flag. // This is related to the running state of the MI driver. // Type: Method. // Args: None. // Return: bool - True = MI Driver is shutting down, false = MI driver is // running. // Throws: None. //-- bool CMIDriver::GetExitApplicationFlag() const { return m_bExitApp; } //++ //------------------------------------------------------------------------------------ // Details: Get the current running state of the MI Driver. // Type: Method. // Args: None. // Return: DriverState_e - The current running state of the application. // Throws: None. //-- CMIDriver::DriverState_e CMIDriver::GetCurrentDriverState() const { return m_eCurrentDriverState; } //++ //------------------------------------------------------------------------------------ // Details: Set the current running state of the MI Driver to running and // currently not in // a debug session. // Type: Method. // Return: MIstatus::success - Functionality succeeded. // MIstatus::failure - Functionality failed. // Return: DriverState_e - The current running state of the application. // Throws: None. //-- bool CMIDriver::SetDriverStateRunningNotDebugging() { // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM if (m_eCurrentDriverState == eDriverState_RunningNotDebugging) return MIstatus::success; // Driver cannot be in the following states to set // eDriverState_RunningNotDebugging switch (m_eCurrentDriverState) { case eDriverState_NotRunning: case eDriverState_Initialising: case eDriverState_ShuttingDown: { SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR)); return MIstatus::failure; } case eDriverState_RunningDebugging: case eDriverState_RunningNotDebugging: break; case eDriverState_count: SetErrorDescription( CMIUtilString::Format(MIRSRC(IDS_CODE_ERR_INVALID_ENUMERATION_VALUE), "SetDriverStateRunningNotDebugging()")); return MIstatus::failure; } // Driver must be in this state to set eDriverState_RunningNotDebugging if (m_eCurrentDriverState != eDriverState_RunningDebugging) { SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR)); return MIstatus::failure; } m_eCurrentDriverState = eDriverState_RunningNotDebugging; return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Set the current running state of the MI Driver to running and // currently not in // a debug session. The driver's state must in the state running and in // a // debug session to set this new state. // Type: Method. // Return: MIstatus::success - Functionality succeeded. // MIstatus::failure - Functionality failed. // Return: DriverState_e - The current running state of the application. // Throws: None. //-- bool CMIDriver::SetDriverStateRunningDebugging() { // CODETAG_DEBUG_SESSION_RUNNING_PROG_RECEIVED_SIGINT_PAUSE_PROGRAM if (m_eCurrentDriverState == eDriverState_RunningDebugging) return MIstatus::success; // Driver cannot be in the following states to set // eDriverState_RunningDebugging switch (m_eCurrentDriverState) { case eDriverState_NotRunning: case eDriverState_Initialising: case eDriverState_ShuttingDown: { SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR)); return MIstatus::failure; } case eDriverState_RunningDebugging: case eDriverState_RunningNotDebugging: break; case eDriverState_count: SetErrorDescription( CMIUtilString::Format(MIRSRC(IDS_CODE_ERR_INVALID_ENUMERATION_VALUE), "SetDriverStateRunningDebugging()")); return MIstatus::failure; } // Driver must be in this state to set eDriverState_RunningDebugging if (m_eCurrentDriverState != eDriverState_RunningNotDebugging) { SetErrorDescription(MIRSRC(IDS_DRIVER_ERR_DRIVER_STATE_ERROR)); return MIstatus::failure; } m_eCurrentDriverState = eDriverState_RunningDebugging; return MIstatus::success; } //++ //------------------------------------------------------------------------------------ // Details: Prepare the client IDE so it will start working/communicating with // *this MI // driver. // Type: Method. // Args: None. // Return: MIstatus::success - Functionality succeeded. // MIstatus::failure - Functionality failed. // Throws: None. //-- bool CMIDriver::InitClientIDEToMIDriver() const { // Put other IDE init functions here return InitClientIDEEclipse(); } //++ //------------------------------------------------------------------------------------ // Details: The IDE Eclipse when debugging locally expects "(gdb)\n" character // sequence otherwise it refuses to communicate and times out. This // should be // sent to Eclipse before anything else. // Type: Method. // Args: None. // Return: MIstatus::success - Functionality succeeded. // MIstatus::failure - Functionality failed. // Throws: None. //-- bool CMIDriver::InitClientIDEEclipse() const { return CMICmnStreamStdout::WritePrompt(); } //++ //------------------------------------------------------------------------------------ // Details: Ask *this driver whether it found an executable in the MI Driver's // list of // arguments which to open and debug. If so instigate commands to set // up a debug // session for that executable. // Type: Method. // Args: None. // Return: bool - True = True = Yes executable given as one of the parameters // to the MI // Driver. // False = not found. // Throws: None. //-- bool CMIDriver::HaveExecutableFileNamePathOnCmdLine() const { return m_bHaveExecutableFileNamePathOnCmdLine; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve from *this driver executable file name path to start a // debug session // with (if present see HaveExecutableFileNamePathOnCmdLine()). // Type: Method. // Args: None. // Return: CMIUtilString & - Executeable file name path or empty string. // Throws: None. //-- const CMIUtilString &CMIDriver::GetExecutableFileNamePathOnCmdLine() const { return m_strCmdLineArgExecuteableFileNamePath; } //++ //------------------------------------------------------------------------------------ // Details: Execute commands (by injecting them into the stdin line queue // container) and // other code to set up the MI Driver such that is can take the // executable // argument passed on the command and create a debug session for it. // Type: Method. // Args: None. // Return: MIstatus::success - Functionality succeeded. // MIstatus::failure - Functionality failed. // Throws: None. //-- bool CMIDriver::LocalDebugSessionStartupExecuteCommands() { const CMIUtilString strCmd(CMIUtilString::Format( "-file-exec-and-symbols \"%s\"", m_strCmdLineArgExecuteableFileNamePath.AddSlashes().c_str())); bool bOk = CMICmnStreamStdout::TextToStdout(strCmd); bOk = bOk && InterpretCommand(strCmd); bOk = bOk && CMICmnStreamStdout::WritePrompt(); return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Set the MI Driver into "its debugging an executable passed as an // argument" // mode as against running via a client like Eclipse. // Type: Method. // Args: None. // Return: None. // Throws: None. //-- void CMIDriver::SetDriverDebuggingArgExecutable() { m_bDriverDebuggingArgExecutable = true; } //++ //------------------------------------------------------------------------------------ // Details: Retrieve the MI Driver state indicating if it is operating in "its // debugging // an executable passed as an argument" mode as against running via a // client // like Eclipse. // Type: Method. // Args: None. // Return: None. // Throws: None. //-- bool CMIDriver::IsDriverDebuggingArgExecutable() const { return m_bDriverDebuggingArgExecutable; } //++ //------------------------------------------------------------------------------------ // Details: Execute commands from command source file in specified mode, and // set exit-flag if needed. // Type: Method. // Args: vbAsyncMode - (R) True = execute commands in asynchronous // mode, false = otherwise. // Return: MIstatus::success - Function succeeded. // MIstatus::failure - Function failed. // Throws: None. //-- bool CMIDriver::ExecuteCommandFile(const bool vbAsyncMode) { std::ifstream ifsStartScript(m_strCmdLineArgCommandFileNamePath.c_str()); if (!ifsStartScript.is_open()) { const CMIUtilString errMsg( CMIUtilString::Format(MIRSRC(IDS_UTIL_FILE_ERR_OPENING_FILE_UNKNOWN), m_strCmdLineArgCommandFileNamePath.c_str())); SetErrorDescription(errMsg.c_str()); const bool bForceExit = true; SetExitApplicationFlag(bForceExit); return MIstatus::failure; } // Switch lldb to synchronous mode CMICmnLLDBDebugSessionInfo &rSessionInfo( CMICmnLLDBDebugSessionInfo::Instance()); const bool bAsyncSetting = rSessionInfo.GetDebugger().GetAsync(); rSessionInfo.GetDebugger().SetAsync(vbAsyncMode); // Execute commands from file bool bOk = MIstatus::success; CMIUtilString strCommand; while (!m_bExitApp && std::getline(ifsStartScript, strCommand)) { // Print command bOk = CMICmnStreamStdout::TextToStdout(strCommand); // Skip if it's a comment or empty line if (strCommand.empty() || strCommand[0] == '#') continue; // Execute if no error if (bOk) { CMIUtilThreadLock lock(rSessionInfo.GetSessionMutex()); bOk = InterpretCommand(strCommand); } // Draw the prompt after command will be executed (if enabled) bOk = bOk && CMICmnStreamStdout::WritePrompt(); // Exit if there is an error if (!bOk) { const bool bForceExit = true; SetExitApplicationFlag(bForceExit); break; } // Wait while the handler thread handles incoming events CMICmnLLDBDebugger::Instance().WaitForHandleEvent(); } // Switch lldb back to initial mode rSessionInfo.GetDebugger().SetAsync(bAsyncSetting); return bOk; } //++ //------------------------------------------------------------------------------------ // Details: Gets called when lldb-mi gets a signal. Stops the process if it was // SIGINT. // // Type: Method. // Args: signal that was delivered // Return: None. // Throws: None. //-- void CMIDriver::DeliverSignal(int signal) { if (signal == SIGINT && (m_eCurrentDriverState == eDriverState_RunningDebugging)) InterpretCommand("-exec-interrupt"); }