--- /dev/null
+#include "test.h"
+#include <objc/objc.h>
+#include <mach/mach.h>
+#include <pthread.h>
+#define _OBJC_PRIVATE_H_
+#include <objc/objc-gdb.h>
+
+extern void _objc_flush_caches(Class cls, BOOL flushMeta);
+
+
+@interface Super { id isa; } @end
+
+@implementation Super
++(void)initialize { }
++class { return self; }
++(int)method { return 1; }
++(int)method2 { return 1; }
+@end
+
+
+semaphore_t sema;
+
+void *thread(void *arg __unused)
+{
+ objc_registerThreadWithCollector();
+
+ semaphore_signal(sema);
+ testprintf("hi\n");
+ while (1) {
+ [Super method];
+ _objc_flush_caches(0, YES);
+ }
+}
+
+
+void stopAllThreads(void)
+{
+ mach_msg_type_number_t count, i;
+ thread_act_array_t list;
+
+ task_threads(mach_task_self(), &list, &count);
+ for (i = 0; i < count; i++) {
+ if (list[i] == mach_thread_self()) continue;
+ thread_suspend(list[i]);
+ mach_port_deallocate(mach_task_self(), list[i]);
+ }
+}
+
+void startAllThreads(void)
+{
+ mach_msg_type_number_t count, i;
+ thread_act_array_t list;
+
+ task_threads(mach_task_self(), &list, &count);
+ for (i = 0; i < count; i++) {
+ if (list[i] == mach_thread_self()) continue;
+ thread_resume(list[i]);
+ mach_port_deallocate(mach_task_self(), list[i]);
+ }
+}
+
+
+static void cycle(int mode, int *good, int *bad)
+{
+ stopAllThreads();
+ if (gdb_objc_startDebuggerMode(mode)) {
+ testprintf("good\n");
+ [Super method];
+ [Super method2];
+ if (mode == OBJC_DEBUGMODE_FULL) {
+ // will crash without full write locks
+ _objc_flush_caches(0, YES);
+ }
+ gdb_objc_endDebuggerMode();
+ ++*good;
+ } else {
+ testprintf("bad\n");
+ ++*bad;
+ }
+ startAllThreads();
+ sched_yield();
+}
+
+
+int main()
+{
+#define STOPS 10000
+#define THREADS 1
+ int i;
+
+ [Super class];
+
+ testassert(STOPS > 200);
+
+ // Uncontended debugger mode
+ testassert(gdb_objc_startDebuggerMode(0));
+ gdb_objc_endDebuggerMode();
+
+ // Uncontended full debugger mode
+ testassert(gdb_objc_startDebuggerMode(OBJC_DEBUGMODE_FULL));
+ gdb_objc_endDebuggerMode();
+
+ // Nested debugger mode
+ testassert(gdb_objc_startDebuggerMode(0));
+ testassert(gdb_objc_startDebuggerMode(0));
+ gdb_objc_endDebuggerMode();
+ gdb_objc_endDebuggerMode();
+
+ // Nested full debugger mode
+ testassert(gdb_objc_startDebuggerMode(OBJC_DEBUGMODE_FULL));
+ testassert(gdb_objc_startDebuggerMode(OBJC_DEBUGMODE_FULL));
+ gdb_objc_endDebuggerMode();
+ gdb_objc_endDebuggerMode();
+
+ // Check that debugger mode sometimes works and sometimes doesn't
+ // when contending with another runtime-manipulating thread.
+
+ semaphore_create(mach_task_self(), &sema, 0, 0);
+
+ for (i = 0; i < THREADS; i++) {
+ pthread_t th;
+ pthread_create(&th, NULL, &thread, NULL);
+ semaphore_wait(sema);
+ }
+
+ testprintf("go\n");
+
+ int good0 = 0, bad0 = 0;
+ for (i = 0; i < STOPS; i++) {
+ cycle(0, &good0, &bad0);
+ }
+ testprintf("good0 %d, bad0 %d\n", good0, bad0);
+
+ int goodF = 0, badF = 0;
+ for (i = 0; i < STOPS; i++) {
+ cycle(OBJC_DEBUGMODE_FULL, &goodF, &badF);
+ }
+ testprintf("goodF %d, badF %d\n", goodF, badF);
+
+ // Require at least 1% each of good and bad.
+ // Also require more than one each (exactly one is likely
+ // a bug wherein the locks got stuck the first time).
+ // Also require that FULL worked less often.
+
+ if (good0 > STOPS/100 && bad0 > STOPS/100 && good0 > 1 && bad0 > 1 &&
+ goodF > STOPS/100 && badF > STOPS/100 && goodF > 1 && badF > 1
+#ifdef __OBJC2__
+ && good0 > goodF /* not reliable enough in old runtime */
+#endif
+ )
+ {
+ succeed(__FILE__);
+ }
+
+ fail("good0=%d/%d bad0=%d/%d goodF=%d/%d badF=%d/%d (required at least %d/%d good)",
+ good0, STOPS, bad0, STOPS, goodF, STOPS, badF, STOPS, STOPS/100, STOPS);
+}