1 //===-- ScriptInterpreter.cpp ---------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Interpreter/ScriptInterpreter.h"
10 #include "lldb/Core/Debugger.h"
11 #include "lldb/Host/ConnectionFileDescriptor.h"
12 #include "lldb/Host/Pipe.h"
13 #include "lldb/Host/PseudoTerminal.h"
14 #include "lldb/Interpreter/CommandReturnObject.h"
15 #include "lldb/Utility/Status.h"
16 #include "lldb/Utility/Stream.h"
17 #include "lldb/Utility/StringList.h"
18 #if defined(_WIN32)
19 #include "lldb/Host/windows/ConnectionGenericFileWindows.h"
20 #endif
21 #include <cstdio>
22 #include <cstdlib>
23 #include <memory>
24 #include <string>
25 
26 using namespace lldb;
27 using namespace lldb_private;
28 
29 ScriptInterpreter::ScriptInterpreter(
30     Debugger &debugger, lldb::ScriptLanguage script_lang,
31     lldb::ScriptedProcessInterfaceUP scripted_process_interface_up)
32     : m_debugger(debugger), m_script_lang(script_lang),
33       m_scripted_process_interface_up(
34           std::move(scripted_process_interface_up)) {}
35 
36 void ScriptInterpreter::CollectDataForBreakpointCommandCallback(
37     std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
38     CommandReturnObject &result) {
39   result.SetStatus(eReturnStatusFailed);
40   result.AppendError(
41       "This script interpreter does not support breakpoint callbacks.");
42 }
43 
44 void ScriptInterpreter::CollectDataForWatchpointCommandCallback(
45     WatchpointOptions *bp_options, CommandReturnObject &result) {
46   result.SetStatus(eReturnStatusFailed);
47   result.AppendError(
48       "This script interpreter does not support watchpoint callbacks.");
49 }
50 
51 bool ScriptInterpreter::LoadScriptingModule(const char *filename,
52                                             bool init_session,
53                                             lldb_private::Status &error,
54                                             StructuredData::ObjectSP *module_sp,
55                                             FileSpec extra_search_dir) {
56   error.SetErrorString(
57       "This script interpreter does not support importing modules.");
58   return false;
59 }
60 
61 std::string ScriptInterpreter::LanguageToString(lldb::ScriptLanguage language) {
62   switch (language) {
63   case eScriptLanguageNone:
64     return "None";
65   case eScriptLanguagePython:
66     return "Python";
67   case eScriptLanguageLua:
68     return "Lua";
69   case eScriptLanguageUnknown:
70     return "Unknown";
71   }
72   llvm_unreachable("Unhandled ScriptInterpreter!");
73 }
74 
75 lldb::DataExtractorSP
76 ScriptInterpreter::GetDataExtractorFromSBData(const lldb::SBData &data) const {
77   return data.m_opaque_sp;
78 }
79 
80 Status
81 ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const {
82   if (error.m_opaque_up)
83     return *error.m_opaque_up.get();
84 
85   return Status();
86 }
87 
88 lldb::ScriptLanguage
89 ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
90   if (language.equals_lower(LanguageToString(eScriptLanguageNone)))
91     return eScriptLanguageNone;
92   if (language.equals_lower(LanguageToString(eScriptLanguagePython)))
93     return eScriptLanguagePython;
94   if (language.equals_lower(LanguageToString(eScriptLanguageLua)))
95     return eScriptLanguageLua;
96   return eScriptLanguageUnknown;
97 }
98 
99 Status ScriptInterpreter::SetBreakpointCommandCallback(
100     std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
101     const char *callback_text) {
102   Status return_error;
103   for (BreakpointOptions &bp_options : bp_options_vec) {
104     return_error = SetBreakpointCommandCallback(bp_options, callback_text);
105     if (return_error.Success())
106       break;
107   }
108   return return_error;
109 }
110 
111 Status ScriptInterpreter::SetBreakpointCommandCallbackFunction(
112     std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
113     const char *function_name, StructuredData::ObjectSP extra_args_sp) {
114   Status error;
115   for (BreakpointOptions &bp_options : bp_options_vec) {
116     error = SetBreakpointCommandCallbackFunction(bp_options, function_name,
117                                                  extra_args_sp);
118     if (!error.Success())
119       return error;
120   }
121   return error;
122 }
123 
124 std::unique_ptr<ScriptInterpreterLocker>
125 ScriptInterpreter::AcquireInterpreterLock() {
126   return std::make_unique<ScriptInterpreterLocker>();
127 }
128 
129 static void ReadThreadBytesReceived(void *baton, const void *src,
130                                     size_t src_len) {
131   if (src && src_len) {
132     Stream *strm = (Stream *)baton;
133     strm->Write(src, src_len);
134     strm->Flush();
135   }
136 }
137 
138 llvm::Expected<std::unique_ptr<ScriptInterpreterIORedirect>>
139 ScriptInterpreterIORedirect::Create(bool enable_io, Debugger &debugger,
140                                     CommandReturnObject *result) {
141   if (enable_io)
142     return std::unique_ptr<ScriptInterpreterIORedirect>(
143         new ScriptInterpreterIORedirect(debugger, result));
144 
145   auto nullin = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL),
146                                             File::eOpenOptionRead);
147   if (!nullin)
148     return nullin.takeError();
149 
150   auto nullout = FileSystem::Instance().Open(FileSpec(FileSystem::DEV_NULL),
151                                              File::eOpenOptionWrite);
152   if (!nullout)
153     return nullin.takeError();
154 
155   return std::unique_ptr<ScriptInterpreterIORedirect>(
156       new ScriptInterpreterIORedirect(std::move(*nullin), std::move(*nullout)));
157 }
158 
159 ScriptInterpreterIORedirect::ScriptInterpreterIORedirect(
160     std::unique_ptr<File> input, std::unique_ptr<File> output)
161     : m_input_file_sp(std::move(input)),
162       m_output_file_sp(std::make_shared<StreamFile>(std::move(output))),
163       m_error_file_sp(m_output_file_sp),
164       m_communication("lldb.ScriptInterpreterIORedirect.comm"),
165       m_disconnect(false) {}
166 
167 ScriptInterpreterIORedirect::ScriptInterpreterIORedirect(
168     Debugger &debugger, CommandReturnObject *result)
169     : m_communication("lldb.ScriptInterpreterIORedirect.comm"),
170       m_disconnect(false) {
171 
172   if (result) {
173     m_input_file_sp = debugger.GetInputFileSP();
174 
175     Pipe pipe;
176     Status pipe_result = pipe.CreateNew(false);
177 #if defined(_WIN32)
178     lldb::file_t read_file = pipe.GetReadNativeHandle();
179     pipe.ReleaseReadFileDescriptor();
180     std::unique_ptr<ConnectionGenericFile> conn_up =
181         std::make_unique<ConnectionGenericFile>(read_file, true);
182 #else
183     std::unique_ptr<ConnectionFileDescriptor> conn_up =
184         std::make_unique<ConnectionFileDescriptor>(
185             pipe.ReleaseReadFileDescriptor(), true);
186 #endif
187 
188     if (conn_up->IsConnected()) {
189       m_communication.SetConnection(std::move(conn_up));
190       m_communication.SetReadThreadBytesReceivedCallback(
191           ReadThreadBytesReceived, &result->GetOutputStream());
192       m_communication.StartReadThread();
193       m_disconnect = true;
194 
195       FILE *outfile_handle = fdopen(pipe.ReleaseWriteFileDescriptor(), "w");
196       m_output_file_sp = std::make_shared<StreamFile>(outfile_handle, true);
197       m_error_file_sp = m_output_file_sp;
198       if (outfile_handle)
199         ::setbuf(outfile_handle, nullptr);
200 
201       result->SetImmediateOutputFile(debugger.GetOutputStream().GetFileSP());
202       result->SetImmediateErrorFile(debugger.GetErrorStream().GetFileSP());
203     }
204   }
205 
206   if (!m_input_file_sp || !m_output_file_sp || !m_error_file_sp)
207     debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_file_sp, m_output_file_sp,
208                                              m_error_file_sp);
209 }
210 
211 void ScriptInterpreterIORedirect::Flush() {
212   if (m_output_file_sp)
213     m_output_file_sp->Flush();
214   if (m_error_file_sp)
215     m_error_file_sp->Flush();
216 }
217 
218 ScriptInterpreterIORedirect::~ScriptInterpreterIORedirect() {
219   if (!m_disconnect)
220     return;
221 
222   assert(m_output_file_sp);
223   assert(m_error_file_sp);
224   assert(m_output_file_sp == m_error_file_sp);
225 
226   // Close the write end of the pipe since we are done with our one line
227   // script. This should cause the read thread that output_comm is using to
228   // exit.
229   m_output_file_sp->GetFile().Close();
230   // The close above should cause this thread to exit when it gets to the end
231   // of file, so let it get all its data.
232   m_communication.JoinReadThread();
233   // Now we can close the read end of the pipe.
234   m_communication.Disconnect();
235 }
236