blob: 015c784ff4b02c3e98bcbb7cf14019f82e6dd172 [file] [log] [blame]
Chris Lattner30fdc8d2010-06-08 16:52:24 +00001//===-- ThreadPlanStepInRange.cpp -------------------------------*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
Chris Lattner30fdc8d2010-06-08 16:52:24 +000010// C Includes
11// C++ Includes
12// Other libraries and framework includes
13// Project includes
Eugene Zelenkoe65b2cf2015-12-15 01:33:19 +000014#include "lldb/Target/ThreadPlanStepInRange.h"
Jim Ingham08581262018-03-12 21:17:04 +000015#include "lldb/Core/Architecture.h"
Jim Ingham4da62062014-01-23 21:52:47 +000016#include "lldb/Core/Module.h"
Jim Ingham7ce490c2010-09-16 00:58:09 +000017#include "lldb/Symbol/Function.h"
Kate Stoneb9c1b512016-09-06 20:57:50 +000018#include "lldb/Symbol/Symbol.h"
Chris Lattner30fdc8d2010-06-08 16:52:24 +000019#include "lldb/Target/Process.h"
20#include "lldb/Target/RegisterContext.h"
Jim Ingham08581262018-03-12 21:17:04 +000021#include "lldb/Target/SectionLoadList.h"
Greg Clayton514487e2011-02-15 21:59:32 +000022#include "lldb/Target/Target.h"
Chris Lattner30fdc8d2010-06-08 16:52:24 +000023#include "lldb/Target/Thread.h"
24#include "lldb/Target/ThreadPlanStepOut.h"
25#include "lldb/Target/ThreadPlanStepThrough.h"
Zachary Turner6f9e6902017-03-03 20:56:28 +000026#include "lldb/Utility/Log.h"
Zachary Turnerbf9a7732017-02-02 21:39:50 +000027#include "lldb/Utility/RegularExpression.h"
28#include "lldb/Utility/Stream.h"
Chris Lattner30fdc8d2010-06-08 16:52:24 +000029
30using namespace lldb;
31using namespace lldb_private;
32
Kate Stoneb9c1b512016-09-06 20:57:50 +000033uint32_t ThreadPlanStepInRange::s_default_flag_values =
34 ThreadPlanShouldStopHere::eStepInAvoidNoDebug;
Chris Lattner30fdc8d2010-06-08 16:52:24 +000035
36//----------------------------------------------------------------------
Kate Stoneb9c1b512016-09-06 20:57:50 +000037// ThreadPlanStepInRange: Step through a stack range, either stepping over or
38// into
Chris Lattner30fdc8d2010-06-08 16:52:24 +000039// based on the value of \a type.
40//----------------------------------------------------------------------
41
Kate Stoneb9c1b512016-09-06 20:57:50 +000042ThreadPlanStepInRange::ThreadPlanStepInRange(
43 Thread &thread, const AddressRange &range,
44 const SymbolContext &addr_context, lldb::RunMode stop_others,
Jim Ingham4b4b2472014-03-13 02:47:14 +000045 LazyBool step_in_avoids_code_without_debug_info,
Kate Stoneb9c1b512016-09-06 20:57:50 +000046 LazyBool step_out_avoids_code_without_debug_info)
47 : ThreadPlanStepRange(ThreadPlan::eKindStepInRange,
48 "Step Range stepping in", thread, range, addr_context,
49 stop_others),
50 ThreadPlanShouldStopHere(this), m_step_past_prologue(true),
51 m_virtual_step(false) {
52 SetCallbacks();
53 SetFlagsToDefault();
54 SetupAvoidNoDebug(step_in_avoids_code_without_debug_info,
55 step_out_avoids_code_without_debug_info);
Chris Lattner30fdc8d2010-06-08 16:52:24 +000056}
57
Kate Stoneb9c1b512016-09-06 20:57:50 +000058ThreadPlanStepInRange::ThreadPlanStepInRange(
59 Thread &thread, const AddressRange &range,
60 const SymbolContext &addr_context, const char *step_into_target,
61 lldb::RunMode stop_others, LazyBool step_in_avoids_code_without_debug_info,
62 LazyBool step_out_avoids_code_without_debug_info)
63 : ThreadPlanStepRange(ThreadPlan::eKindStepInRange,
64 "Step Range stepping in", thread, range, addr_context,
65 stop_others),
66 ThreadPlanShouldStopHere(this), m_step_past_prologue(true),
67 m_virtual_step(false), m_step_into_target(step_into_target) {
68 SetCallbacks();
69 SetFlagsToDefault();
70 SetupAvoidNoDebug(step_in_avoids_code_without_debug_info,
71 step_out_avoids_code_without_debug_info);
Jim Inghamc6276822012-12-12 19:58:40 +000072}
73
Eugene Zelenkoe65b2cf2015-12-15 01:33:19 +000074ThreadPlanStepInRange::~ThreadPlanStepInRange() = default;
Chris Lattner30fdc8d2010-06-08 16:52:24 +000075
Kate Stoneb9c1b512016-09-06 20:57:50 +000076void ThreadPlanStepInRange::SetupAvoidNoDebug(
77 LazyBool step_in_avoids_code_without_debug_info,
78 LazyBool step_out_avoids_code_without_debug_info) {
79 bool avoid_nodebug = true;
80
81 switch (step_in_avoids_code_without_debug_info) {
82 case eLazyBoolYes:
83 avoid_nodebug = true;
84 break;
85 case eLazyBoolNo:
86 avoid_nodebug = false;
87 break;
88 case eLazyBoolCalculate:
89 avoid_nodebug = m_thread.GetStepInAvoidsNoDebug();
90 break;
91 }
92 if (avoid_nodebug)
93 GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug);
94 else
95 GetFlags().Clear(ThreadPlanShouldStopHere::eStepInAvoidNoDebug);
96
97 switch (step_out_avoids_code_without_debug_info) {
98 case eLazyBoolYes:
99 avoid_nodebug = true;
100 break;
101 case eLazyBoolNo:
102 avoid_nodebug = false;
103 break;
104 case eLazyBoolCalculate:
105 avoid_nodebug = m_thread.GetStepOutAvoidsNoDebug();
106 break;
107 }
108 if (avoid_nodebug)
109 GetFlags().Set(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug);
110 else
111 GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug);
Jim Ingham4b4b2472014-03-13 02:47:14 +0000112}
113
Kate Stoneb9c1b512016-09-06 20:57:50 +0000114void ThreadPlanStepInRange::GetDescription(Stream *s,
115 lldb::DescriptionLevel level) {
116 if (level == lldb::eDescriptionLevelBrief) {
117 s->Printf("step in");
118 return;
119 }
Jim Ingham2bdbfd52014-09-29 23:17:18 +0000120
Kate Stoneb9c1b512016-09-06 20:57:50 +0000121 s->Printf("Stepping in");
122 bool printed_line_info = false;
123 if (m_addr_context.line_entry.IsValid()) {
124 s->Printf(" through line ");
125 m_addr_context.line_entry.DumpStopContext(s, false);
126 printed_line_info = true;
127 }
Jim Ingham2bdbfd52014-09-29 23:17:18 +0000128
Kate Stoneb9c1b512016-09-06 20:57:50 +0000129 const char *step_into_target = m_step_into_target.AsCString();
130 if (step_into_target && step_into_target[0] != '\0')
131 s->Printf(" targeting %s", m_step_into_target.AsCString());
Jim Ingham2bdbfd52014-09-29 23:17:18 +0000132
Kate Stoneb9c1b512016-09-06 20:57:50 +0000133 if (!printed_line_info || level == eDescriptionLevelVerbose) {
134 s->Printf(" using ranges:");
135 DumpRanges(s);
136 }
Jim Ingham2bdbfd52014-09-29 23:17:18 +0000137
Kate Stoneb9c1b512016-09-06 20:57:50 +0000138 s->PutChar('.');
Chris Lattner30fdc8d2010-06-08 16:52:24 +0000139}
140
Kate Stoneb9c1b512016-09-06 20:57:50 +0000141bool ThreadPlanStepInRange::ShouldStop(Event *event_ptr) {
142 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
Chris Lattner30fdc8d2010-06-08 16:52:24 +0000143
Kate Stoneb9c1b512016-09-06 20:57:50 +0000144 if (log) {
145 StreamString s;
146 s.Address(
147 m_thread.GetRegisterContext()->GetPC(),
148 m_thread.CalculateTarget()->GetArchitecture().GetAddressByteSize());
149 log->Printf("ThreadPlanStepInRange reached %s.", s.GetData());
150 }
Jim Inghamf02a2e92012-09-07 01:11:44 +0000151
Kate Stoneb9c1b512016-09-06 20:57:50 +0000152 if (IsPlanComplete())
Jim Ingham221d51c2013-05-08 00:35:16 +0000153 return true;
Kate Stoneb9c1b512016-09-06 20:57:50 +0000154
155 m_no_more_plans = false;
156 if (m_sub_plan_sp && m_sub_plan_sp->IsPlanComplete()) {
157 if (!m_sub_plan_sp->PlanSucceeded()) {
158 SetPlanComplete();
159 m_no_more_plans = true;
160 return true;
161 } else
162 m_sub_plan_sp.reset();
163 }
164
165 if (m_virtual_step) {
166 // If we've just completed a virtual step, all we need to do is check for a
167 // ShouldStopHere plan, and otherwise
168 // we're done.
169 // FIXME - This can be both a step in and a step out. Probably should
170 // record which in the m_virtual_step.
171 m_sub_plan_sp = CheckShouldStopHereAndQueueStepOut(eFrameCompareYounger);
172 } else {
173 // Stepping through should be done running other threads in general, since
174 // we're setting a breakpoint and
175 // continuing. So only stop others if we are explicitly told to do so.
176
177 bool stop_others = (m_stop_others == lldb::eOnlyThisThread);
178
179 FrameComparison frame_order = CompareCurrentFrameToStartFrame();
180
181 if (frame_order == eFrameCompareOlder ||
182 frame_order == eFrameCompareSameParent) {
183 // If we're in an older frame then we should stop.
184 //
185 // A caveat to this is if we think the frame is older but we're actually
186 // in a trampoline.
187 // I'm going to make the assumption that you wouldn't RETURN to a
188 // trampoline. So if we are
189 // in a trampoline we think the frame is older because the trampoline
190 // confused the backtracer.
191 m_sub_plan_sp = m_thread.QueueThreadPlanForStepThrough(m_stack_id, false,
192 stop_others);
193 if (!m_sub_plan_sp) {
194 // Otherwise check the ShouldStopHere for step out:
195 m_sub_plan_sp = CheckShouldStopHereAndQueueStepOut(frame_order);
Jim Inghama4bb80b2017-08-23 19:40:21 +0000196 if (log) {
197 if (m_sub_plan_sp)
198 log->Printf("ShouldStopHere found plan to step out of this frame.");
199 else
200 log->Printf("ShouldStopHere no plan to step out of this frame.");
201 }
Kate Stoneb9c1b512016-09-06 20:57:50 +0000202 } else if (log) {
203 log->Printf(
204 "Thought I stepped out, but in fact arrived at a trampoline.");
205 }
206 } else if (frame_order == eFrameCompareEqual && InSymbol()) {
207 // If we are not in a place we should step through, we're done.
208 // One tricky bit here is that some stubs don't push a frame, so we have
209 // to check
210 // both the case of a frame that is younger, or the same as this frame.
211 // However, if the frame is the same, and we are still in the symbol we
212 // started
213 // in, the we don't need to do this. This first check isn't strictly
214 // necessary,
215 // but it is more efficient.
216
217 // If we're still in the range, keep going, either by running to the next
218 // branch breakpoint, or by
219 // stepping.
220 if (InRange()) {
221 SetNextBranchBreakpoint();
222 return false;
223 }
224
225 SetPlanComplete();
226 m_no_more_plans = true;
227 return true;
228 }
229
230 // If we get to this point, we're not going to use a previously set "next
231 // branch" breakpoint, so delete it:
232 ClearNextBranchBreakpoint();
233
234 // We may have set the plan up above in the FrameIsOlder section:
235
236 if (!m_sub_plan_sp)
237 m_sub_plan_sp = m_thread.QueueThreadPlanForStepThrough(m_stack_id, false,
238 stop_others);
239
240 if (log) {
241 if (m_sub_plan_sp)
242 log->Printf("Found a step through plan: %s", m_sub_plan_sp->GetName());
243 else
244 log->Printf("No step through plan found.");
245 }
246
247 // If not, give the "should_stop" callback a chance to push a plan to get us
248 // out of here.
249 // But only do that if we actually have stepped in.
250 if (!m_sub_plan_sp && frame_order == eFrameCompareYounger)
251 m_sub_plan_sp = CheckShouldStopHereAndQueueStepOut(frame_order);
252
253 // If we've stepped in and we are going to stop here, check to see if we
254 // were asked to
255 // run past the prologue, and if so do that.
256
257 if (!m_sub_plan_sp && frame_order == eFrameCompareYounger &&
258 m_step_past_prologue) {
259 lldb::StackFrameSP curr_frame = m_thread.GetStackFrameAtIndex(0);
260 if (curr_frame) {
261 size_t bytes_to_skip = 0;
262 lldb::addr_t curr_addr = m_thread.GetRegisterContext()->GetPC();
263 Address func_start_address;
264
265 SymbolContext sc = curr_frame->GetSymbolContext(eSymbolContextFunction |
266 eSymbolContextSymbol);
267
268 if (sc.function) {
269 func_start_address = sc.function->GetAddressRange().GetBaseAddress();
270 if (curr_addr ==
271 func_start_address.GetLoadAddress(
272 m_thread.CalculateTarget().get()))
273 bytes_to_skip = sc.function->GetPrologueByteSize();
274 } else if (sc.symbol) {
275 func_start_address = sc.symbol->GetAddress();
276 if (curr_addr ==
277 func_start_address.GetLoadAddress(
278 m_thread.CalculateTarget().get()))
279 bytes_to_skip = sc.symbol->GetPrologueByteSize();
280 }
281
Jim Ingham08581262018-03-12 21:17:04 +0000282 if (bytes_to_skip == 0 && sc.symbol) {
283 TargetSP target = m_thread.CalculateTarget();
284 Architecture *arch = target->GetArchitecturePlugin();
285 if (arch) {
286 Address curr_sec_addr;
287 target->GetSectionLoadList().ResolveLoadAddress(curr_addr,
288 curr_sec_addr);
289 bytes_to_skip = arch->GetBytesToSkip(*sc.symbol, curr_sec_addr);
290 }
291 }
292
Kate Stoneb9c1b512016-09-06 20:57:50 +0000293 if (bytes_to_skip != 0) {
294 func_start_address.Slide(bytes_to_skip);
295 log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP);
296 if (log)
297 log->Printf("Pushing past prologue ");
298
299 m_sub_plan_sp = m_thread.QueueThreadPlanForRunToAddress(
300 false, func_start_address, true);
301 }
302 }
303 }
304 }
305
306 if (!m_sub_plan_sp) {
307 m_no_more_plans = true;
308 SetPlanComplete();
309 return true;
310 } else {
311 m_no_more_plans = false;
312 m_sub_plan_sp->SetPrivate(true);
313 return false;
314 }
Jim Ingham513c6bb2012-09-01 01:02:41 +0000315}
Daniel Malea246cb612013-05-14 15:20:12 +0000316
Kate Stoneb9c1b512016-09-06 20:57:50 +0000317void ThreadPlanStepInRange::SetAvoidRegexp(const char *name) {
Zachary Turner95eae422016-09-21 16:01:28 +0000318 auto name_ref = llvm::StringRef::withNullAsEmpty(name);
Kate Stoneb9c1b512016-09-06 20:57:50 +0000319 if (!m_avoid_regexp_ap)
Zachary Turner95eae422016-09-21 16:01:28 +0000320 m_avoid_regexp_ap.reset(new RegularExpression(name_ref));
Kate Stoneb9c1b512016-09-06 20:57:50 +0000321
Zachary Turner95eae422016-09-21 16:01:28 +0000322 m_avoid_regexp_ap->Compile(name_ref);
Daniel Malea246cb612013-05-14 15:20:12 +0000323}
Kate Stoneb9c1b512016-09-06 20:57:50 +0000324
325void ThreadPlanStepInRange::SetDefaultFlagValue(uint32_t new_value) {
326 // TODO: Should we test this for sanity?
327 ThreadPlanStepInRange::s_default_flag_values = new_value;
328}
329
330bool ThreadPlanStepInRange::FrameMatchesAvoidCriteria() {
331 StackFrame *frame = GetThread().GetStackFrameAtIndex(0).get();
332
333 // Check the library list first, as that's cheapest:
334 bool libraries_say_avoid = false;
335
336 FileSpecList libraries_to_avoid(GetThread().GetLibrariesToAvoid());
337 size_t num_libraries = libraries_to_avoid.GetSize();
338 if (num_libraries > 0) {
339 SymbolContext sc(frame->GetSymbolContext(eSymbolContextModule));
340 FileSpec frame_library(sc.module_sp->GetFileSpec());
341
342 if (frame_library) {
343 for (size_t i = 0; i < num_libraries; i++) {
344 const FileSpec &file_spec(libraries_to_avoid.GetFileSpecAtIndex(i));
345 if (FileSpec::Equal(file_spec, frame_library, false)) {
346 libraries_say_avoid = true;
347 break;
348 }
349 }
350 }
351 }
352 if (libraries_say_avoid)
353 return true;
354
355 const RegularExpression *avoid_regexp_to_use = m_avoid_regexp_ap.get();
356 if (avoid_regexp_to_use == nullptr)
357 avoid_regexp_to_use = GetThread().GetSymbolsToAvoidRegexp();
358
359 if (avoid_regexp_to_use != nullptr) {
360 SymbolContext sc = frame->GetSymbolContext(
361 eSymbolContextFunction | eSymbolContextBlock | eSymbolContextSymbol);
362 if (sc.symbol != nullptr) {
363 const char *frame_function_name =
364 sc.GetFunctionName(Mangled::ePreferDemangledWithoutArguments)
365 .GetCString();
366 if (frame_function_name) {
367 size_t num_matches = 0;
368 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
369 if (log)
370 num_matches = 1;
371
372 RegularExpression::Match regex_match(num_matches);
373
374 bool return_value =
375 avoid_regexp_to_use->Execute(frame_function_name, &regex_match);
376 if (return_value) {
377 if (log) {
378 std::string match;
379 regex_match.GetMatchAtIndex(frame_function_name, 0, match);
380 log->Printf("Stepping out of function \"%s\" because it matches "
381 "the avoid regexp \"%s\" - match substring: \"%s\".",
Zachary Turner95eae422016-09-21 16:01:28 +0000382 frame_function_name,
383 avoid_regexp_to_use->GetText().str().c_str(),
Kate Stoneb9c1b512016-09-06 20:57:50 +0000384 match.c_str());
385 }
386 }
387 return return_value;
388 }
389 }
390 }
391 return false;
392}
393
394bool ThreadPlanStepInRange::DefaultShouldStopHereCallback(
395 ThreadPlan *current_plan, Flags &flags, FrameComparison operation,
396 void *baton) {
397 bool should_stop_here = true;
398 StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get();
399 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
400
401 // First see if the ThreadPlanShouldStopHere default implementation thinks we
402 // should get out of here:
403 should_stop_here = ThreadPlanShouldStopHere::DefaultShouldStopHereCallback(
404 current_plan, flags, operation, baton);
405 if (!should_stop_here)
406 return should_stop_here;
407
408 if (should_stop_here && current_plan->GetKind() == eKindStepInRange &&
409 operation == eFrameCompareYounger) {
410 ThreadPlanStepInRange *step_in_range_plan =
411 static_cast<ThreadPlanStepInRange *>(current_plan);
412 if (step_in_range_plan->m_step_into_target) {
413 SymbolContext sc = frame->GetSymbolContext(
414 eSymbolContextFunction | eSymbolContextBlock | eSymbolContextSymbol);
415 if (sc.symbol != nullptr) {
416 // First try an exact match, since that's cheap with ConstStrings. Then
417 // do a strstr compare.
418 if (step_in_range_plan->m_step_into_target == sc.GetFunctionName()) {
419 should_stop_here = true;
420 } else {
421 const char *target_name =
422 step_in_range_plan->m_step_into_target.AsCString();
423 const char *function_name = sc.GetFunctionName().AsCString();
424
425 if (function_name == nullptr)
426 should_stop_here = false;
427 else if (strstr(function_name, target_name) == nullptr)
428 should_stop_here = false;
429 }
430 if (log && !should_stop_here)
431 log->Printf("Stepping out of frame %s which did not match step into "
432 "target %s.",
433 sc.GetFunctionName().AsCString(),
434 step_in_range_plan->m_step_into_target.AsCString());
435 }
436 }
437
438 if (should_stop_here) {
439 ThreadPlanStepInRange *step_in_range_plan =
440 static_cast<ThreadPlanStepInRange *>(current_plan);
441 // Don't log the should_step_out here, it's easier to do it in
442 // FrameMatchesAvoidCriteria.
443 should_stop_here = !step_in_range_plan->FrameMatchesAvoidCriteria();
444 }
445 }
446
447 return should_stop_here;
448}
449
450bool ThreadPlanStepInRange::DoPlanExplainsStop(Event *event_ptr) {
451 // We always explain a stop. Either we've just done a single step, in which
452 // case we'll do our ordinary processing, or we stopped for some
453 // reason that isn't handled by our sub-plans, in which case we want to just
454 // stop right
455 // away.
456 // In general, we don't want to mark the plan as complete for unexplained
457 // stops.
458 // For instance, if you step in to some code with no debug info, so you step
459 // out
460 // and in the course of that hit a breakpoint, then you want to stop & show
461 // the user
462 // the breakpoint, but not unship the step in plan, since you still may want
463 // to complete that
464 // plan when you continue. This is particularly true when doing "step in to
465 // target function."
466 // stepping.
467 //
468 // The only variation is that if we are doing "step by running to next branch"
469 // in which case
470 // if we hit our branch breakpoint we don't set the plan to complete.
471
472 bool return_value = false;
473
474 if (m_virtual_step) {
475 return_value = true;
476 } else {
477 StopInfoSP stop_info_sp = GetPrivateStopInfo();
478 if (stop_info_sp) {
479 StopReason reason = stop_info_sp->GetStopReason();
480
481 if (reason == eStopReasonBreakpoint) {
482 if (NextRangeBreakpointExplainsStop(stop_info_sp)) {
483 return_value = true;
484 }
485 } else if (IsUsuallyUnexplainedStopReason(reason)) {
486 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
487 if (log)
488 log->PutCString("ThreadPlanStepInRange got asked if it explains the "
489 "stop for some reason other than step.");
490 return_value = false;
491 } else {
492 return_value = true;
493 }
494 } else
495 return_value = true;
496 }
497
498 return return_value;
499}
500
501bool ThreadPlanStepInRange::DoWillResume(lldb::StateType resume_state,
502 bool current_plan) {
503 m_virtual_step = false;
504 if (resume_state == eStateStepping && current_plan) {
505 // See if we are about to step over a virtual inlined call.
506 bool step_without_resume = m_thread.DecrementCurrentInlinedDepth();
507 if (step_without_resume) {
508 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
509 if (log)
510 log->Printf("ThreadPlanStepInRange::DoWillResume: returning false, "
511 "inline_depth: %d",
512 m_thread.GetCurrentInlinedDepth());
513 SetStopInfo(StopInfo::CreateStopReasonToTrace(m_thread));
514
515 // FIXME: Maybe it would be better to create a InlineStep stop reason, but
516 // then
517 // the whole rest of the world would have to handle that stop reason.
518 m_virtual_step = true;
519 }
520 return !step_without_resume;
521 }
522 return true;
523}
524
525bool ThreadPlanStepInRange::IsVirtualStep() { return m_virtual_step; }