1*61e8e688SJim Ingham //===-- ThreadPlanStack.cpp -------------------------------------*- C++ -*-===//
2*61e8e688SJim Ingham //
3*61e8e688SJim Ingham // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*61e8e688SJim Ingham // See https://llvm.org/LICENSE.txt for license information.
5*61e8e688SJim Ingham // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*61e8e688SJim Ingham //
7*61e8e688SJim Ingham //===----------------------------------------------------------------------===//
8*61e8e688SJim Ingham 
9*61e8e688SJim Ingham #include "lldb/Target/ThreadPlanStack.h"
10*61e8e688SJim Ingham #include "lldb/Target/Process.h"
11*61e8e688SJim Ingham #include "lldb/Target/Target.h"
12*61e8e688SJim Ingham #include "lldb/Target/Thread.h"
13*61e8e688SJim Ingham #include "lldb/Target/ThreadPlan.h"
14*61e8e688SJim Ingham #include "lldb/Utility/Log.h"
15*61e8e688SJim Ingham 
16*61e8e688SJim Ingham using namespace lldb;
17*61e8e688SJim Ingham using namespace lldb_private;
18*61e8e688SJim Ingham 
19*61e8e688SJim Ingham static void PrintPlanElement(Stream *s, const ThreadPlanSP &plan,
20*61e8e688SJim Ingham                              lldb::DescriptionLevel desc_level,
21*61e8e688SJim Ingham                              int32_t elem_idx) {
22*61e8e688SJim Ingham   s->IndentMore();
23*61e8e688SJim Ingham   s->Indent();
24*61e8e688SJim Ingham   s->Printf("Element %d: ", elem_idx);
25*61e8e688SJim Ingham   plan->GetDescription(s, desc_level);
26*61e8e688SJim Ingham   s->EOL();
27*61e8e688SJim Ingham   s->IndentLess();
28*61e8e688SJim Ingham }
29*61e8e688SJim Ingham 
30*61e8e688SJim Ingham void ThreadPlanStack::DumpThreadPlans(Stream *s,
31*61e8e688SJim Ingham                                       lldb::DescriptionLevel desc_level,
32*61e8e688SJim Ingham                                       bool include_internal) const {
33*61e8e688SJim Ingham 
34*61e8e688SJim Ingham   uint32_t stack_size;
35*61e8e688SJim Ingham 
36*61e8e688SJim Ingham   s->IndentMore();
37*61e8e688SJim Ingham   s->Indent();
38*61e8e688SJim Ingham   s->Printf("Active plan stack:\n");
39*61e8e688SJim Ingham   int32_t print_idx = 0;
40*61e8e688SJim Ingham   for (auto plan : m_plans) {
41*61e8e688SJim Ingham     PrintPlanElement(s, plan, desc_level, print_idx++);
42*61e8e688SJim Ingham   }
43*61e8e688SJim Ingham 
44*61e8e688SJim Ingham   if (AnyCompletedPlans()) {
45*61e8e688SJim Ingham     print_idx = 0;
46*61e8e688SJim Ingham     s->Indent();
47*61e8e688SJim Ingham     s->Printf("Completed Plan Stack:\n");
48*61e8e688SJim Ingham     for (auto plan : m_completed_plans)
49*61e8e688SJim Ingham       PrintPlanElement(s, plan, desc_level, print_idx++);
50*61e8e688SJim Ingham   }
51*61e8e688SJim Ingham 
52*61e8e688SJim Ingham   if (AnyDiscardedPlans()) {
53*61e8e688SJim Ingham     print_idx = 0;
54*61e8e688SJim Ingham     s->Indent();
55*61e8e688SJim Ingham     s->Printf("Discarded Plan Stack:\n");
56*61e8e688SJim Ingham     for (auto plan : m_discarded_plans)
57*61e8e688SJim Ingham       PrintPlanElement(s, plan, desc_level, print_idx++);
58*61e8e688SJim Ingham   }
59*61e8e688SJim Ingham 
60*61e8e688SJim Ingham   s->IndentLess();
61*61e8e688SJim Ingham }
62*61e8e688SJim Ingham 
63*61e8e688SJim Ingham size_t ThreadPlanStack::CheckpointCompletedPlans() {
64*61e8e688SJim Ingham   m_completed_plan_checkpoint++;
65*61e8e688SJim Ingham   m_completed_plan_store.insert(
66*61e8e688SJim Ingham       std::make_pair(m_completed_plan_checkpoint, m_completed_plans));
67*61e8e688SJim Ingham   return m_completed_plan_checkpoint;
68*61e8e688SJim Ingham }
69*61e8e688SJim Ingham 
70*61e8e688SJim Ingham void ThreadPlanStack::RestoreCompletedPlanCheckpoint(size_t checkpoint) {
71*61e8e688SJim Ingham   auto result = m_completed_plan_store.find(checkpoint);
72*61e8e688SJim Ingham   assert(result != m_completed_plan_store.end() &&
73*61e8e688SJim Ingham          "Asked for a checkpoint that didn't exist");
74*61e8e688SJim Ingham   m_completed_plans.swap((*result).second);
75*61e8e688SJim Ingham   m_completed_plan_store.erase(result);
76*61e8e688SJim Ingham }
77*61e8e688SJim Ingham 
78*61e8e688SJim Ingham void ThreadPlanStack::DiscardCompletedPlanCheckpoint(size_t checkpoint) {
79*61e8e688SJim Ingham   m_completed_plan_store.erase(checkpoint);
80*61e8e688SJim Ingham }
81*61e8e688SJim Ingham 
82*61e8e688SJim Ingham void ThreadPlanStack::ThreadDestroyed(Thread *thread) {
83*61e8e688SJim Ingham   // Tell the plan stacks that this thread is going away:
84*61e8e688SJim Ingham   for (ThreadPlanSP plan : m_plans)
85*61e8e688SJim Ingham     plan->ThreadDestroyed();
86*61e8e688SJim Ingham 
87*61e8e688SJim Ingham   for (ThreadPlanSP plan : m_discarded_plans)
88*61e8e688SJim Ingham     plan->ThreadDestroyed();
89*61e8e688SJim Ingham 
90*61e8e688SJim Ingham   for (ThreadPlanSP plan : m_completed_plans)
91*61e8e688SJim Ingham     plan->ThreadDestroyed();
92*61e8e688SJim Ingham 
93*61e8e688SJim Ingham   // Now clear the current plan stacks:
94*61e8e688SJim Ingham   m_plans.clear();
95*61e8e688SJim Ingham   m_discarded_plans.clear();
96*61e8e688SJim Ingham   m_completed_plans.clear();
97*61e8e688SJim Ingham 
98*61e8e688SJim Ingham   // Push a ThreadPlanNull on the plan stack.  That way we can continue
99*61e8e688SJim Ingham   // assuming that the plan stack is never empty, but if somebody errantly asks
100*61e8e688SJim Ingham   // questions of a destroyed thread without checking first whether it is
101*61e8e688SJim Ingham   // destroyed, they won't crash.
102*61e8e688SJim Ingham   if (thread != nullptr) {
103*61e8e688SJim Ingham     lldb::ThreadPlanSP null_plan_sp(new ThreadPlanNull(*thread));
104*61e8e688SJim Ingham     m_plans.push_back(null_plan_sp);
105*61e8e688SJim Ingham   }
106*61e8e688SJim Ingham }
107*61e8e688SJim Ingham 
108*61e8e688SJim Ingham void ThreadPlanStack::EnableTracer(bool value, bool single_stepping) {
109*61e8e688SJim Ingham   for (ThreadPlanSP plan : m_plans) {
110*61e8e688SJim Ingham     if (plan->GetThreadPlanTracer()) {
111*61e8e688SJim Ingham       plan->GetThreadPlanTracer()->EnableTracing(value);
112*61e8e688SJim Ingham       plan->GetThreadPlanTracer()->EnableSingleStep(single_stepping);
113*61e8e688SJim Ingham     }
114*61e8e688SJim Ingham   }
115*61e8e688SJim Ingham }
116*61e8e688SJim Ingham 
117*61e8e688SJim Ingham void ThreadPlanStack::SetTracer(lldb::ThreadPlanTracerSP &tracer_sp) {
118*61e8e688SJim Ingham   for (ThreadPlanSP plan : m_plans)
119*61e8e688SJim Ingham     plan->SetThreadPlanTracer(tracer_sp);
120*61e8e688SJim Ingham }
121*61e8e688SJim Ingham 
122*61e8e688SJim Ingham void ThreadPlanStack::PushPlan(lldb::ThreadPlanSP new_plan_sp) {
123*61e8e688SJim Ingham   // If the thread plan doesn't already have a tracer, give it its parent's
124*61e8e688SJim Ingham   // tracer:
125*61e8e688SJim Ingham   // The first plan has to be a base plan:
126*61e8e688SJim Ingham   assert(m_plans.size() > 0 ||
127*61e8e688SJim Ingham          new_plan_sp->IsBasePlan() && "Zeroth plan must be a base plan");
128*61e8e688SJim Ingham 
129*61e8e688SJim Ingham   if (!new_plan_sp->GetThreadPlanTracer()) {
130*61e8e688SJim Ingham     assert(!m_plans.empty());
131*61e8e688SJim Ingham     new_plan_sp->SetThreadPlanTracer(m_plans.back()->GetThreadPlanTracer());
132*61e8e688SJim Ingham   }
133*61e8e688SJim Ingham   m_plans.push_back(new_plan_sp);
134*61e8e688SJim Ingham   new_plan_sp->DidPush();
135*61e8e688SJim Ingham }
136*61e8e688SJim Ingham 
137*61e8e688SJim Ingham lldb::ThreadPlanSP ThreadPlanStack::PopPlan() {
138*61e8e688SJim Ingham   assert(m_plans.size() > 1 && "Can't pop the base thread plan");
139*61e8e688SJim Ingham 
140*61e8e688SJim Ingham   lldb::ThreadPlanSP &plan_sp = m_plans.back();
141*61e8e688SJim Ingham   m_completed_plans.push_back(plan_sp);
142*61e8e688SJim Ingham   plan_sp->WillPop();
143*61e8e688SJim Ingham   m_plans.pop_back();
144*61e8e688SJim Ingham   return plan_sp;
145*61e8e688SJim Ingham }
146*61e8e688SJim Ingham 
147*61e8e688SJim Ingham lldb::ThreadPlanSP ThreadPlanStack::DiscardPlan() {
148*61e8e688SJim Ingham   assert(m_plans.size() > 1 && "Can't discard the base thread plan");
149*61e8e688SJim Ingham 
150*61e8e688SJim Ingham   lldb::ThreadPlanSP &plan_sp = m_plans.back();
151*61e8e688SJim Ingham   m_discarded_plans.push_back(plan_sp);
152*61e8e688SJim Ingham   plan_sp->WillPop();
153*61e8e688SJim Ingham   m_plans.pop_back();
154*61e8e688SJim Ingham   return plan_sp;
155*61e8e688SJim Ingham }
156*61e8e688SJim Ingham 
157*61e8e688SJim Ingham // If the input plan is nullptr, discard all plans.  Otherwise make sure this
158*61e8e688SJim Ingham // plan is in the stack, and if so discard up to and including it.
159*61e8e688SJim Ingham void ThreadPlanStack::DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr) {
160*61e8e688SJim Ingham   int stack_size = m_plans.size();
161*61e8e688SJim Ingham 
162*61e8e688SJim Ingham   if (up_to_plan_ptr == nullptr) {
163*61e8e688SJim Ingham     for (int i = stack_size - 1; i > 0; i--)
164*61e8e688SJim Ingham       DiscardPlan();
165*61e8e688SJim Ingham     return;
166*61e8e688SJim Ingham   }
167*61e8e688SJim Ingham 
168*61e8e688SJim Ingham   bool found_it = false;
169*61e8e688SJim Ingham   for (int i = stack_size - 1; i > 0; i--) {
170*61e8e688SJim Ingham     if (m_plans[i].get() == up_to_plan_ptr) {
171*61e8e688SJim Ingham       found_it = true;
172*61e8e688SJim Ingham       break;
173*61e8e688SJim Ingham     }
174*61e8e688SJim Ingham   }
175*61e8e688SJim Ingham 
176*61e8e688SJim Ingham   if (found_it) {
177*61e8e688SJim Ingham     bool last_one = false;
178*61e8e688SJim Ingham     for (int i = stack_size - 1; i > 0 && !last_one; i--) {
179*61e8e688SJim Ingham       if (GetCurrentPlan().get() == up_to_plan_ptr)
180*61e8e688SJim Ingham         last_one = true;
181*61e8e688SJim Ingham       DiscardPlan();
182*61e8e688SJim Ingham     }
183*61e8e688SJim Ingham   }
184*61e8e688SJim Ingham }
185*61e8e688SJim Ingham 
186*61e8e688SJim Ingham void ThreadPlanStack::DiscardAllPlans() {
187*61e8e688SJim Ingham   int stack_size = m_plans.size();
188*61e8e688SJim Ingham   for (int i = stack_size - 1; i > 0; i--) {
189*61e8e688SJim Ingham     DiscardPlan();
190*61e8e688SJim Ingham   }
191*61e8e688SJim Ingham   return;
192*61e8e688SJim Ingham }
193*61e8e688SJim Ingham 
194*61e8e688SJim Ingham void ThreadPlanStack::DiscardConsultingMasterPlans() {
195*61e8e688SJim Ingham   while (true) {
196*61e8e688SJim Ingham     int master_plan_idx;
197*61e8e688SJim Ingham     bool discard = true;
198*61e8e688SJim Ingham 
199*61e8e688SJim Ingham     // Find the first master plan, see if it wants discarding, and if yes
200*61e8e688SJim Ingham     // discard up to it.
201*61e8e688SJim Ingham     for (master_plan_idx = m_plans.size() - 1; master_plan_idx >= 0;
202*61e8e688SJim Ingham          master_plan_idx--) {
203*61e8e688SJim Ingham       if (m_plans[master_plan_idx]->IsMasterPlan()) {
204*61e8e688SJim Ingham         discard = m_plans[master_plan_idx]->OkayToDiscard();
205*61e8e688SJim Ingham         break;
206*61e8e688SJim Ingham       }
207*61e8e688SJim Ingham     }
208*61e8e688SJim Ingham 
209*61e8e688SJim Ingham     // If the master plan doesn't want to get discarded, then we're done.
210*61e8e688SJim Ingham     if (!discard)
211*61e8e688SJim Ingham       return;
212*61e8e688SJim Ingham 
213*61e8e688SJim Ingham     // First pop all the dependent plans:
214*61e8e688SJim Ingham     for (int i = m_plans.size() - 1; i > master_plan_idx; i--) {
215*61e8e688SJim Ingham       DiscardPlan();
216*61e8e688SJim Ingham     }
217*61e8e688SJim Ingham 
218*61e8e688SJim Ingham     // Now discard the master plan itself.
219*61e8e688SJim Ingham     // The bottom-most plan never gets discarded.  "OkayToDiscard" for it
220*61e8e688SJim Ingham     // means discard it's dependent plans, but not it...
221*61e8e688SJim Ingham     if (master_plan_idx > 0) {
222*61e8e688SJim Ingham       DiscardPlan();
223*61e8e688SJim Ingham     }
224*61e8e688SJim Ingham   }
225*61e8e688SJim Ingham }
226*61e8e688SJim Ingham 
227*61e8e688SJim Ingham lldb::ThreadPlanSP ThreadPlanStack::GetCurrentPlan() const {
228*61e8e688SJim Ingham   assert(m_plans.size() != 0 && "There will always be a base plan.");
229*61e8e688SJim Ingham   return m_plans.back();
230*61e8e688SJim Ingham }
231*61e8e688SJim Ingham 
232*61e8e688SJim Ingham lldb::ThreadPlanSP ThreadPlanStack::GetCompletedPlan(bool skip_private) const {
233*61e8e688SJim Ingham   if (m_completed_plans.empty())
234*61e8e688SJim Ingham     return {};
235*61e8e688SJim Ingham 
236*61e8e688SJim Ingham   if (!skip_private)
237*61e8e688SJim Ingham     return m_completed_plans.back();
238*61e8e688SJim Ingham 
239*61e8e688SJim Ingham   for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
240*61e8e688SJim Ingham     lldb::ThreadPlanSP completed_plan_sp;
241*61e8e688SJim Ingham     completed_plan_sp = m_completed_plans[i];
242*61e8e688SJim Ingham     if (!completed_plan_sp->GetPrivate())
243*61e8e688SJim Ingham       return completed_plan_sp;
244*61e8e688SJim Ingham   }
245*61e8e688SJim Ingham   return {};
246*61e8e688SJim Ingham }
247*61e8e688SJim Ingham 
248*61e8e688SJim Ingham lldb::ThreadPlanSP ThreadPlanStack::GetPlanByIndex(uint32_t plan_idx,
249*61e8e688SJim Ingham                                                    bool skip_private) const {
250*61e8e688SJim Ingham   uint32_t idx = 0;
251*61e8e688SJim Ingham   ThreadPlan *up_to_plan_ptr = nullptr;
252*61e8e688SJim Ingham 
253*61e8e688SJim Ingham   for (lldb::ThreadPlanSP plan_sp : m_plans) {
254*61e8e688SJim Ingham     if (skip_private && plan_sp->GetPrivate())
255*61e8e688SJim Ingham       continue;
256*61e8e688SJim Ingham     if (idx == plan_idx)
257*61e8e688SJim Ingham       return plan_sp;
258*61e8e688SJim Ingham     idx++;
259*61e8e688SJim Ingham   }
260*61e8e688SJim Ingham   return {};
261*61e8e688SJim Ingham }
262*61e8e688SJim Ingham 
263*61e8e688SJim Ingham lldb::ValueObjectSP ThreadPlanStack::GetReturnValueObject() const {
264*61e8e688SJim Ingham   if (m_completed_plans.empty())
265*61e8e688SJim Ingham     return {};
266*61e8e688SJim Ingham 
267*61e8e688SJim Ingham   for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
268*61e8e688SJim Ingham     lldb::ValueObjectSP return_valobj_sp;
269*61e8e688SJim Ingham     return_valobj_sp = m_completed_plans[i]->GetReturnValueObject();
270*61e8e688SJim Ingham     if (return_valobj_sp)
271*61e8e688SJim Ingham       return return_valobj_sp;
272*61e8e688SJim Ingham   }
273*61e8e688SJim Ingham   return {};
274*61e8e688SJim Ingham }
275*61e8e688SJim Ingham 
276*61e8e688SJim Ingham lldb::ExpressionVariableSP ThreadPlanStack::GetExpressionVariable() const {
277*61e8e688SJim Ingham   if (m_completed_plans.empty())
278*61e8e688SJim Ingham     return {};
279*61e8e688SJim Ingham 
280*61e8e688SJim Ingham   for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
281*61e8e688SJim Ingham     lldb::ExpressionVariableSP expression_variable_sp;
282*61e8e688SJim Ingham     expression_variable_sp = m_completed_plans[i]->GetExpressionVariable();
283*61e8e688SJim Ingham     if (expression_variable_sp)
284*61e8e688SJim Ingham       return expression_variable_sp;
285*61e8e688SJim Ingham   }
286*61e8e688SJim Ingham   return {};
287*61e8e688SJim Ingham }
288*61e8e688SJim Ingham bool ThreadPlanStack::AnyPlans() const {
289*61e8e688SJim Ingham   // There is always a base plan...
290*61e8e688SJim Ingham   return m_plans.size() > 1;
291*61e8e688SJim Ingham }
292*61e8e688SJim Ingham 
293*61e8e688SJim Ingham bool ThreadPlanStack::AnyCompletedPlans() const {
294*61e8e688SJim Ingham   return !m_completed_plans.empty();
295*61e8e688SJim Ingham }
296*61e8e688SJim Ingham 
297*61e8e688SJim Ingham bool ThreadPlanStack::AnyDiscardedPlans() const {
298*61e8e688SJim Ingham   return !m_discarded_plans.empty();
299*61e8e688SJim Ingham }
300*61e8e688SJim Ingham 
301*61e8e688SJim Ingham bool ThreadPlanStack::IsPlanDone(ThreadPlan *in_plan) const {
302*61e8e688SJim Ingham   for (auto plan : m_completed_plans) {
303*61e8e688SJim Ingham     if (plan.get() == in_plan)
304*61e8e688SJim Ingham       return true;
305*61e8e688SJim Ingham   }
306*61e8e688SJim Ingham   return false;
307*61e8e688SJim Ingham }
308*61e8e688SJim Ingham 
309*61e8e688SJim Ingham bool ThreadPlanStack::WasPlanDiscarded(ThreadPlan *in_plan) const {
310*61e8e688SJim Ingham   for (auto plan : m_discarded_plans) {
311*61e8e688SJim Ingham     if (plan.get() == in_plan)
312*61e8e688SJim Ingham       return true;
313*61e8e688SJim Ingham   }
314*61e8e688SJim Ingham   return false;
315*61e8e688SJim Ingham }
316*61e8e688SJim Ingham 
317*61e8e688SJim Ingham ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const {
318*61e8e688SJim Ingham   if (current_plan == nullptr)
319*61e8e688SJim Ingham     return nullptr;
320*61e8e688SJim Ingham 
321*61e8e688SJim Ingham   // Look first in the completed plans, if the plan is here and there is
322*61e8e688SJim Ingham   // a completed plan above it, return that.
323*61e8e688SJim Ingham   int stack_size = m_completed_plans.size();
324*61e8e688SJim Ingham   for (int i = stack_size - 1; i > 0; i--) {
325*61e8e688SJim Ingham     if (current_plan == m_completed_plans[i].get())
326*61e8e688SJim Ingham       return m_completed_plans[i - 1].get();
327*61e8e688SJim Ingham   }
328*61e8e688SJim Ingham 
329*61e8e688SJim Ingham   // If this is the first completed plan, the previous one is the
330*61e8e688SJim Ingham   // bottom of the regular plan stack.
331*61e8e688SJim Ingham   if (stack_size > 0 && m_completed_plans[0].get() == current_plan) {
332*61e8e688SJim Ingham     return GetCurrentPlan().get();
333*61e8e688SJim Ingham   }
334*61e8e688SJim Ingham 
335*61e8e688SJim Ingham   // Otherwise look for it in the regular plans.
336*61e8e688SJim Ingham   stack_size = m_plans.size();
337*61e8e688SJim Ingham   for (int i = stack_size - 1; i > 0; i--) {
338*61e8e688SJim Ingham     if (current_plan == m_plans[i].get())
339*61e8e688SJim Ingham       return m_plans[i - 1].get();
340*61e8e688SJim Ingham   }
341*61e8e688SJim Ingham   return nullptr;
342*61e8e688SJim Ingham }
343*61e8e688SJim Ingham 
344*61e8e688SJim Ingham ThreadPlan *ThreadPlanStack::GetInnermostExpression() const {
345*61e8e688SJim Ingham   int stack_size = m_plans.size();
346*61e8e688SJim Ingham 
347*61e8e688SJim Ingham   for (int i = stack_size - 1; i > 0; i--) {
348*61e8e688SJim Ingham     if (m_plans[i]->GetKind() == ThreadPlan::eKindCallFunction)
349*61e8e688SJim Ingham       return m_plans[i].get();
350*61e8e688SJim Ingham   }
351*61e8e688SJim Ingham   return nullptr;
352*61e8e688SJim Ingham }
353*61e8e688SJim Ingham 
354*61e8e688SJim Ingham void ThreadPlanStack::WillResume() {
355*61e8e688SJim Ingham   m_completed_plans.clear();
356*61e8e688SJim Ingham   m_discarded_plans.clear();
357*61e8e688SJim Ingham }
358*61e8e688SJim Ingham 
359*61e8e688SJim Ingham const ThreadPlanStack::PlanStack &
360*61e8e688SJim Ingham ThreadPlanStack::GetStackOfKind(ThreadPlanStack::StackKind kind) const {
361*61e8e688SJim Ingham   switch (kind) {
362*61e8e688SJim Ingham   case ePlans:
363*61e8e688SJim Ingham     return m_plans;
364*61e8e688SJim Ingham   case eCompletedPlans:
365*61e8e688SJim Ingham     return m_completed_plans;
366*61e8e688SJim Ingham   case eDiscardedPlans:
367*61e8e688SJim Ingham     return m_discarded_plans;
368*61e8e688SJim Ingham   }
369*61e8e688SJim Ingham   llvm_unreachable("Invalid StackKind value");
370*61e8e688SJim Ingham }
371