130fdc8d8SChris Lattner //===-- ThreadPlanCallFunction.cpp ------------------------------*- C++ -*-===// 230fdc8d8SChris Lattner // 330fdc8d8SChris Lattner // The LLVM Compiler Infrastructure 430fdc8d8SChris Lattner // 530fdc8d8SChris Lattner // This file is distributed under the University of Illinois Open Source 630fdc8d8SChris Lattner // License. See LICENSE.TXT for details. 730fdc8d8SChris Lattner // 830fdc8d8SChris Lattner //===----------------------------------------------------------------------===// 930fdc8d8SChris Lattner 1030fdc8d8SChris Lattner #include "lldb/Target/ThreadPlanCallFunction.h" 1130fdc8d8SChris Lattner 1230fdc8d8SChris Lattner // C Includes 1330fdc8d8SChris Lattner // C++ Includes 1430fdc8d8SChris Lattner // Other libraries and framework includes 156db73ca5SSean Callanan #include "llvm/Support/MachO.h" 1630fdc8d8SChris Lattner // Project includes 1730fdc8d8SChris Lattner #include "lldb/lldb-private-log.h" 1840d871faSJim Ingham #include "lldb/Breakpoint/Breakpoint.h" 1940d871faSJim Ingham #include "lldb/Breakpoint/BreakpointLocation.h" 2030fdc8d8SChris Lattner #include "lldb/Core/Address.h" 2130fdc8d8SChris Lattner #include "lldb/Core/Log.h" 2230fdc8d8SChris Lattner #include "lldb/Core/Stream.h" 23f211510fSSean Callanan #include "lldb/Target/LanguageRuntime.h" 2430fdc8d8SChris Lattner #include "lldb/Target/Process.h" 2530fdc8d8SChris Lattner #include "lldb/Target/RegisterContext.h" 2640d871faSJim Ingham #include "lldb/Target/StopInfo.h" 2730fdc8d8SChris Lattner #include "lldb/Target/Target.h" 2830fdc8d8SChris Lattner #include "lldb/Target/Thread.h" 2930fdc8d8SChris Lattner #include "lldb/Target/ThreadPlanRunToAddress.h" 3030fdc8d8SChris Lattner 3130fdc8d8SChris Lattner using namespace lldb; 3230fdc8d8SChris Lattner using namespace lldb_private; 3330fdc8d8SChris Lattner 3430fdc8d8SChris Lattner //---------------------------------------------------------------------- 3530fdc8d8SChris Lattner // ThreadPlanCallFunction: Plan to call a single function 3630fdc8d8SChris Lattner //---------------------------------------------------------------------- 3730fdc8d8SChris Lattner 3830fdc8d8SChris Lattner ThreadPlanCallFunction::ThreadPlanCallFunction (Thread &thread, 3930fdc8d8SChris Lattner Address &function, 4030fdc8d8SChris Lattner lldb::addr_t arg, 4130fdc8d8SChris Lattner bool stop_other_threads, 42fc55f5d1SSean Callanan bool discard_on_error, 4317827830SSean Callanan lldb::addr_t *this_arg, 4417827830SSean Callanan lldb::addr_t *cmd_arg) : 45b01e742aSJim Ingham ThreadPlan (ThreadPlan::eKindCallFunction, "Call function plan", thread, eVoteNoOpinion, eVoteNoOpinion), 4630fdc8d8SChris Lattner m_valid (false), 471ee0d4f7SBenjamin Kramer m_stop_other_threads (stop_other_threads), 4830fdc8d8SChris Lattner m_arg_addr (arg), 4930fdc8d8SChris Lattner m_args (NULL), 501ee0d4f7SBenjamin Kramer m_process (thread.GetProcess()), 519da3683cSJim Ingham m_thread (thread), 52e359d9b7SSean Callanan m_takedown_done (false), 53e359d9b7SSean Callanan m_function_sp(NULL) 5430fdc8d8SChris Lattner { 5530fdc8d8SChris Lattner SetOkayToDiscard (discard_on_error); 5630fdc8d8SChris Lattner 5730fdc8d8SChris Lattner Process& process = thread.GetProcess(); 5830fdc8d8SChris Lattner Target& target = process.GetTarget(); 5931f1d2f5SGreg Clayton const ABI *abi = process.GetABI().get(); 6030fdc8d8SChris Lattner 6130fdc8d8SChris Lattner if (!abi) 6230fdc8d8SChris Lattner return; 6330fdc8d8SChris Lattner 6477787033SJim Ingham LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); 6577787033SJim Ingham 666db73ca5SSean Callanan SetBreakpoints(); 676db73ca5SSean Callanan 68e359d9b7SSean Callanan m_function_sp = thread.GetRegisterContext()->GetSP() - abi->GetRedZoneSize(); 6930fdc8d8SChris Lattner 7030fdc8d8SChris Lattner ModuleSP executableModuleSP (target.GetExecutableModule()); 7130fdc8d8SChris Lattner 72672e6f59SJim Ingham if (!executableModuleSP) 73672e6f59SJim Ingham { 74672e6f59SJim Ingham log->Printf ("Can't execute code without an executable module."); 7530fdc8d8SChris Lattner return; 76672e6f59SJim Ingham } 77672e6f59SJim Ingham else 78672e6f59SJim Ingham { 79672e6f59SJim Ingham ObjectFile *objectFile = executableModuleSP->GetObjectFile(); 80672e6f59SJim Ingham if (!objectFile) 81672e6f59SJim Ingham { 82672e6f59SJim Ingham log->Printf ("Could not find object file for module \"%s\".", 83672e6f59SJim Ingham executableModuleSP->GetFileSpec().GetFilename().AsCString()); 84672e6f59SJim Ingham return; 85672e6f59SJim Ingham } 86672e6f59SJim Ingham m_start_addr = objectFile->GetEntryPointAddress(); 87672e6f59SJim Ingham if (!m_start_addr.IsValid()) 88672e6f59SJim Ingham { 89672e6f59SJim Ingham log->Printf ("Could not find entry point address for executable module \"%s\".", 90672e6f59SJim Ingham executableModuleSP->GetFileSpec().GetFilename().AsCString()); 91672e6f59SJim Ingham return; 92672e6f59SJim Ingham } 93672e6f59SJim Ingham } 9430fdc8d8SChris Lattner 95f5e56de0SGreg Clayton lldb::addr_t StartLoadAddr = m_start_addr.GetLoadAddress(&target); 9630fdc8d8SChris Lattner 9777787033SJim Ingham // Checkpoint the thread state so we can restore it later. 989da3683cSJim Ingham if (log && log->GetVerbose()) 999da3683cSJim Ingham ReportRegisterState ("About to checkpoint thread before function call. Original register state was:"); 1009da3683cSJim Ingham 10177787033SJim Ingham if (!thread.CheckpointThreadState (m_stored_thread_state)) 10277787033SJim Ingham { 10377787033SJim Ingham if (log) 10477787033SJim Ingham log->Printf ("Setting up ThreadPlanCallFunction, failed to checkpoint thread state."); 10530fdc8d8SChris Lattner return; 10677787033SJim Ingham } 10777787033SJim Ingham // Now set the thread state to "no reason" so we don't run with whatever signal was outstanding... 10877787033SJim Ingham thread.SetStopInfoToNothing(); 10930fdc8d8SChris Lattner 11030fdc8d8SChris Lattner m_function_addr = function; 111f5e56de0SGreg Clayton lldb::addr_t FunctionLoadAddr = m_function_addr.GetLoadAddress(&target); 11230fdc8d8SChris Lattner 113*fdeb1563SGreg Clayton if (this_arg && cmd_arg) 114*fdeb1563SGreg Clayton { 11530fdc8d8SChris Lattner if (!abi->PrepareTrivialCall (thread, 116e359d9b7SSean Callanan m_function_sp, 11730fdc8d8SChris Lattner FunctionLoadAddr, 11830fdc8d8SChris Lattner StartLoadAddr, 11917827830SSean Callanan this_arg, 120*fdeb1563SGreg Clayton cmd_arg, 121*fdeb1563SGreg Clayton &m_arg_addr)) 12230fdc8d8SChris Lattner return; 123*fdeb1563SGreg Clayton } 124*fdeb1563SGreg Clayton else if (this_arg) 125*fdeb1563SGreg Clayton { 126*fdeb1563SGreg Clayton if (!abi->PrepareTrivialCall (thread, 127*fdeb1563SGreg Clayton m_function_sp, 128*fdeb1563SGreg Clayton FunctionLoadAddr, 129*fdeb1563SGreg Clayton StartLoadAddr, 130*fdeb1563SGreg Clayton this_arg, 131*fdeb1563SGreg Clayton &m_arg_addr, 132*fdeb1563SGreg Clayton NULL)) 133*fdeb1563SGreg Clayton return; 134*fdeb1563SGreg Clayton } 135*fdeb1563SGreg Clayton else 136*fdeb1563SGreg Clayton { 137*fdeb1563SGreg Clayton if (!abi->PrepareTrivialCall (thread, 138*fdeb1563SGreg Clayton m_function_sp, 139*fdeb1563SGreg Clayton FunctionLoadAddr, 140*fdeb1563SGreg Clayton StartLoadAddr, 141*fdeb1563SGreg Clayton &m_arg_addr, 142*fdeb1563SGreg Clayton NULL, 143*fdeb1563SGreg Clayton NULL)) 144*fdeb1563SGreg Clayton return; 145*fdeb1563SGreg Clayton } 14630fdc8d8SChris Lattner 1479da3683cSJim Ingham ReportRegisterState ("Function call was set up. Register state was:"); 1489da3683cSJim Ingham 1499da3683cSJim Ingham m_valid = true; 1509da3683cSJim Ingham } 1519da3683cSJim Ingham 1529da3683cSJim Ingham ThreadPlanCallFunction::~ThreadPlanCallFunction () 1539da3683cSJim Ingham { 1549da3683cSJim Ingham } 1559da3683cSJim Ingham 1569da3683cSJim Ingham void 1579da3683cSJim Ingham ThreadPlanCallFunction::ReportRegisterState (const char *message) 1589da3683cSJim Ingham { 1599da3683cSJim Ingham LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); 160ece96492SSean Callanan if (log) 161ece96492SSean Callanan { 1625ccbd294SGreg Clayton RegisterContext *reg_ctx = m_thread.GetRegisterContext().get(); 163ece96492SSean Callanan 1649da3683cSJim Ingham log->PutCString(message); 165ece96492SSean Callanan 166ece96492SSean Callanan for (uint32_t register_index = 0, num_registers = reg_ctx->GetRegisterCount(); 167ece96492SSean Callanan register_index < num_registers; 168ece96492SSean Callanan ++register_index) 169ece96492SSean Callanan { 170ece96492SSean Callanan const char *register_name = reg_ctx->GetRegisterName(register_index); 171ece96492SSean Callanan uint64_t register_value = reg_ctx->ReadRegisterAsUnsigned(register_index, LLDB_INVALID_ADDRESS); 172ece96492SSean Callanan 173ece96492SSean Callanan log->Printf(" %s = 0x%llx", register_name, register_value); 174ece96492SSean Callanan } 175ece96492SSean Callanan } 17610af7c43SSean Callanan } 17710af7c43SSean Callanan 17810af7c43SSean Callanan void 17910af7c43SSean Callanan ThreadPlanCallFunction::DoTakedown () 18010af7c43SSean Callanan { 1819da3683cSJim Ingham LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); 1829da3683cSJim Ingham if (!m_takedown_done) 18377787033SJim Ingham { 1849da3683cSJim Ingham if (log) 1859da3683cSJim Ingham log->Printf ("DoTakedown called for thread 0x%4.4x, m_valid: %d complete: %d.\n", m_thread.GetID(), m_valid, IsPlanComplete()); 1869da3683cSJim Ingham m_takedown_done = true; 18777787033SJim Ingham m_thread.RestoreThreadStateFromCheckpoint(m_stored_thread_state); 18810af7c43SSean Callanan SetPlanComplete(); 18910af7c43SSean Callanan ClearBreakpoints(); 1909da3683cSJim Ingham if (log && log->GetVerbose()) 1919da3683cSJim Ingham ReportRegisterState ("Restoring thread state after function call. Restored register state:"); 1922c36439cSJim Ingham 1939da3683cSJim Ingham } 1949da3683cSJim Ingham else 1959da3683cSJim Ingham { 1969da3683cSJim Ingham if (log) 1979da3683cSJim Ingham log->Printf ("DoTakedown called as no-op for thread 0x%4.4x, m_valid: %d complete: %d.\n", m_thread.GetID(), m_valid, IsPlanComplete()); 19830fdc8d8SChris Lattner } 19977787033SJim Ingham } 20030fdc8d8SChris Lattner 20130fdc8d8SChris Lattner void 202bda4e5ebSJim Ingham ThreadPlanCallFunction::WillPop () 203bda4e5ebSJim Ingham { 204bda4e5ebSJim Ingham DoTakedown(); 205bda4e5ebSJim Ingham } 206bda4e5ebSJim Ingham 207bda4e5ebSJim Ingham void 20830fdc8d8SChris Lattner ThreadPlanCallFunction::GetDescription (Stream *s, lldb::DescriptionLevel level) 20930fdc8d8SChris Lattner { 21030fdc8d8SChris Lattner if (level == lldb::eDescriptionLevelBrief) 21130fdc8d8SChris Lattner { 21230fdc8d8SChris Lattner s->Printf("Function call thread plan"); 21330fdc8d8SChris Lattner } 21430fdc8d8SChris Lattner else 21530fdc8d8SChris Lattner { 21630fdc8d8SChris Lattner if (m_args) 217f5e56de0SGreg Clayton s->Printf("Thread plan to call 0x%llx with parsed arguments", m_function_addr.GetLoadAddress(&m_process.GetTarget()), m_arg_addr); 21830fdc8d8SChris Lattner else 219f5e56de0SGreg Clayton s->Printf("Thread plan to call 0x%llx void * argument at: 0x%llx", m_function_addr.GetLoadAddress(&m_process.GetTarget()), m_arg_addr); 22030fdc8d8SChris Lattner } 22130fdc8d8SChris Lattner } 22230fdc8d8SChris Lattner 22330fdc8d8SChris Lattner bool 22430fdc8d8SChris Lattner ThreadPlanCallFunction::ValidatePlan (Stream *error) 22530fdc8d8SChris Lattner { 22630fdc8d8SChris Lattner if (!m_valid) 22730fdc8d8SChris Lattner return false; 22830fdc8d8SChris Lattner 22930fdc8d8SChris Lattner return true; 23030fdc8d8SChris Lattner } 23130fdc8d8SChris Lattner 23230fdc8d8SChris Lattner bool 23330fdc8d8SChris Lattner ThreadPlanCallFunction::PlanExplainsStop () 23430fdc8d8SChris Lattner { 23540d871faSJim Ingham // If our subplan knows why we stopped, even if it's done (which would forward the question to us) 23640d871faSJim Ingham // we answer yes. 23740d871faSJim Ingham if(m_subplan_sp.get() != NULL && m_subplan_sp->PlanExplainsStop()) 23840d871faSJim Ingham return true; 2393e6fedcaSSean Callanan 240c98aca60SSean Callanan // Check if the breakpoint is one of ours. 241c98aca60SSean Callanan 242c98aca60SSean Callanan if (BreakpointsExplainStop()) 243c98aca60SSean Callanan return true; 244c98aca60SSean Callanan 24540d871faSJim Ingham // If we don't want to discard this plan, than any stop we don't understand should be propagated up the stack. 24640d871faSJim Ingham if (!OkayToDiscard()) 24740d871faSJim Ingham return false; 24840d871faSJim Ingham 24940d871faSJim Ingham // Otherwise, check the case where we stopped for an internal breakpoint, in that case, continue on. 25040d871faSJim Ingham // If it is not an internal breakpoint, consult OkayToDiscard. 25140d871faSJim Ingham lldb::StopInfoSP stop_info_sp = GetPrivateStopReason(); 2526db73ca5SSean Callanan 25340d871faSJim Ingham if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint) 25440d871faSJim Ingham { 25540d871faSJim Ingham uint64_t break_site_id = stop_info_sp->GetValue(); 25640d871faSJim Ingham lldb::BreakpointSiteSP bp_site_sp = m_thread.GetProcess().GetBreakpointSiteList().FindByID(break_site_id); 25740d871faSJim Ingham if (bp_site_sp) 25840d871faSJim Ingham { 25940d871faSJim Ingham uint32_t num_owners = bp_site_sp->GetNumberOfOwners(); 26040d871faSJim Ingham bool is_internal = true; 26140d871faSJim Ingham for (uint32_t i = 0; i < num_owners; i++) 26240d871faSJim Ingham { 2636db73ca5SSean Callanan Breakpoint &bp = bp_site_sp->GetOwnerAtIndex(i)->GetBreakpoint(); 2646db73ca5SSean Callanan 2656db73ca5SSean Callanan if (!bp.IsInternal()) 26640d871faSJim Ingham { 26740d871faSJim Ingham is_internal = false; 26840d871faSJim Ingham break; 26940d871faSJim Ingham } 27040d871faSJim Ingham } 27140d871faSJim Ingham if (is_internal) 27240d871faSJim Ingham return false; 27340d871faSJim Ingham } 27440d871faSJim Ingham 27540d871faSJim Ingham return OkayToDiscard(); 27640d871faSJim Ingham } 27740d871faSJim Ingham else 27840d871faSJim Ingham { 27940d871faSJim Ingham // If the subplan is running, any crashes are attributable to us. 2802c36439cSJim Ingham // If we want to discard the plan, then we say we explain the stop 2812c36439cSJim Ingham // but if we are going to be discarded, let whoever is above us 2822c36439cSJim Ingham // explain the stop. 2832c36439cSJim Ingham return ((m_subplan_sp.get() != NULL) && !OkayToDiscard()); 28430fdc8d8SChris Lattner } 28540d871faSJim Ingham } 28630fdc8d8SChris Lattner 28730fdc8d8SChris Lattner bool 28830fdc8d8SChris Lattner ThreadPlanCallFunction::ShouldStop (Event *event_ptr) 28930fdc8d8SChris Lattner { 29030fdc8d8SChris Lattner if (PlanExplainsStop()) 29130fdc8d8SChris Lattner { 2929da3683cSJim Ingham ReportRegisterState ("Function completed. Register state was:"); 2935300d37aSSean Callanan 29410af7c43SSean Callanan DoTakedown(); 2956db73ca5SSean Callanan 29630fdc8d8SChris Lattner return true; 29730fdc8d8SChris Lattner } 29830fdc8d8SChris Lattner else 29930fdc8d8SChris Lattner { 30030fdc8d8SChris Lattner return false; 30130fdc8d8SChris Lattner } 30230fdc8d8SChris Lattner } 30330fdc8d8SChris Lattner 30430fdc8d8SChris Lattner bool 30530fdc8d8SChris Lattner ThreadPlanCallFunction::StopOthers () 30630fdc8d8SChris Lattner { 30730fdc8d8SChris Lattner return m_stop_other_threads; 30830fdc8d8SChris Lattner } 30930fdc8d8SChris Lattner 31030fdc8d8SChris Lattner void 31130fdc8d8SChris Lattner ThreadPlanCallFunction::SetStopOthers (bool new_value) 31230fdc8d8SChris Lattner { 31330fdc8d8SChris Lattner if (m_subplan_sp) 31430fdc8d8SChris Lattner { 31530fdc8d8SChris Lattner ThreadPlanRunToAddress *address_plan = static_cast<ThreadPlanRunToAddress *>(m_subplan_sp.get()); 31630fdc8d8SChris Lattner address_plan->SetStopOthers(new_value); 31730fdc8d8SChris Lattner } 31830fdc8d8SChris Lattner m_stop_other_threads = new_value; 31930fdc8d8SChris Lattner } 32030fdc8d8SChris Lattner 32130fdc8d8SChris Lattner StateType 32206e827ccSJim Ingham ThreadPlanCallFunction::GetPlanRunState () 32330fdc8d8SChris Lattner { 32430fdc8d8SChris Lattner return eStateRunning; 32530fdc8d8SChris Lattner } 32630fdc8d8SChris Lattner 32730fdc8d8SChris Lattner void 32830fdc8d8SChris Lattner ThreadPlanCallFunction::DidPush () 32930fdc8d8SChris Lattner { 330be3a1b14SSean Callanan //#define SINGLE_STEP_EXPRESSIONS 331be3a1b14SSean Callanan 332be3a1b14SSean Callanan #ifndef SINGLE_STEP_EXPRESSIONS 33330fdc8d8SChris Lattner m_subplan_sp.reset(new ThreadPlanRunToAddress(m_thread, m_start_addr, m_stop_other_threads)); 33430fdc8d8SChris Lattner 33530fdc8d8SChris Lattner m_thread.QueueThreadPlan(m_subplan_sp, false); 33677787033SJim Ingham m_subplan_sp->SetPrivate (true); 337be3a1b14SSean Callanan #endif 33830fdc8d8SChris Lattner } 33930fdc8d8SChris Lattner 34030fdc8d8SChris Lattner bool 34130fdc8d8SChris Lattner ThreadPlanCallFunction::WillStop () 34230fdc8d8SChris Lattner { 34330fdc8d8SChris Lattner return true; 34430fdc8d8SChris Lattner } 34530fdc8d8SChris Lattner 34630fdc8d8SChris Lattner bool 34730fdc8d8SChris Lattner ThreadPlanCallFunction::MischiefManaged () 34830fdc8d8SChris Lattner { 34930fdc8d8SChris Lattner if (IsPlanComplete()) 35030fdc8d8SChris Lattner { 3512d4edfbcSGreg Clayton LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); 35230fdc8d8SChris Lattner 35330fdc8d8SChris Lattner if (log) 35430fdc8d8SChris Lattner log->Printf("Completed call function plan."); 35530fdc8d8SChris Lattner 35630fdc8d8SChris Lattner ThreadPlan::MischiefManaged (); 35730fdc8d8SChris Lattner return true; 35830fdc8d8SChris Lattner } 35930fdc8d8SChris Lattner else 36030fdc8d8SChris Lattner { 36130fdc8d8SChris Lattner return false; 36230fdc8d8SChris Lattner } 36330fdc8d8SChris Lattner } 3646db73ca5SSean Callanan 3656db73ca5SSean Callanan void 3666db73ca5SSean Callanan ThreadPlanCallFunction::SetBreakpoints () 3676db73ca5SSean Callanan { 368f211510fSSean Callanan m_cxx_language_runtime = m_process.GetLanguageRuntime(eLanguageTypeC_plus_plus); 369f211510fSSean Callanan m_objc_language_runtime = m_process.GetLanguageRuntime(eLanguageTypeObjC); 3706db73ca5SSean Callanan 371f211510fSSean Callanan if (m_cxx_language_runtime) 372f211510fSSean Callanan m_cxx_language_runtime->SetExceptionBreakpoints(); 373f211510fSSean Callanan if (m_objc_language_runtime) 374f211510fSSean Callanan m_objc_language_runtime->SetExceptionBreakpoints(); 3756db73ca5SSean Callanan } 3766db73ca5SSean Callanan 3776db73ca5SSean Callanan void 3786db73ca5SSean Callanan ThreadPlanCallFunction::ClearBreakpoints () 3796db73ca5SSean Callanan { 380f211510fSSean Callanan if (m_cxx_language_runtime) 381f211510fSSean Callanan m_cxx_language_runtime->ClearExceptionBreakpoints(); 382f211510fSSean Callanan if (m_objc_language_runtime) 383f211510fSSean Callanan m_objc_language_runtime->ClearExceptionBreakpoints(); 3846db73ca5SSean Callanan } 385c98aca60SSean Callanan 386c98aca60SSean Callanan bool 387c98aca60SSean Callanan ThreadPlanCallFunction::BreakpointsExplainStop() 388c98aca60SSean Callanan { 389c98aca60SSean Callanan lldb::StopInfoSP stop_info_sp = GetPrivateStopReason(); 390c98aca60SSean Callanan 391f211510fSSean Callanan if (m_cxx_language_runtime && 392f211510fSSean Callanan m_cxx_language_runtime->ExceptionBreakpointsExplainStop(stop_info_sp)) 393c98aca60SSean Callanan return true; 394f211510fSSean Callanan 395f211510fSSean Callanan if (m_objc_language_runtime && 396f211510fSSean Callanan m_objc_language_runtime->ExceptionBreakpointsExplainStop(stop_info_sp)) 397f211510fSSean Callanan return true; 398c98aca60SSean Callanan 399c98aca60SSean Callanan return false; 400c98aca60SSean Callanan } 401