HAL  v4.5.0-83-g30c8f0afc
The Hardware Analyzer - a comprehensive reverse engineering and manipulation framework for gate-level netlists.
simulation_process.cpp
Go to the documentation of this file.
3 
6 
7 #include <QDir>
8 #include <QFileInfo>
9 #include <QProcess>
10 #include <QStringList>
11 #include <QCoreApplication>
12 #include <QThread>
13 #include <QUuid>
14 #include <vector>
15 
16 namespace hal
17 {
18 
19  QString SimulationProcessLog::sLogFilename = "engine_log.html";
20 
22  : QObject(parent)
23  {;}
24 
26  : QObject(parent), mStream(nullptr)
27  {
28  QString filename(QDir(workdir).absoluteFilePath(sLogFilename));
29  mFile.setFileName(filename);
30  if (mFile.open(QIODevice::WriteOnly))
31  mStream = new QTextStream(&mFile);
32  }
33 
35  {
36  if (mStream)
37  {
38  mStream->flush();
39  delete mStream;
40  }
41  if (mFile.isOpen())
42  mFile.close();
43  }
44 
46  {
47  if (!logReceiver) return;
49  }
50 
52  {
53  if (mStream)
54  mStream->flush();
55  }
56 
58  {
59  if (mStream)
60  (*mStream) << txt;
61  Q_EMIT processOutput(txt);
62  }
63 
65  : mEngine(engine), mLineIndex(0), mNumberLines(0), mSaleaeDirectoryFilename(controller->get_saleae_directory_filename()),
66  mProcessLog(nullptr), mProcess(nullptr)
67  {
70  }
71 
73  {
74  if (mProcessLog) mProcessLog->deleteLater();
75  if (mProcess) mProcess->deleteLater();
76  }
77 
78  void SimulationProcess::abortOnError()
79  {
80  mEngine->failed();
81  Q_EMIT processFinished(false);
82  }
83 
84  void SimulationProcess::openHtmlLog()
85  {
86  }
87 
89  {
90  if (mEngine->get_engine_property("ssh_server").empty())
91  runLocal();
92  else
93  runRemote();
94  }
95 
96  void SimulationProcess::runRemote()
97  {
98  SaleaeDirectory sd(mSaleaeDirectoryFilename);
99  QFileInfo finfo(QString::fromStdString(mSaleaeDirectoryFilename));
100  QString saleaeFilesToCopy = finfo.fileName();
101  for (int inx = 0; inx < sd.get_next_available_index(); inx++)
102  {
103  saleaeFilesToCopy += QString(" digital_%1.bin").arg(inx);
104  }
105 
106  QDir localDir(QString::fromStdString(mEngine->get_working_directory()));
107  QString remoteDir = "hal_simul_" + QUuid::createUuid().toString(QUuid::Id128);
108  QString shellScriptName(localDir.absoluteFilePath("remote.sh"));
109  QString hostname = QString::fromStdString(mEngine->get_engine_property("ssh_server"));
110  QString resultFile("waveform.vcd");
111  QFile ff(shellScriptName);
112  if (!ff.open(QIODevice::WriteOnly))
113  {
114  log_warning("simulation_plugin", "cannot open remote script '{}' for writing", shellScriptName.toStdString());
115  return abortOnError();
116  }
117  ff.write("set -x\n"); // echo commands
118  ff.write("HOSTNAME=" + hostname.toUtf8() + "\n");
119  ff.write("LOCALDIR=" + localDir.absolutePath().toUtf8() + "\n");
120  ff.write(QString("if ssh ${HOSTNAME} mkdir -p /tmp/%1; then\n").arg(remoteDir).toUtf8());
121  ff.write(QString(" REMOTEDIR=/tmp/%1\n").arg(remoteDir).toUtf8());
122  ff.write("else\n");
123  ff.write(QString(" ssh ${HOSTNAME} mkdir %1\n").arg(remoteDir).toUtf8());
124  ff.write(QString(" REMOTEDIR=%1\n").arg(remoteDir).toUtf8());
125  ff.write("fi\n");
126  ff.write("set -e\n"); // bailout on error
127  ff.write("tar -czf - ${LOCALDIR} | ssh ${HOSTNAME} \"cd ${REMOTEDIR} ; tar -xzf -\"\n");
128  ff.write("ssh ${HOSTNAME} \"mkdir -p ${REMOTEDIR}${LOCALDIR}/saleae\"\n");
129  ff.write(QString("cd %1; tar -czf - %2 | ssh ${HOSTNAME} \"cd ${REMOTEDIR}${LOCALDIR}/saleae ; tar -xzf -\"\n").arg(finfo.absolutePath()).arg(saleaeFilesToCopy).toUtf8());
130  mNumberLines = mEngine->numberCommandLines();
131  for (int i = 0; i < mNumberLines; i++)
132  {
133  QString remoteCmd("ssh ${HOSTNAME} \"cd ${REMOTEDIR}${LOCALDIR} ;");
134  for (const std::string s : mEngine->commandLine(i))
135  {
136  remoteCmd += " " + QString::fromStdString(s);
137  }
138  remoteCmd += QString("\"\n");
139  ff.write(remoteCmd.toUtf8());
140  }
141  ff.write(QString("ssh %1 \"cd ${REMOTEDIR}%2 ; gzip -c %3\" | gzip -dc > %4\n").arg(hostname).arg(localDir.absolutePath()).arg(resultFile).arg(localDir.absoluteFilePath(resultFile)).toUtf8());
142  // ff.write(QString("ssh %1 \"rm -rf ${REMOTEDIR}\"\n").arg(hostname).toUtf8());
143  ff.close();
145 
146  QStringList args;
147  args << shellScriptName;
148 
149  if (!runProcess("bash", args))
150  return abortOnError();
151 
152  if (!mEngine->finalize())
153  return abortOnError();
154 
155  Q_EMIT processFinished(true);
156  }
157 
158  void SimulationProcess::runLocal()
159  {
160  mNumberLines = mEngine->numberCommandLines();
161  while (mLineIndex < mNumberLines)
162  {
163  QStringList args;
164  QString prog;
165 
166  bool first = true;
167  for (const std::string& s : mEngine->commandLine(mLineIndex))
168  {
170  if (first)
171  prog = qs;
172  else
173  args << qs;
174  first = false;
175  }
176  if (prog.isEmpty())
177  return abortOnError();
178 
179  if (!runProcess(prog, args))
180  return abortOnError();
181 
182  ++mLineIndex;
183 
184  if (mProcess)
185  {
186  mProcess->deleteLater();
187  mProcess = nullptr;
188  }
189  }
190 
191  if (!mEngine->finalize())
192  return abortOnError();
193 
194  Q_EMIT processFinished(true);
195  }
196 
197  QByteArray SimulationProcess::toHtml(const QByteArray& txt)
198  {
199  QByteArray retval;
200  for (char cc : txt)
201  {
202  switch (cc)
203  {
204  case '<':
205  retval += "&lt;";
206  break;
207  case '>':
208  retval += "&gt;";
209  break;
210  case '&':
211  retval += "&amp;";
212  break;
213  default:
214  retval += cc;
215  break;
216  }
217  }
218  return retval;
219  }
220 
221  bool SimulationProcess::runProcess(const QString& prog, const QStringList& args)
222  {
223  QString logCommand = "<html><body bgcolor=\"#000000\">\n<h1><font color=\"#ffffff\">" + prog;
224  for (const QString& arg : args)
225  logCommand += " " + arg;
226  logCommand += "</font></h1>\n";
227  mProcess = new QProcess;
228 
230  connect(mProcess,&QProcess::readyReadStandardError,this,&SimulationProcess::handleReadyReadStandardError);
231  connect(mProcess,&QProcess::readyReadStandardOutput,this,&SimulationProcess::handleReadyReadStandardOutput);
232  connect(mProcess,QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
233  [this](int exitCode, QProcess::ExitStatus exitStatus){ this->handleFinished(exitCode,exitStatus); });
234 
235  (*mProcessLog) << logCommand;
236  mProcessLog->flush();
237 
238  mProcess->start(prog, args);
239 
240  if (!mProcess->waitForStarted())
241  {
242  log_warning("simulation_plugin", "Cannot start process '{}' with args '{}'",
243  prog.toStdString(), args.join(' ').toStdString());
244  if (prog == "verilator")
245  log_warning("simulation_plugin", "You might want to check whether verilator has been installed on system");
246  return false;
247  }
248 
249  if (qApp)
250  {
251  // entering event loop
252  if (exec())
253  return false; // event loop ended with non-zero value
254  }
255  else
256  {
257  int msecs = 30000;
258  QString timeoutAfterSec = QString::fromStdString(mEngine->get_engine_property("timeout_after_sec"));
259  if (!timeoutAfterSec.isEmpty())
260  {
261  bool ok;
262  msecs = timeoutAfterSec.toInt(&ok) * 1000;
263  if (!ok || msecs <= 0) msecs = -1;
264  }
265  log_warning("simulation_plugin", "No QApplication running, event loop not entered, will poll for process to finish.");
266  if (!mProcess->waitForFinished(msecs))
267  {
268  (*mProcessLog) << "<p><font color=\"#ff4040\">Process timeout after 30 sec.</font></p>\n";
269  mProcessLog->flush();
270  return false;
271  }
272  handleReadyReadStandardError();
273  handleReadyReadStandardOutput();
274  }
275 
276  return true;
277  }
278 
279  void SimulationProcess::handleReadyReadStandardError()
280  {
281  QString errTxt;
282  if (!mProcess) return;
283 
284  for (const QByteArray& line : mProcess->readAllStandardError().split('\n'))
285  {
286  if (line.startsWith("%Error"))
287  errTxt += "<p><font color=\"#ff4040\">";
288  else if (line.startsWith("%Warning"))
289  errTxt += "<p><font color=\"#ffe050\">";
290  else
291  errTxt += "<p><font color=\"#ffffff\">";
292 
293  errTxt += QString::fromUtf8(toHtml(line)) + "</font></p>\n";
294  }
295 
296  (*mProcessLog) << errTxt;
297  }
298 
299  void SimulationProcess::handleReadyReadStandardOutput()
300  {
301  QString outTxt;
302  if (!mProcess) return;
303 
304  for (const QByteArray& line : mProcess->readAllStandardOutput().split('\n'))
305  {
306  outTxt += "<p><font color=\"#e0e0e0\">" + QString::fromUtf8(toHtml(line)) + "</font></p>\n";
307  }
308 
309  (*mProcessLog) << outTxt;
310  }
311 
312  void SimulationProcess::handleFinished(int exitCode, QProcess::ExitStatus exitStatus)
313  {
314  mProcess->waitForFinished();
315  handleReadyReadStandardError();
316  handleReadyReadStandardOutput();
317  QString endTxt = QString("<p>exit code %1</p></body></html>\n").arg(exitCode);
318  (*mProcessLog) << endTxt;
319  mProcessLog->flush();
320 
321  if (exitCode != 0) exit(exitCode);
322 
323  if (exitStatus != QProcess::NormalExit) exit(-99);
324 
325  exit(0);
326  }
327 } // namespace hal
The SaleaeDirectory class provides the API for the SALEAE directory file. The SALEAE directory file w...
std::string get_working_directory() const
virtual std::string get_engine_property(const std::string &key)
virtual int numberCommandLines() const =0
virtual std::vector< std::string > commandLine(int lineIndex) const =0
SimulationLogReceiver(QObject *parent=nullptr)
virtual void handleLog(const QString &txt)
SimulationProcess(NetlistSimulatorController *controller, SimulationEngineScripted *engine)
void processFinished(bool success)
void setLogReceiver(SimulationLogReceiver *logReceiver)
void operator<<(const QString &txt)
void processOutput(QString txt)
SimulationProcessLog(const QString &workdir, QObject *parent=nullptr)
#define log_warning(channel,...)
Definition: log.h:76
Definition: defines.h:45
QList< QByteArray > split(char sep) const const
virtual bool open(QIODevice::OpenMode mode) override
void setFileName(const QString &name)
virtual void close() override
bool isOpen() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void deleteLater()
void finished(int exitCode)
QByteArray readAllStandardError()
QByteArray readAllStandardOutput()
void readyReadStandardError()
void readyReadStandardOutput()
void setWorkingDirectory(const QString &dir)
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromStdString(const std::string &str)
QString fromUtf8(const char *str, int size)
bool isEmpty() const const
int toInt(bool *ok, int base) const const
std::string toStdString() const const
QByteArray toUtf8() const const
QueuedConnection
void flush()
int exec()
void exit(int returnCode)
QUuid createUuid()
QString toString() const const