// Copyright © 2009 The Go Authors. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #define __DARWIN_UNIX03 0 #include #include #include #include #include #include #include #include #include #define Ureg Ureg32 #include #undef Ureg #define Ureg Ureg64 #include #undef Ureg #undef waitpid /* want Unix waitpid, not Plan 9 */ typedef struct Ureg32 Ureg32; typedef struct Ureg64 Ureg64; extern mach_port_t mach_reply_port(void); // should be in system headers, is not // Mach-error wrapper. // Takes a mach return code and converts it into 0 / -1, // setting errstr when it returns -1. static struct { int code; char *name; } macherr[] = { KERN_INVALID_ADDRESS, "invalid address", KERN_PROTECTION_FAILURE, "protection failure", KERN_NO_SPACE, "no space", KERN_INVALID_ARGUMENT, "invalid argument", KERN_FAILURE, "failure", KERN_RESOURCE_SHORTAGE, "resource shortage", KERN_NOT_RECEIVER, "not receiver", KERN_NO_ACCESS, "no access", KERN_MEMORY_FAILURE, "memory failure", KERN_MEMORY_ERROR, "memory error", KERN_ALREADY_IN_SET, "already in set", KERN_NOT_IN_SET, "not in set", KERN_NAME_EXISTS, "name exists", KERN_ABORTED, "aborted", KERN_INVALID_NAME, "invalid name", KERN_INVALID_TASK, "invalid task", KERN_INVALID_RIGHT, "invalid right", KERN_INVALID_VALUE, "invalid value", KERN_UREFS_OVERFLOW, "urefs overflow", KERN_INVALID_CAPABILITY, "invalid capability", KERN_RIGHT_EXISTS, "right exists", KERN_INVALID_HOST, "invalid host", KERN_MEMORY_PRESENT, "memory present", KERN_MEMORY_DATA_MOVED, "memory data moved", KERN_MEMORY_RESTART_COPY, "memory restart copy", KERN_INVALID_PROCESSOR_SET, "invalid processor set", KERN_POLICY_LIMIT, "policy limit", KERN_INVALID_POLICY, "invalid policy", KERN_INVALID_OBJECT, "invalid object", KERN_ALREADY_WAITING, "already waiting", KERN_DEFAULT_SET, "default set", KERN_EXCEPTION_PROTECTED, "exception protected", KERN_INVALID_LEDGER, "invalid ledger", KERN_INVALID_MEMORY_CONTROL, "invalid memory control", KERN_INVALID_SECURITY, "invalid security", KERN_NOT_DEPRESSED, "not depressed", KERN_TERMINATED, "terminated", KERN_LOCK_SET_DESTROYED, "lock set destroyed", KERN_LOCK_UNSTABLE, "lock unstable", KERN_LOCK_OWNED, "lock owned", KERN_LOCK_OWNED_SELF, "lock owned self", KERN_SEMAPHORE_DESTROYED, "semaphore destroyed", KERN_RPC_SERVER_TERMINATED, "rpc server terminated", KERN_RPC_TERMINATE_ORPHAN, "rpc terminate orphan", KERN_RPC_CONTINUE_ORPHAN, "rpc continue orphan", KERN_NOT_SUPPORTED, "not supported", KERN_NODE_DOWN, "node down", KERN_NOT_WAITING, "not waiting", KERN_OPERATION_TIMED_OUT, "operation timed out", KERN_RETURN_MAX, "return max", MACH_SEND_IN_PROGRESS, "send in progress", MACH_SEND_INVALID_DATA, "send invalid data", MACH_SEND_INVALID_DEST, "send invalid dest", MACH_SEND_TIMED_OUT, "send timed out", MACH_SEND_INTERRUPTED, "send interrupted", MACH_SEND_MSG_TOO_SMALL, "send msg too small", MACH_SEND_INVALID_REPLY, "send invalid reply", MACH_SEND_INVALID_RIGHT, "send invalid right", MACH_SEND_INVALID_NOTIFY, "send invalid notify", MACH_SEND_INVALID_MEMORY, "send invalid memory", MACH_SEND_NO_BUFFER, "send no buffer", MACH_SEND_TOO_LARGE, "send too large", MACH_SEND_INVALID_TYPE, "send invalid type", MACH_SEND_INVALID_HEADER, "send invalid header", MACH_SEND_INVALID_TRAILER, "send invalid trailer", MACH_SEND_INVALID_RT_OOL_SIZE, "send invalid rt ool size", MACH_RCV_IN_PROGRESS, "rcv in progress", MACH_RCV_INVALID_NAME, "rcv invalid name", MACH_RCV_TIMED_OUT, "rcv timed out", MACH_RCV_TOO_LARGE, "rcv too large", MACH_RCV_INTERRUPTED, "rcv interrupted", MACH_RCV_PORT_CHANGED, "rcv port changed", MACH_RCV_INVALID_NOTIFY, "rcv invalid notify", MACH_RCV_INVALID_DATA, "rcv invalid data", MACH_RCV_PORT_DIED, "rcv port died", MACH_RCV_IN_SET, "rcv in set", MACH_RCV_HEADER_ERROR, "rcv header error", MACH_RCV_BODY_ERROR, "rcv body error", MACH_RCV_INVALID_TYPE, "rcv invalid type", MACH_RCV_SCATTER_SMALL, "rcv scatter small", MACH_RCV_INVALID_TRAILER, "rcv invalid trailer", MACH_RCV_IN_PROGRESS_TIMED, "rcv in progress timed", MIG_TYPE_ERROR, "mig type error", MIG_REPLY_MISMATCH, "mig reply mismatch", MIG_REMOTE_ERROR, "mig remote error", MIG_BAD_ID, "mig bad id", MIG_BAD_ARGUMENTS, "mig bad arguments", MIG_NO_REPLY, "mig no reply", MIG_EXCEPTION, "mig exception", MIG_ARRAY_TOO_LARGE, "mig array too large", MIG_SERVER_DIED, "server died", MIG_TRAILER_ERROR, "trailer has an unknown format", }; static int me(kern_return_t r) { int i; if(r == 0) return 0; for(i=0; i= nelem(thr)) return nil; // TODO: We probably should save the old thread exception // ports for each bit and then put them back when we exit. // Probably the BSD signal handlers have put stuff there. mach_port_insert_right(mach_task_self(), excport, excport, MACH_MSG_TYPE_MAKE_SEND); if(me(thread_set_exception_ports(thread[j], ExcMask, excport, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE)) < 0){ fprint(2, "warning: cannot set excport: %r\n"); } thr[nthr].pid = pid; thr[nthr].task = task; thr[nthr].thread = thread[j]; if(ret == nil) ret = &thr[nthr]; nthr++; skip:; } return ret; } static Thread* idtotable(int id) { if(id >= 0) return addpid(id, 1); id = -(id+1); if(id >= nthr) return nil; return &thr[id]; } /* static int idtopid(int id) { Thread *t; if((t = idtotable(id)) == nil) return -1; return t->pid; } */ static mach_port_t idtotask(int id) { Thread *t; if((t = idtotable(id)) == nil) return -1; return t->task; } static mach_port_t idtothread(int id) { Thread *t; if((t = idtotable(id)) == nil) return -1; return t->thread; } static int machsegrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr); static int machregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr); Map* attachproc(int id, Fhdr *fp) { Thread *t; Map *map; if((t = idtotable(id)) == nil) return nil; if(t->map) return t->map; map = newmap(0, 4); if(!map) return nil; map->pid = -((t-thr) + 1); if(mach->regsize) setmap(map, -1, 0, mach->regsize, 0, "regs", machregrw); setmap(map, -1, fp->txtaddr, fp->txtaddr+fp->txtsz, fp->txtaddr, "*text", machsegrw); setmap(map, -1, fp->dataddr, mach->utop, fp->dataddr, "*data", machsegrw); t->map = map; return map; } // Return list of ids for threads in id. int procthreadpids(int id, int *out, int nout) { Thread *t; int i, n, pid; t = idtotable(id); if(t == nil) return -1; pid = t->pid; addpid(pid, 1); // force refresh of thread list n = 0; for(i=0; ipid); if(task == -1) return -1; if(isr){ vm_size_t nn; nn = n; if(me(vm_read_overwrite(task, addr, n, (uintptr)v, &nn)) < 0) return -1; return nn; }else{ r = vm_write(task, addr, (uintptr)v, n); if(r == KERN_INVALID_ADDRESS){ // Happens when writing to text segment. // Change protections. if(me(vm_protect(task, addr, n, 0, VM_PROT_WRITE|VM_PROT_READ|VM_PROT_EXECUTE)) < 0){ fprint(2, "vm_protect: %s\n", r); return -1; } r = vm_write(task, addr, (uintptr)v, n); } if(r != 0){ me(r); return -1; } return n; } } // Convert Ureg offset to x86_thread_state32_t offset. static int go2darwin32(uvlong addr) { switch(addr){ case offsetof(Ureg32, ax): return offsetof(x86_thread_state32_t, eax); case offsetof(Ureg32, bx): return offsetof(x86_thread_state32_t, ebx); case offsetof(Ureg32, cx): return offsetof(x86_thread_state32_t, ecx); case offsetof(Ureg32, dx): return offsetof(x86_thread_state32_t, edx); case offsetof(Ureg32, si): return offsetof(x86_thread_state32_t, esi); case offsetof(Ureg32, di): return offsetof(x86_thread_state32_t, edi); case offsetof(Ureg32, bp): return offsetof(x86_thread_state32_t, ebp); case offsetof(Ureg32, fs): return offsetof(x86_thread_state32_t, fs); case offsetof(Ureg32, gs): return offsetof(x86_thread_state32_t, gs); case offsetof(Ureg32, pc): return offsetof(x86_thread_state32_t, eip); case offsetof(Ureg32, cs): return offsetof(x86_thread_state32_t, cs); case offsetof(Ureg32, flags): return offsetof(x86_thread_state32_t, eflags); case offsetof(Ureg32, sp): return offsetof(x86_thread_state32_t, esp); } return -1; } // Convert Ureg offset to x86_thread_state64_t offset. static int go2darwin64(uvlong addr) { switch(addr){ case offsetof(Ureg64, ax): return offsetof(x86_thread_state64_t, rax); case offsetof(Ureg64, bx): return offsetof(x86_thread_state64_t, rbx); case offsetof(Ureg64, cx): return offsetof(x86_thread_state64_t, rcx); case offsetof(Ureg64, dx): return offsetof(x86_thread_state64_t, rdx); case offsetof(Ureg64, si): return offsetof(x86_thread_state64_t, rsi); case offsetof(Ureg64, di): return offsetof(x86_thread_state64_t, rdi); case offsetof(Ureg64, bp): return offsetof(x86_thread_state64_t, rbp); case offsetof(Ureg64, r8): return offsetof(x86_thread_state64_t, r8); case offsetof(Ureg64, r9): return offsetof(x86_thread_state64_t, r9); case offsetof(Ureg64, r10): return offsetof(x86_thread_state64_t, r10); case offsetof(Ureg64, r11): return offsetof(x86_thread_state64_t, r11); case offsetof(Ureg64, r12): return offsetof(x86_thread_state64_t, r12); case offsetof(Ureg64, r13): return offsetof(x86_thread_state64_t, r13); case offsetof(Ureg64, r14): return offsetof(x86_thread_state64_t, r14); case offsetof(Ureg64, r15): return offsetof(x86_thread_state64_t, r15); case offsetof(Ureg64, fs): return offsetof(x86_thread_state64_t, fs); case offsetof(Ureg64, gs): return offsetof(x86_thread_state64_t, gs); case offsetof(Ureg64, ip): return offsetof(x86_thread_state64_t, rip); case offsetof(Ureg64, cs): return offsetof(x86_thread_state64_t, cs); case offsetof(Ureg64, flags): return offsetof(x86_thread_state64_t, rflags); case offsetof(Ureg64, sp): return offsetof(x86_thread_state64_t, rsp); } return -1; } extern Mach mi386; // Read/write from fake register segment. static int machregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr) { uint nn, count, state; mach_port_t thread; int reg; char buf[100]; union { x86_thread_state64_t reg64; x86_thread_state32_t reg32; uchar p[1]; } u; uchar *p; if(n > 8){ werrstr("asked for %d-byte register", n); return -1; } thread = idtothread(map->pid); if(thread == -1){ werrstr("no such id"); return -1; } if(mach == &mi386) { count = x86_THREAD_STATE32_COUNT; state = x86_THREAD_STATE32; if((reg = go2darwin32(addr)) < 0 || reg+n > sizeof u){ if(isr){ memset(v, 0, n); return 0; } werrstr("register %llud not available", addr); return -1; } } else { count = x86_THREAD_STATE64_COUNT; state = x86_THREAD_STATE64; if((reg = go2darwin64(addr)) < 0 || reg+n > sizeof u){ if(isr){ memset(v, 0, n); return 0; } werrstr("register %llud not available", addr); return -1; } } if(!isr && me(thread_suspend(thread)) < 0){ werrstr("thread suspend %#x: %r", thread); return -1; } nn = count; if(me(thread_get_state(thread, state, (void*)u.p, &nn)) < 0){ if(!isr) thread_resume(thread); rerrstr(buf, sizeof buf); if(strcmp(buf, "send invalid dest") == 0) werrstr("process exited"); else werrstr("thread_get_state: %r"); return -1; } p = u.p+reg; if(isr) memmove(v, p, n); else{ memmove(p, v, n); nn = count; if(me(thread_set_state(thread, state, (void*)u.p, nn)) < 0){ thread_resume(thread); werrstr("thread_set_state: %r"); return -1; } if(me(thread_resume(thread)) < 0){ werrstr("thread_resume: %r"); return -1; } } return 0; } enum { FLAGS_TF = 0x100 // x86 single-step processor flag }; // Is thread t suspended? static int threadstopped(Thread *t) { struct thread_basic_info info; uint size; size = sizeof info; if(me(thread_info(t->thread, THREAD_BASIC_INFO, (thread_info_t)&info, &size)) < 0){ fprint(2, "threadstopped thread_info %#x: %r\n"); return 1; } return info.suspend_count > 0; } // If thread t is suspended, start it up again. // If singlestep is set, only let it execute one instruction. static int threadstart(Thread *t, int singlestep) { int i; uint n; struct thread_basic_info info; if(!threadstopped(t)) return 0; // Set or clear the processor single-step flag, as appropriate. if(mach == &mi386) { x86_thread_state32_t regs; n = x86_THREAD_STATE32_COUNT; if(me(thread_get_state(t->thread, x86_THREAD_STATE32, (thread_state_t)®s, &n)) < 0) return -1; if(singlestep) regs.eflags |= FLAGS_TF; else regs.eflags &= ~FLAGS_TF; if(me(thread_set_state(t->thread, x86_THREAD_STATE32, (thread_state_t)®s, x86_THREAD_STATE32_COUNT)) < 0) return -1; } else { x86_thread_state64_t regs; n = x86_THREAD_STATE64_COUNT; if(me(thread_get_state(t->thread, x86_THREAD_STATE64, (thread_state_t)®s, &n)) < 0) return -1; if(singlestep) regs.rflags |= FLAGS_TF; else regs.rflags &= ~FLAGS_TF; if(me(thread_set_state(t->thread, x86_THREAD_STATE64, (thread_state_t)®s, x86_THREAD_STATE64_COUNT)) < 0) return -1; } // Run. n = sizeof info; if(me(thread_info(t->thread, THREAD_BASIC_INFO, (thread_info_t)&info, &n)) < 0) return -1; for(i=0; ithread)) < 0) return -1; return 0; } // Stop thread t. static int threadstop(Thread *t) { if(threadstopped(t)) return 0; if(me(thread_suspend(t->thread)) < 0) return -1; return 0; } // Callback for exc_server below. Called when a thread we are // watching has an exception like hitting a breakpoint. kern_return_t catch_exception_raise(mach_port_t eport, mach_port_t thread, mach_port_t task, exception_type_t exception, exception_data_t code, mach_msg_type_number_t ncode) { Thread *t; int i; t = nil; for(i=0; i 0) addpid(thr[0].pid, 1); for(i=0; iexc = exception; if(ncode > nelem(t->code)) ncode = nelem(t->code); memmove(t->code, code, ncode*sizeof t->code[0]); // Suspend thread, so that we can look at it & restart it later. if(me(thread_suspend(thread)) < 0) fprint(2, "catch_exception_raise thread_suspend: %r\n"); // Synchronize with waitstop below. pthread_mutex_lock(&mu); pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mu); return KERN_SUCCESS; } // Exception watching thread, started in addpid above. static void* excthread(void *v) { extern boolean_t exc_server(); mach_msg_server(exc_server, 2048, excport, 0); return 0; } // Wait for pid to exit. static int exited; static void* waitthread(void *v) { int pid, status; pid = (int)(uintptr)v; waitpid(pid, &status, 0); exited = 1; // Synchronize with waitstop below. pthread_mutex_lock(&mu); pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mu); return nil; } // Wait for thread t to stop. static int waitstop(Thread *t) { pthread_mutex_lock(&mu); while(!exited && !threadstopped(t)) pthread_cond_wait(&cond, &mu); pthread_mutex_unlock(&mu); return 0; } int ctlproc(int id, char *msg) { Thread *t; int status; // Hang/attached dance is for debugging newly exec'ed programs. // After fork, the child does ctlproc("hang") before exec, // and the parent does ctlproc("attached") and then waitstop. // Using these requires the BSD ptrace interface, unlike everything // else we do, which uses only the Mach interface. Our goal here // is to do as little as possible using ptrace and then flip over to Mach. if(strcmp(msg, "hang") == 0) return ptrace(PT_TRACE_ME, 0, 0, 0); if(strcmp(msg, "attached") == 0){ // The pid "id" has done a ctlproc "hang" and then // exec, so we should find it stoppped just before exec // of the new program. #undef waitpid if(waitpid(id, &status, WUNTRACED) < 0){ fprint(2, "ctlproc attached waitpid: %r\n"); return -1; } if(WIFEXITED(status) || !WIFSTOPPED(status)){ fprint(2, "ctlproc attached: bad process state\n"); return -1; } // Find Mach thread for pid and suspend it. t = addpid(id, 1); if(t == nil) return -1; if(me(thread_suspend(t->thread)) < 0){ fprint(2, "ctlproc attached: thread_suspend: %r\n"); return -1; } // Let ptrace tell the process to keep going: // then ptrace is out of the way and we're back in Mach land. return ptrace(PT_CONTINUE, id, (caddr_t)1, 0); } // All the other control messages require a Thread structure. if((t = idtotable(id)) == nil){ werrstr("no such thread"); return -1; } if(strcmp(msg, "kill") == 0) return ptrace(PT_KILL, t->pid, 0, 0); if(strcmp(msg, "start") == 0) return threadstart(t, 0); if(strcmp(msg, "stop") == 0) return threadstop(t); if(strcmp(msg, "startstop") == 0){ if(threadstart(t, 0) < 0) return -1; return waitstop(t); } if(strcmp(msg, "step") == 0){ if(threadstart(t, 1) < 0) return -1; return waitstop(t); } if(strcmp(msg, "waitstop") == 0) return waitstop(t); // sysstop not available on OS X werrstr("unknown control message"); return -1; } char* procstatus(int id) { Thread *t; if((t = idtotable(id)) == nil) return "gone!"; if(threadstopped(t)) return "Stopped"; return "Running"; }