Skip to content

Commit 59237bb

Browse files
committed
[lldb] Use a time-based timeout in IRInterpreter
At the moment the IRInterpreter will stop interpreting an expression after a hardcoded 4096 instructions. After it reaches the limit it will stop interpreting and leave the process in whatever state it was when the timeout was reached. This patch changes the instruction limit to a timeout and uses the user-specified expression timeout value for this. The main motivation is to allow users on targets where we can't use the JIT to run more complicated expressions if they really want to (which they can do now by just increasing the timeout). The time-based approach also seems much more meaningful than the arbitrary (and very low) instruction limit. 4096 instructions can be interpreted in a few microseconds on some setups but might take much longer if we have a slow connection to the target. I don't think any user actually cares about the number of instructions that are executed but only about the time they are willing to wait for a result. Based off an original patch by Raphael Isemann. Differential revision: https://reviews.llvm.org/D102762
1 parent e93a813 commit 59237bb

File tree

5 files changed

+68
-16
lines changed

5 files changed

+68
-16
lines changed

lldb/include/lldb/Expression/IRInterpreter.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "lldb/Utility/ConstString.h"
1313
#include "lldb/Utility/Stream.h"
14+
#include "lldb/Utility/Timeout.h"
1415
#include "lldb/lldb-public.h"
1516
#include "llvm/ADT/ArrayRef.h"
1617
#include "llvm/Pass.h"
@@ -44,7 +45,8 @@ class IRInterpreter {
4445
lldb_private::Status &error,
4546
lldb::addr_t stack_frame_bottom,
4647
lldb::addr_t stack_frame_top,
47-
lldb_private::ExecutionContext &exe_ctx);
48+
lldb_private::ExecutionContext &exe_ctx,
49+
lldb_private::Timeout<std::micro> timeout);
4850

