diff options
-rw-r--r-- | lldb/include/lldb/Target/Process.h | 72 | ||||
-rw-r--r-- | lldb/lldb.xcodeproj/project.pbxproj | 2 | ||||
-rw-r--r-- | lldb/source/Expression/ClangFunction.cpp | 97 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp | 8 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h | 2 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp | 20 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h | 3 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp | 42 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h | 2 | ||||
-rw-r--r-- | lldb/source/Target/Process.cpp | 89 | ||||
-rw-r--r-- | lldb/test/foundation/main.m | 16 |
11 files changed, 244 insertions, 109 deletions
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index f8684abcf6d..07f4d3de8e0 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -273,6 +273,8 @@ public: class ProcessEventData : public EventData { + friend class Process; + public: ProcessEventData (); ProcessEventData (const lldb::ProcessSP &process, lldb::StateType state); @@ -286,13 +288,25 @@ public: GetFlavor () const; const lldb::ProcessSP & - GetProcessSP() const; - + GetProcessSP() const + { + return m_process_sp; + } lldb::StateType - GetState() const; - + GetState() const + { + return m_state; + } bool - GetRestarted () const; + GetRestarted () const + { + return m_restarted; + } + bool + GetInterrupted () const + { + return m_interrupted; + } virtual void Dump (Stream *s) const; @@ -316,20 +330,37 @@ public: SetRestartedInEvent (Event *event_ptr, bool new_value); static bool + GetInterruptedFromEvent (const Event *event_ptr); + + static void + SetInterruptedInEvent (Event *event_ptr, bool new_value); + + static bool SetUpdateStateOnRemoval (Event *event_ptr); private: void - SetUpdateStateOnRemoval(); - + SetUpdateStateOnRemoval() + { + m_update_state = true; + } void - SetRestarted (bool new_value); + SetRestarted (bool new_value) + { + m_restarted = new_value; + } + void + SetInterrupted (bool new_value) + { + m_interrupted = new_value; + } lldb::ProcessSP m_process_sp; lldb::StateType m_state; bool m_restarted; // For "eStateStopped" events, this is true if the target was automatically restarted. bool m_update_state; + bool m_interrupted; DISALLOW_COPY_AND_ASSIGN (ProcessEventData); }; @@ -703,7 +734,7 @@ public: /// @see Thread:Step() /// @see Thread:Suspend() //------------------------------------------------------------------ - virtual Error + Error Resume (); //------------------------------------------------------------------ @@ -711,11 +742,15 @@ public: /// /// This function is not meant to be overridden by Process /// subclasses. + /// If the process is successfully halted, a eStateStopped + /// process event with GetInterrupted will be broadcast. If false, we will + /// halt the process with no events generated by the halt. /// /// @return - /// Returns an error object. + /// Returns an error object. If the error is empty, the process is halted. + /// otherwise the halt has failed. //------------------------------------------------------------------ - virtual Error + Error Halt (); //------------------------------------------------------------------ @@ -727,7 +762,7 @@ public: /// @return /// Returns an error object. //------------------------------------------------------------------ - virtual Error + Error Detach (); //------------------------------------------------------------------ @@ -740,7 +775,7 @@ public: /// @return /// Returns an error object. //------------------------------------------------------------------ - virtual Error + Error Destroy(); //------------------------------------------------------------------ @@ -752,7 +787,7 @@ public: /// @return /// Returns an error object. //------------------------------------------------------------------ - virtual Error + Error Signal (int signal); virtual UnixSignals & @@ -972,12 +1007,19 @@ public: //------------------------------------------------------------------ /// Halts a running process. /// + /// DoHalt should consume any process events that were delivered in the + /// process of implementing the halt. + /// + /// @param[out] caused_stop + /// If true, then this Halt caused the stop, otherwise, the process was + /// already stopped. + /// /// @return /// Returns \b true if the process successfully halts, \b false /// otherwise. //------------------------------------------------------------------ virtual Error - DoHalt () = 0; + DoHalt (bool &caused_stop) = 0; //------------------------------------------------------------------ /// Called after halting a process. diff --git a/lldb/lldb.xcodeproj/project.pbxproj b/lldb/lldb.xcodeproj/project.pbxproj index 2c13f0ca302..118eae7a0c8 100644 --- a/lldb/lldb.xcodeproj/project.pbxproj +++ b/lldb/lldb.xcodeproj/project.pbxproj @@ -2942,7 +2942,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; GCC_WARN_UNUSED_VARIABLE = YES; - ONLY_ACTIVE_ARCH = NO; + ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; VALID_ARCHS = "x86_64 i386"; }; diff --git a/lldb/source/Expression/ClangFunction.cpp b/lldb/source/Expression/ClangFunction.cpp index 40cddf3699d..84b8bff9a26 100644 --- a/lldb/source/Expression/ClangFunction.cpp +++ b/lldb/source/Expression/ClangFunction.cpp @@ -26,6 +26,7 @@ #include "lldb/Expression/ClangFunction.h" #include "lldb/Symbol/Type.h" #include "lldb/Core/DataExtractor.h" +#include "lldb/Core/State.h" #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectList.h" #include "lldb/Interpreter/CommandReturnObject.h" @@ -509,18 +510,25 @@ ClangFunction::ExecuteFunction ( if (call_plan_sp == NULL) return eExecutionSetupError; -//#define SINGLE_STEP_EXPRESSIONS - -#ifdef SINGLE_STEP_EXPRESSIONS - return eExecutionInterrupted; -#else call_plan_sp->SetPrivate(true); exe_ctx.thread->QueueThreadPlan(call_plan_sp, true); -#endif + + Listener listener("ClangFunction temporary listener"); + exe_ctx.process->HijackProcessEvents(&listener); + + Error resume_error = exe_ctx.process->Resume (); + if (!resume_error.Success()) + { + errors.Printf("Error resuming inferior: \"%s\".\n", resume_error.AsCString()); + exe_ctx.process->RestoreProcessEvents(); + return eExecutionSetupError; + } // We need to call the function synchronously, so spin waiting for it to return. // If we get interrupted while executing, we're going to lose our context, and // won't be able to gather the result at this point. + // We set the timeout AFTER the resume, since the resume takes some time and we + // don't want to charge that to the timeout. TimeValue* timeout_ptr = NULL; TimeValue real_timeout; @@ -532,18 +540,7 @@ ClangFunction::ExecuteFunction ( timeout_ptr = &real_timeout; } - Listener listener("ClangFunction temporary listener"); - exe_ctx.process->HijackProcessEvents(&listener); - - Error resume_error = exe_ctx.process->Resume (); - if (!resume_error.Success()) - { - errors.Printf("Error resuming inferior: \"%s\".\n", resume_error.AsCString()); - return eExecutionSetupError; - } - lldb::LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); - while (1) { lldb::EventSP event_sp; @@ -551,12 +548,11 @@ ClangFunction::ExecuteFunction ( // Now wait for the process to stop again: bool got_event = listener.WaitForEvent (timeout_ptr, event_sp); - if (!got_event && !call_plan_sp->IsPlanComplete()) + if (!got_event) { // Right now this is the only way to tell we've timed out... // We should interrupt the process here... // Not really sure what to do if Halt fails here... - log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP); if (log) if (try_all_threads) log->Printf ("Running function with timeout: %d timed out, trying with all threads enabled.", @@ -568,45 +564,54 @@ ClangFunction::ExecuteFunction ( if (exe_ctx.process->Halt().Success()) { timeout_ptr = NULL; - - got_event = listener.WaitForEvent (timeout_ptr, event_sp); - stop_state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); - - if (stop_state == lldb::eStateInvalid) - { - errors.Printf ("Got an invalid stop state after halt."); - } - else if (stop_state != lldb::eStateStopped) - { - StreamString s; - event_sp->Dump (&s); + if (log) + log->Printf ("Halt succeeded."); - errors.Printf("Didn't get a stopped event after Halting the target, got: \"%s\"", s.GetData()); - } + // Between the time that we got the timeout and the time we halted, but target + // might have actually completed the plan. If so, we're done. Note, I call WFE here with a short + // timeout to + got_event = listener.WaitForEvent(NULL, event_sp); - if (try_all_threads) + if (got_event) { - // Between the time that we got the timeout and the time we halted, but target - // might have actually completed the plan. If so, we're done. + stop_state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + if (log) + { + log->Printf ("Stopped with event: %s", StateAsCString(stop_state)); + if (stop_state == lldb::eStateStopped && Process::ProcessEventData::GetInterruptedFromEvent(event_sp.get())) + log->Printf (" Event was the Halt interruption event."); + } + if (exe_ctx.thread->IsThreadPlanDone (call_plan_sp.get())) { + if (log) + log->Printf ("Even though we timed out, the call plan was done. Exiting wait loop."); return_value = eExecutionCompleted; break; } - - call_plan_ptr->SetStopOthers (false); - exe_ctx.process->Resume(); - continue; - } - else - { - exe_ctx.process->RestoreProcessEvents (); - return eExecutionInterrupted; + + if (try_all_threads) + { + + call_plan_ptr->SetStopOthers (false); + if (log) + log->Printf ("About to resume."); + + exe_ctx.process->Resume(); + continue; + } + else + { + exe_ctx.process->RestoreProcessEvents (); + return eExecutionInterrupted; + } } } } stop_state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + if (log) + log->Printf("Got event: %s.", StateAsCString(stop_state)); if (stop_state == lldb::eStateRunning || stop_state == lldb::eStateStepping) continue; @@ -623,7 +628,6 @@ ClangFunction::ExecuteFunction ( } else { - log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP); if (log) { StreamString s; @@ -690,7 +694,6 @@ ClangFunction::ExecuteFunction ( event_explanation = ts.GetData(); } while (0); - log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP); if (log) log->Printf("Execution interrupted: %s %s", s.GetData(), event_explanation); } diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp index d20077fb5c6..259c08072a1 100644 --- a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp @@ -674,7 +674,7 @@ ProcessMacOSX::RefreshStateAfterStop () } Error -ProcessMacOSX::DoHalt () +ProcessMacOSX::DoHalt (bool &caused_stop) { return Signal (SIGSTOP); } @@ -718,8 +718,10 @@ ProcessMacOSX::DoSIGSTOP (bool clear_all_breakpoints) // Pause the Private State Thread so it doesn't intercept the events we need to wait for. PausePrivateStateThread(); - - m_thread_list.DiscardThreadPlans(); + // I don't think this is right. Halt should just stop the process, and then whoever called halt should + // arrange whatever they need to with the thread plans. + + //m_thread_list.DiscardThreadPlans(); // First jettison all the current thread plans, since we want to make sure it // really just stops. diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h index afbd9b3f1ae..92d2c9c1653 100644 --- a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h @@ -144,7 +144,7 @@ public: DoResume (); virtual lldb_private::Error - DoHalt (); + DoHalt (bool &caused_stop); virtual lldb_private::Error WillDetach (); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp index 50a7aec4c17..ee03424e528 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -199,14 +199,14 @@ GDBRemoteCommunication::SendContinuePacketAndWaitForResponse log->Printf ("GDBRemoteCommunication::%s ()", __FUNCTION__); Mutex::Locker locker(m_sequence_mutex); - m_is_running.SetValue (true, eBroadcastNever); - // ScopedValueChanger<bool> restore_running_to_false (m_is_running, false); StateType state = eStateRunning; if (SendPacket(payload, packet_length) == 0) state = eStateInvalid; + m_is_running.SetValue (true, eBroadcastAlways); + while (state == eStateRunning) { log = ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS); @@ -856,3 +856,19 @@ GDBRemoteCommunication::DeallocateMemory (addr_t addr, uint32_t timeout_seconds) } return false; } + +bool +GDBRemoteCommunication::WaitForIsRunning (uint32_t timeout_sec) +{ + TimeValue timeout; + if (timeout_sec) + { + timeout = TimeValue::Now(); + timeout.OffsetWithSeconds (timeout_sec); + } + bool timed_out = false; + m_is_running.WaitForValueEqualTo (true, &timeout, &timed_out); + return timed_out; +} + + diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h index e5560720f4c..ac49bf271f1 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.h @@ -203,6 +203,9 @@ public: } bool + WaitForIsRunning (uint32_t timeout_sec); + + bool GetHostInfo (uint32_t timeout_seconds); bool diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 52499ba173a..fcbd523699f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -895,9 +895,15 @@ ProcessGDBRemote::WillResume () Error ProcessGDBRemote::DoResume () { + Error error; ProcessGDBRemoteLog::LogIf (GDBR_LOG_PROCESS, "ProcessGDBRemote::Resume()"); m_async_broadcaster.BroadcastEvent (eBroadcastBitAsyncContinue, new EventDataBytes (m_continue_packet.GetData(), m_continue_packet.GetSize())); - return Error(); + const uint32_t timedout_sec = 1; + if (m_gdb_comm.WaitForIsRunning (timedout_sec)) + { + error.SetErrorString("Resume timed out."); + } + return error; } size_t @@ -1115,20 +1121,47 @@ ProcessGDBRemote::RefreshStateAfterStop () } Error -ProcessGDBRemote::DoHalt () +ProcessGDBRemote::DoHalt (bool &caused_stop) { Error error; + caused_stop = false; + if (m_gdb_comm.IsRunning()) { + PausePrivateStateThread(); bool timed_out = false; Mutex::Locker locker; - if (!m_gdb_comm.SendInterrupt (locker, 2, &timed_out)) + + if (m_gdb_comm.SendInterrupt (locker, 2, &timed_out)) + { + EventSP event_sp; + TimeValue timeout_time; + timeout_time = TimeValue::Now(); + timeout_time.OffsetWithSeconds(2); + + StateType state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp); + + if (!StateIsStoppedState (state)) + { + LogSP log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS)); + if (log) + log->Printf("ProcessGDBRemote::DoHalt() failed to stop after sending interrupt"); + error.SetErrorString ("Did not get stopped event after interrupt succeeded."); + } + else + caused_stop = true; + } + else { if (timed_out) error.SetErrorString("timed out sending interrupt packet"); else error.SetErrorString("unknown error sending interrupt packet"); } + + // Resume the private state thread at this point. + ResumePrivateStateThread(); + } return error; } @@ -2082,6 +2115,9 @@ ProcessGDBRemote::AsyncThread (void *arg) if (listener.WaitForEvent (NULL, event_sp)) { const uint32_t event_type = event_sp->GetType(); + if (log) + log->Printf ("ProcessGDBRemote::%s (arg = %p, pid = %i) Got an event of type: %d...", __FUNCTION__, arg, process->GetID(), event_type); + switch (event_type) { case eBroadcastBitAsyncContinue: diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index ae79c5a48bc..1e85e9918a7 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -138,7 +138,7 @@ public: DoResume (); virtual lldb_private::Error - DoHalt (); + DoHalt (bool &caused_stop); virtual lldb_private::Error WillDetach (); diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index ea6b200f318..749ae3ceb88 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1438,9 +1438,19 @@ Process::Halt () if (error.Success()) { - error = DoHalt(); + bool caused_stop; + error = DoHalt(caused_stop); if (error.Success()) + { DidHalt(); + if (caused_stop) + { + ProcessEventData *new_data = new ProcessEventData (GetTarget().GetProcessSP(), eStateStopped); + new_data->SetInterrupted(true); + BroadcastEvent (eBroadcastBitStateChanged, new_data); + } + } + } return error; } @@ -1591,9 +1601,14 @@ Process::ShouldBroadcastEvent (Event *event_ptr) // If we are going to stop, then we always broadcast the event. // If we aren't going to stop, let the thread plans decide if we're going to report this event. // If no thread has an opinion, we don't report it. - if (state != eStateInvalid) + if (ProcessEventData::GetInterruptedFromEvent (event_ptr)) + { + if (log) + log->Printf ("Process::ShouldBroadcastEvent (%p) stopped due to an interrupt, state: %s", event_ptr, StateAsCString(state)); + return true; + } + else { - RefreshStateAfterStop (); if (m_thread_list.ShouldStop (event_ptr) == false) @@ -1610,7 +1625,7 @@ Process::ShouldBroadcastEvent (Event *event_ptr) } if (log) - log->Printf ("Process::ShouldBroadcastEvent (%p) Restarting process", event_ptr, StateAsCString(state)); + log->Printf ("Process::ShouldBroadcastEvent (%p) Restarting process from state: %s", event_ptr, StateAsCString(state)); Resume (); } else @@ -1785,7 +1800,13 @@ Process::RunPrivateStateThread () control_only = false; break; } + + log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS); + if (log) + log->Printf ("Process::%s (arg = %p, pid = %i) got a control event: %d", __FUNCTION__, this, GetID(), event_sp->GetType()); + m_private_state_control_wait.SetValue (true, eBroadcastAlways); + continue; } @@ -1799,7 +1820,13 @@ Process::RunPrivateStateThread () if (internal_state == eStateInvalid || internal_state == eStateExited || internal_state == eStateDetached ) + { + log = lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS); + if (log) + log->Printf ("Process::%s (arg = %p, pid = %i) about to exit with internal state %s...", __FUNCTION__, this, GetID(), StateAsCString(internal_state)); + break; + } } // Verify log is still enabled before attempting to write to it... @@ -1820,7 +1847,8 @@ Process::ProcessEventData::ProcessEventData () : m_process_sp (), m_state (eStateInvalid), m_restarted (false), - m_update_state (false) + m_update_state (false), + m_interrupted (false) { } @@ -1829,7 +1857,8 @@ Process::ProcessEventData::ProcessEventData (const ProcessSP &process_sp, StateT m_process_sp (process_sp), m_state (state), m_restarted (false), - m_update_state (false) + m_update_state (false), + m_interrupted (false) { } @@ -1850,30 +1879,6 @@ Process::ProcessEventData::GetFlavor () const return ProcessEventData::GetFlavorString (); } -const ProcessSP & -Process::ProcessEventData::GetProcessSP () const -{ - return m_process_sp; -} - -StateType -Process::ProcessEventData::GetState () const -{ - return m_state; -} - -bool -Process::ProcessEventData::GetRestarted () const -{ - return m_restarted; -} - -void -Process::ProcessEventData::SetRestarted (bool new_value) -{ - m_restarted = new_value; -} - void Process::ProcessEventData::DoOnRemoval (Event *event_ptr) { @@ -1974,6 +1979,24 @@ Process::ProcessEventData::SetRestartedInEvent (Event *event_ptr, bool new_value } bool +Process::ProcessEventData::GetInterruptedFromEvent (const Event *event_ptr) +{ + const ProcessEventData *data = GetEventDataFromEvent (event_ptr); + if (data == NULL) + return false; + else + return data->GetInterrupted (); +} + +void +Process::ProcessEventData::SetInterruptedInEvent (Event *event_ptr, bool new_value) +{ + ProcessEventData *data = const_cast<ProcessEventData *>(GetEventDataFromEvent (event_ptr)); + if (data != NULL) + data->SetInterrupted(new_value); +} + +bool Process::ProcessEventData::SetUpdateStateOnRemoval (Event *event_ptr) { ProcessEventData *data = const_cast<ProcessEventData *>(GetEventDataFromEvent (event_ptr)); @@ -1985,12 +2008,6 @@ Process::ProcessEventData::SetUpdateStateOnRemoval (Event *event_ptr) return false; } -void -Process::ProcessEventData::SetUpdateStateOnRemoval() -{ - m_update_state = true; -} - Target * Process::CalculateTarget () { diff --git a/lldb/test/foundation/main.m b/lldb/test/foundation/main.m index dff08b2cea6..4b798536597 100644 --- a/lldb/test/foundation/main.m +++ b/lldb/test/foundation/main.m @@ -1,13 +1,20 @@ #import <Foundation/Foundation.h> +#include <unistd.h> @interface MyString : NSObject { NSString *str; NSDate *date; + BOOL _desc_pauses; } + +@property BOOL descriptionPauses; + - (id)initWithNSString:(NSString *)string; @end @implementation MyString +@synthesize descriptionPauses = _desc_pauses; + - (id)initWithNSString:(NSString *)string { if (self = [super init]) @@ -15,6 +22,7 @@ str = [NSString stringWithString:string]; date = [NSDate date]; } + self.descriptionPauses = NO; return self; } @@ -27,6 +35,12 @@ - (NSString *)description { + if (self.descriptionPauses) + { + printf ("\nAbout to sleep.\n"); + usleep(100000); + } + return [str stringByAppendingFormat:@" with timestamp: %@", date]; } @end @@ -40,6 +54,8 @@ int main (int argc, char const *argv[]) MyString *my = [[MyString alloc] initWithNSString:str]; NSLog(@"MyString instance: %@", [my description]); + my.descriptionPauses = YES; + id str_id = str; // Set break point at this line. SEL sel = @selector(length); BOOL responds = [str respondsToSelector:sel]; |