4951
private:
5052
static bool supportsFunction(llvm::Function &llvm_function,

lldb/include/lldb/Target/Target.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,16 @@ class EvaluateExpressionOptions {
346346
m_use_dynamic = dynamic;
347347
}
348348

349-
const Timeout<std::micro> &GetTimeout() const { return m_timeout; }
349+
const Timeout<std::micro> &GetTimeout() const {
350+
assert(m_timeout && m_timeout->count() > 0);
351+
return m_timeout;
352+
}
350353

351-
void SetTimeout(const Timeout<std::micro> &timeout) { m_timeout = timeout; }
354+
void SetTimeout(const Timeout<std::micro> &timeout) {
355+
// Disallow setting a non-zero timeout.
356+
if (timeout && timeout->count() > 0)
357+
m_timeout = timeout;
358+
}
352359

353360
const Timeout<std::micro> &GetOneThreadTimeout() const {
354361
return m_one_thread_timeout;

lldb/source/Expression/IRInterpreter.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,8 @@ static const char *memory_allocation_error =
470470
"Interpreter couldn't allocate memory";
471471
static const char *memory_write_error = "Interpreter couldn't write to memory";
472472
static const char *memory_read_error = "Interpreter couldn't read from memory";
473-
static const char *infinite_loop_error = "Interpreter ran for too many cycles";
473+
static const char *timeout_error =
474+
"Reached timeout while interpreting expression";
474475
static const char *too_many_functions_error =
475476
"Interpreter doesn't handle modules with multiple function bodies.";
476477

@@ -683,7 +684,8 @@ bool IRInterpreter::Interpret(llvm::Module &module, llvm::Function &function,
683684
lldb_private::Status &error,
684685
lldb::addr_t stack_frame_bottom,
685686
lldb::addr_t stack_frame_top,
686-
lldb_private::ExecutionContext &exe_ctx) {
687+
lldb_private::ExecutionContext &exe_ctx,
688+
lldb_private::Timeout<std::micro> timeout) {
687689
lldb_private::Log *log(GetLog(LLDBLog::Expressions));
688690

689691
if (log) {
@@ -722,11 +724,23 @@ bool IRInterpreter::Interpret(llvm::Module &module, llvm::Function &function,
722724
frame.MakeArgument(&*ai, ptr);
723725
}
724726

725-
uint32_t num_insts = 0;
726-
727727
frame.Jump(&function.front());
728728

729-
while (frame.m_ii != frame.m_ie && (++num_insts < 4096)) {
729+
using clock = std::chrono::steady_clock;
730+
731+
// Compute the time at which the timeout has been exceeded.
732+
std::optional<clock::time_point> end_time;
733+
if (timeout && timeout->count() > 0)
734+
end_time = clock::now() + *timeout;
735+
736+
while (frame.m_ii != frame.m_ie) {
737+
// Timeout reached: stop interpreting.
738+
if (end_time && clock::now() >= *end_time) {
739+
error.SetErrorToGenericError();
740+
error.SetErrorString(timeout_error);
741+
return false;
742+
}
743+
730744
const Instruction *inst = &*frame.m_ii;
731745

732746
LLDB_LOGF(log, "Interpreting %s", PrintValue(inst).c_str());
@@ -1571,11 +1585,5 @@ bool IRInterpreter::Interpret(llvm::Module &module, llvm::Function &function,
15711585
++frame.m_ii;
15721586
}
15731587

1574-
if (num_insts >= 4096) {
1575-
error.SetErrorToGenericError();
1576-
error.SetErrorString(infinite_loop_error);
1577-
return false;
1578-
}
1579-
15801588
return false;
15811589
}

lldb/source/Expression/LLVMUserExpression.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
120120

121121
IRInterpreter::Interpret(*module, *function, args, *m_execution_unit_sp,
122122
interpreter_error, function_stack_bottom,
123-
function_stack_top, exe_ctx);
123+
function_stack_top, exe_ctx, options.GetTimeout());
124124

125125
if (!interpreter_error.Success()) {
126126
diagnostic_manager.Printf(eDiagnosticSeverityError,
@@ -233,7 +233,7 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
233233
eDiagnosticSeverityError,
234234
"Couldn't complete execution; the thread "
235235
"on which the expression was being run: 0x%" PRIx64
236-
" exited during its execution.",
236+
" exited during its execution.",
237237
expr_thread_id);
238238
return execution_result;
239239
} else if (execution_result != lldb::eExpressionCompleted) {

lldb/test/API/commands/expression/ir-interpreter/TestIRInterpreter.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,41 @@
1212
class IRInterpreterTestCase(TestBase):
1313
NO_DEBUG_INFO_TESTCASE = True
1414

15+
def time_expression(self, expr, options):
16+
start = time.time()
17+
res = self.target.EvaluateExpression(expr, options)
18+
return res, time.time() - start
19+
20+
def test_interpreter_timeout(self):
21+
"""Test the timeout logic in the IRInterpreter."""
22+
self.build()
23+
self.target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
24+
self.assertTrue(self.target, VALID_TARGET)
25+
26+
# A non-trivial infinite loop.
27+
inf_loop = "for (unsigned i = 0; i < 100; ++i) --i; 1"
28+
timeout_error = "Reached timeout while interpreting expression"
29+
options = lldb.SBExpressionOptions()
30+
31+
# This is an IRInterpreter specific test, so disable the JIT.
32+
options.SetAllowJIT(False)
33+
34+
# No timeout means a 500ms.
35+
options.SetTimeoutInMicroSeconds(0)
36+
res, duration_sec = self.time_expression(inf_loop, options)
37+
self.assertIn(timeout_error, str(res.GetError()))
38+
39+
# Depending on the machine load the expression might take quite some
40+
# time, so give the time a generous upper bound.
41+
self.assertLess(duration_sec, 15)
42+
43+
# Try a simple one second timeout.
44+
options.SetTimeoutInMicroSeconds(1000000)
45+
res, duration_sec = self.time_expression(inf_loop, options)
46+
self.assertIn(timeout_error, str(res.GetError()))
47+
self.assertGreaterEqual(duration_sec, 1)
48+
self.assertLess(duration_sec, 30)
49+
1550
def setUp(self):
1651
# Call super's setUp().
1752
TestBase.setUp(self)

0 commit comments

Comments
 (0)