From 93314be3c2c80efc88ae4c607318dc13980d3aa1 Mon Sep 17 00:00:00 2001 From: Jini George Date: Wed, 27 Jun 2018 20:04:31 +0530 Subject: [PATCH] 8189429: SA: MacOSX: Replace the deprecated PT_ATTACH with PT_ATTACHEXC Avoid the waitpid() and receive, handle and reply to the incoming Mach exception message obtained with PT_ATTACHEXC. Reviewed-by: sballal, erikj, gziemski, rwestberg, dholmes, dcubed, poonam, dsamersoff --- make/RunTestsPrebuiltSpec.gmk | 3 +- make/autoconf/basics.m4 | 1 + make/autoconf/spec.gmk.in | 1 + make/gensrc/Gensrc-jdk.hotspot.agent.gmk | 17 +- make/lib/Lib-jdk.hotspot.agent.gmk | 2 + .../native/libsaproc/MacosxDebuggerLocal.m | 417 +++++++++++++----- 6 files changed, 330 insertions(+), 111 deletions(-) diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk index 75a8f64de05..fd76562e7e1 100644 --- a/make/RunTestsPrebuiltSpec.gmk +++ b/make/RunTestsPrebuiltSpec.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -147,6 +147,7 @@ GZIP := gzip HEAD := head LS := ls LN := ln +MIG := mig MKDIR := mkdir MV := mv NAWK := nawk diff --git a/make/autoconf/basics.m4 b/make/autoconf/basics.m4 index 2bf72ad4d10..3f9994b8324 100644 --- a/make/autoconf/basics.m4 +++ b/make/autoconf/basics.m4 @@ -1208,6 +1208,7 @@ AC_DEFUN_ONCE([BASIC_SETUP_COMPLEX_TOOLS], if test "x$OPENJDK_TARGET_OS" = "xmacosx"; then BASIC_REQUIRE_PROGS(DSYMUTIL, dsymutil) + BASIC_REQUIRE_PROGS(MIG, mig) BASIC_REQUIRE_PROGS(XATTR, xattr) BASIC_PATH_PROGS(CODESIGN, codesign) if test "x$CODESIGN" != "x"; then diff --git a/make/autoconf/spec.gmk.in b/make/autoconf/spec.gmk.in index ec45949e9cb..38ab871bd2a 100644 --- a/make/autoconf/spec.gmk.in +++ b/make/autoconf/spec.gmk.in @@ -675,6 +675,7 @@ GZIP:=@GZIP@ HEAD:=@HEAD@ LS:=@LS@ LN:=@LN@ +MIG:=@MIG@ MKDIR:=@MKDIR@ MV:=@MV@ NAWK:=@NAWK@ diff --git a/make/gensrc/Gensrc-jdk.hotspot.agent.gmk b/make/gensrc/Gensrc-jdk.hotspot.agent.gmk index 9196727d55f..3b272f893b2 100644 --- a/make/gensrc/Gensrc-jdk.hotspot.agent.gmk +++ b/make/gensrc/Gensrc-jdk.hotspot.agent.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,21 @@ TARGETS += $(SA_PROPERTIES) ################################################################################ +ifeq ($(OPENJDK_TARGET_OS), macosx) + MIG_OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/gensrc/jdk.hotspot.agent + MACH_EXC_HEADER := $(MIG_OUTPUT_DIR)/mach_exc.h + MACH_EXC_USER := $(MIG_OUTPUT_DIR)/mach_excUser.c + MACH_EXC_SERVER := $(MIG_OUTPUT_DIR)/mach_excServer.c + + $(MACH_EXC_SERVER): $(SDKROOT)/usr/include/mach/mach_exc.defs + $(MIG) -isysroot $(SDKROOT) -server $@ -user $(MACH_EXC_USER) \ + -header $(MACH_EXC_HEADER) $(SDKROOT)/usr/include/mach/mach_exc.defs + + TARGETS += $(MACH_EXC_SERVER) +endif + +################################################################################ + all: $(TARGETS) .PHONY: all default diff --git a/make/lib/Lib-jdk.hotspot.agent.gmk b/make/lib/Lib-jdk.hotspot.agent.gmk index 2f805c438e8..f198f5eeaf4 100644 --- a/make/lib/Lib-jdk.hotspot.agent.gmk +++ b/make/lib/Lib-jdk.hotspot.agent.gmk @@ -38,6 +38,7 @@ else ifeq ($(OPENJDK_TARGET_OS), solaris) else ifeq ($(OPENJDK_TARGET_OS), macosx) SA_CFLAGS := -Damd64 -D_GNU_SOURCE -mno-omit-leaf-frame-pointer \ -mstack-alignment=16 -fPIC + LIBSA_EXTRA_SRC := $(SUPPORT_OUTPUTDIR)/gensrc/jdk.hotspot.agent else ifeq ($(OPENJDK_TARGET_OS), windows) SA_CFLAGS := -D_WINDOWS -D_DEBUG -D_CONSOLE -D_MBCS -EHsc ifeq ($(OPENJDK_TARGET_CPU), x86_64) @@ -57,6 +58,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBSA, \ DISABLED_WARNINGS_CXX_solstudio := truncwarn unknownpragma, \ CFLAGS := $(CFLAGS_JDKLIB) $(SA_CFLAGS), \ CXXFLAGS := $(CXXFLAGS_JDKLIB) $(SA_CFLAGS) $(SA_CXXFLAGS), \ + EXTRA_SRC := $(LIBSA_EXTRA_SRC), \ LDFLAGS := $(LDFLAGS_JDKLIB) $(SA_LDFLAGS), \ LIBS_linux := -lthread_db $(LIBDL), \ LIBS_solaris := -ldl -ldemangle -lthread -lproc, \ diff --git a/src/jdk.hotspot.agent/macosx/native/libsaproc/MacosxDebuggerLocal.m b/src/jdk.hotspot.agent/macosx/native/libsaproc/MacosxDebuggerLocal.m index c05a29d3e76..e2fa2fc47b5 100644 --- a/src/jdk.hotspot.agent/macosx/native/libsaproc/MacosxDebuggerLocal.m +++ b/src/jdk.hotspot.agent/macosx/native/libsaproc/MacosxDebuggerLocal.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ #import #import #import +#import #import #import #import @@ -69,6 +70,63 @@ static jmethodID getJavaThreadsInfo_ID = 0; // indicator if thread id (lwpid_t) was set static bool _threads_filled = false; +// mach_exc_server defined in the generated mach_excServer.c +extern boolean_t mach_exc_server(mach_msg_header_t *input_msg_hdr, + mach_msg_header_t *output_msg_hdr); + +kern_return_t catch_mach_exception_raise( + mach_port_t exception_port, mach_port_t thread, + mach_port_t task, exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t code_cnt); + +kern_return_t catch_mach_exception_raise_state( + mach_port_t exception_port, exception_type_t exception, + const mach_exception_data_t code, mach_msg_type_number_t code_cnt, + int *flavor, const thread_state_t old_state, + mach_msg_type_number_t old_state_cnt, thread_state_t new_state, + mach_msg_type_number_t *new_state_cnt); + +kern_return_t catch_mach_exception_raise_state_identity( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, mach_exception_data_t code, + mach_msg_type_number_t code_cnt, int *flavor, thread_state_t old_state, + mach_msg_type_number_t old_state_cnt, thread_state_t new_state, + mach_msg_type_number_t *new_state_cnt); + +static struct exception_saved_state { + exception_mask_t saved_masks[EXC_TYPES_COUNT]; + mach_port_t saved_ports[EXC_TYPES_COUNT]; + exception_behavior_t saved_behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t saved_flavors[EXC_TYPES_COUNT]; + mach_msg_type_number_t saved_exception_types_count; +} exception_saved_state; + +static mach_port_t tgt_exception_port; + +// Mirrors __Reply__mach_exception_raise_t generated in mach_excServer.c +static struct rep_msg { + mach_msg_header_t header; + NDR_record_t ndr; + kern_return_t ret_code; +} rep_msg; + +// Mirrors __Request__mach_exception_raise_t generated in mach_excServer.c +// with a large trailing pad to avoid MACH_MSG_RCV_TOO_LARGE +static struct exc_msg { + mach_msg_header_t header; + // start of the kernel processed data + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + // end of the kernel processed data + NDR_record_t ndr; + exception_type_t exception; + mach_msg_type_number_t code_cnt; + mach_exception_data_t code; // an array of int64_t + char pad[512]; +} exc_msg; + static void putSymbolicator(JNIEnv *env, jobject this_obj, id symbolicator) { (*env)->SetLongField(env, this_obj, symbolicatorID, (jlong)(intptr_t)symbolicator); } @@ -91,9 +149,9 @@ static task_t getTask(JNIEnv *env, jobject this_obj) { #define CHECK_EXCEPTION if ((*env)->ExceptionOccurred(env)) { return;} #define THROW_NEW_DEBUGGER_EXCEPTION_(str, value) { throw_new_debugger_exception(env, str); return value; } #define THROW_NEW_DEBUGGER_EXCEPTION(str) { throw_new_debugger_exception(env, str); return;} -#define CHECK_EXCEPTION_CLEAR if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); } -#define CHECK_EXCEPTION_CLEAR_VOID if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); return; } -#define CHECK_EXCEPTION_CLEAR_(value) if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); return value; } +#define CHECK_EXCEPTION_CLEAR if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); } +#define CHECK_EXCEPTION_CLEAR_VOID if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); return; } +#define CHECK_EXCEPTION_CLEAR_(value) if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionClear(env); return value; } static void throw_new_debugger_exception(JNIEnv* env, const char* errMsg) { jclass exceptionClass = (*env)->FindClass(env, "sun/jvm/hotspot/debugger/DebuggerException"); @@ -129,7 +187,7 @@ static struct ps_prochandle* get_proc_handle(JNIEnv* env, jobject this_obj) { * Method: init0 * Signature: ()V */ -JNIEXPORT void JNICALL +JNIEXPORT void JNICALL Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_init0(JNIEnv *env, jclass cls) { symbolicatorID = (*env)->GetFieldID(env, cls, "symbolicator", "J"); CHECK_EXCEPTION; @@ -202,10 +260,10 @@ jlong lookupByNameIncore( * Method: lookupByName0 * Signature: (Ljava/lang/String;Ljava/lang/String;)J */ -JNIEXPORT jlong JNICALL +JNIEXPORT jlong JNICALL Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_lookupByName0( - JNIEnv *env, jobject this_obj, - jstring objectName, jstring symbolName) + JNIEnv *env, jobject this_obj, + jstring objectName, jstring symbolName) { struct ps_prochandle* ph = get_proc_handle(env, this_obj); if (ph != NULL && ph->core != NULL) { @@ -280,8 +338,8 @@ jbyteArray readBytesFromCore( */ JNIEXPORT jbyteArray JNICALL Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_readBytesFromProcess0( - JNIEnv *env, jobject this_obj, - jlong addr, jlong numBytes) + JNIEnv *env, jobject this_obj, + jlong addr, jlong numBytes) { print_debug("readBytesFromProcess called. addr = %llx numBytes = %lld\n", addr, numBytes); @@ -320,16 +378,16 @@ Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_readBytesFromProcess0( task_t gTask = getTask(env, this_obj); // Try to read each of the pages. for (i = 0; i < pageCount; i++) { - result = vm_read(gTask, alignedAddress + i*vm_page_size, vm_page_size, + result = vm_read(gTask, alignedAddress + i*vm_page_size, vm_page_size, &pages[i], &byteCount); - mapped[i] = (result == KERN_SUCCESS); + mapped[i] = (result == KERN_SUCCESS); // assume all failures are unmapped pages } print_debug("%ld pages\n", pageCount); - + remaining = numBytes; - + for (i = 0; i < pageCount; i++) { unsigned long len = vm_page_size; unsigned long start = 0; @@ -366,18 +424,18 @@ Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_readBytesFromProcess0( * integers to host all java threads' id, stack_start, stack_end as: * [uid0, stack_start0, stack_end0, uid1, stack_start1, stack_end1, ...] * - * The work cannot be done at init0 since Threads is not available yet(VM not initialized yet). + * The work cannot be done at init0 since Threads is not available yet(VM not initialized yet). * This function should be called only once if succeeded - */ + */ bool fill_java_threads(JNIEnv* env, jobject this_obj, struct ps_prochandle* ph) { int n = 0, i = 0, j; struct reg regs; - + jlongArray thrinfos = (*env)->CallObjectMethod(env, this_obj, getJavaThreadsInfo_ID); CHECK_EXCEPTION_(false); int len = (int)(*env)->GetArrayLength(env, thrinfos); uint64_t* cinfos = (uint64_t *)(*env)->GetLongArrayElements(env, thrinfos, NULL); - CHECK_EXCEPTION_(false); + CHECK_EXCEPTION_(false); n = get_num_threads(ph); print_debug("fill_java_threads called, num_of_thread = %d\n", n); for (i = 0; i < n; i++) { @@ -388,7 +446,7 @@ bool fill_java_threads(JNIEnv* env, jobject this_obj, struct ps_prochandle* ph) for (j = 0; j < len; j += 3) { lwpid_t uid = cinfos[j]; uint64_t beg = cinfos[j + 1]; - uint64_t end = cinfos[j + 2]; + uint64_t end = cinfos[j + 2]; if ((regs.r_rsp < end && regs.r_rsp >= beg) || (regs.r_rbp < end && regs.r_rbp >= beg)) { set_lwp_id(ph, i, uid); @@ -478,19 +536,19 @@ jlongArray getThreadIntegerRegisterSetFromCore(JNIEnv *env, jobject this_obj, lo thread_t lookupThreadFromThreadId(task_t task, jlong thread_id) { print_debug("lookupThreadFromThreadId thread_id=0x%llx\n", thread_id); - + thread_array_t thread_list = NULL; mach_msg_type_number_t thread_list_count = 0; thread_t result_thread = 0; int i; - + // get the list of all the send rights kern_return_t result = task_threads(task, &thread_list, &thread_list_count); if (result != KERN_SUCCESS) { print_debug("task_threads returned 0x%x\n", result); return 0; } - + for(i = 0 ; i < thread_list_count; i++) { thread_identifier_info_data_t m_ident_info; mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; @@ -501,7 +559,7 @@ lookupThreadFromThreadId(task_t task, jlong thread_id) { print_debug("thread_info returned 0x%x\n", result); break; } - + // if this is the one we're looking for, return the send right if (thread_id == m_ident_info.thread_id) { @@ -509,10 +567,10 @@ lookupThreadFromThreadId(task_t task, jlong thread_id) { break; } } - + vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (thread_t)); vm_deallocate(mach_task_self(), (vm_address_t) thread_list, thread_list_count); - + return result_thread; } @@ -522,10 +580,10 @@ lookupThreadFromThreadId(task_t task, jlong thread_id) { * Method: getThreadIntegerRegisterSet0 * Signature: (J)[J */ -JNIEXPORT jlongArray JNICALL +JNIEXPORT jlongArray JNICALL Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_getThreadIntegerRegisterSet0( - JNIEnv *env, jobject this_obj, - jlong thread_id) + JNIEnv *env, jobject this_obj, + jlong thread_id) { print_debug("getThreadRegisterSet0 called\n"); @@ -578,7 +636,7 @@ Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_getThreadIntegerRegisterSet0( primitiveArray[REG_INDEX(RCX)] = state.__rcx; primitiveArray[REG_INDEX(RAX)] = state.__rax; primitiveArray[REG_INDEX(TRAPNO)] = 0; // trapno, not used - primitiveArray[REG_INDEX(ERR)] = 0; // err, not used + primitiveArray[REG_INDEX(ERR)] = 0; // err, not used primitiveArray[REG_INDEX(RIP)] = state.__rip; primitiveArray[REG_INDEX(CS)] = state.__cs; primitiveArray[REG_INDEX(RFL)] = state.__rflags; @@ -630,78 +688,96 @@ Java_sun_jvm_hotspot_debugger_macosx_MacOSXDebuggerLocal_translateTID0( return (jint) usable_tid; } +// attach to a process/thread specified by "pid" +static bool ptrace_attach(pid_t pid) { + errno = 0; + ptrace(PT_ATTACHEXC, pid, 0, 0); -static bool ptrace_continue(pid_t pid, int signal) { - // pass the signal to the process so we don't swallow it - int res; - if ((res = ptrace(PT_CONTINUE, pid, (caddr_t)1, signal)) < 0) { - print_error("attach: ptrace(PT_CONTINUE, %d) failed with %d\n", pid, res); + if (errno != 0) { + print_error("ptrace_attach: ptrace(PT_ATTACHEXC,...) failed: %s", strerror(errno)); return false; } return true; } -// waits until the ATTACH has stopped the process -// by signal SIGSTOP -static bool ptrace_waitpid(pid_t pid) { - int ret; - int status; - while (true) { - // Wait for debuggee to stop. - ret = waitpid(pid, &status, 0); - if (ret >= 0) { - if (WIFSTOPPED(status)) { - // Any signal will stop the thread, make sure it is SIGSTOP. Otherwise SIGSTOP - // will still be pending and delivered when the process is DETACHED and the process - // will go to sleep. - if (WSTOPSIG(status) == SIGSTOP) { - // Debuggee stopped by SIGSTOP. - return true; - } - if (!ptrace_continue(pid, WSTOPSIG(status))) { - print_error("attach: Failed to correctly attach to VM. VM might HANG! [PTRACE_CONT failed, stopped by %d]\n", WSTOPSIG(status)); - return false; - } - } else { - print_error("attach: waitpid(): Child process exited/terminated (status = 0x%x)\n", status); - return false; - } - } else { - switch (errno) { - case EINTR: - continue; - break; - case ECHILD: - print_error("attach: waitpid() failed. Child process pid (%d) does not exist \n", pid); - break; - case EINVAL: - print_error("attach: waitpid() failed. Invalid options argument.\n"); - break; - default: - print_error("attach: waitpid() failed. Unexpected error %d\n",errno); - break; - } - return false; - } +kern_return_t catch_mach_exception_raise( + mach_port_t exception_port, mach_port_t thread_port, mach_port_t task_port, + exception_type_t exception_type, mach_exception_data_t codes, + mach_msg_type_number_t num_codes) { + + print_debug("catch_mach_exception_raise: Exception port = %d thread_port = %d " + "task port %d exc type = %d num_codes %d\n", + exception_port, thread_port, task_port, exception_type, num_codes); + + // This message should denote a Unix soft signal, with + // 1. the exception type = EXC_SOFTWARE + // 2. codes[0] (which is the code) = EXC_SOFT_SIGNAL + // 3. codes[1] (which is the sub-code) = SIGSTOP + if (!(exception_type == EXC_SOFTWARE && + codes[0] == EXC_SOFT_SIGNAL && + codes[num_codes -1] == SIGSTOP)) { + print_error("catch_mach_exception_raise: Message doesn't denote a Unix " + "soft signal. exception_type = %d, codes[0] = %d, " + "codes[num_codes -1] = %d, num_codes = %d\n", + exception_type, codes[0], codes[num_codes - 1], num_codes); + return MACH_RCV_INVALID_TYPE; } + return KERN_SUCCESS; } -// attach to a process/thread specified by "pid" -static bool ptrace_attach(pid_t pid) { - int res; -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - if ((res = ptrace(PT_ATTACH, pid, 0, 0)) < 0) { - print_error("ptrace(PT_ATTACH, %d) failed with %d\n", pid, res); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif +kern_return_t catch_mach_exception_raise_state( + mach_port_t exception_port, exception_type_t exception, const mach_exception_data_t code, + mach_msg_type_number_t code_cnt, int *flavor, const thread_state_t old_state, + mach_msg_type_number_t old_state_cnt, thread_state_t new_state, + mach_msg_type_number_t *new_state_cnt) { + return MACH_RCV_INVALID_TYPE; +} + + +kern_return_t catch_mach_exception_raise_state_identity( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, mach_exception_data_t code, + mach_msg_type_number_t code_cnt, int *flavor, + thread_state_t old_state, mach_msg_type_number_t old_state_cnt, + thread_state_t new_state, mach_msg_type_number_t *new_state_cnt) { + return MACH_RCV_INVALID_TYPE; +} + +// wait to receive an exception message +static bool wait_for_exception() { + kern_return_t result; + + result = mach_msg(&exc_msg.header, + MACH_RCV_MSG, + 0, + sizeof(exc_msg), + tgt_exception_port, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (result != MACH_MSG_SUCCESS) { + print_error("attach: wait_for_exception: mach_msg() failed: '%s' (%d)\n", + mach_error_string(result), result); return false; - } else { - return ptrace_waitpid(pid); } + + if (mach_exc_server(&exc_msg.header, &rep_msg.header) == false || + rep_msg.ret_code != KERN_SUCCESS) { + print_error("attach: wait_for_exception: mach_exc_server failure\n"); + if (rep_msg.ret_code != KERN_SUCCESS) { + print_error("catch_mach_exception_raise() failed '%s' (%d)\n", + mach_error_string(rep_msg.ret_code), rep_msg.ret_code); + } + return false; + } + + print_debug("reply msg from mach_exc_server: (msg->{bits = %#x, size = %u, " + "remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x},)", + rep_msg.header.msgh_bits, rep_msg.header.msgh_size, + rep_msg.header.msgh_remote_port, rep_msg.header.msgh_local_port, + rep_msg.header.msgh_reserved, rep_msg.header.msgh_id); + + return true; } /* @@ -719,18 +795,97 @@ JNF_COCOA_ENTER(env); kern_return_t result; task_t gTask = 0; + result = task_for_pid(mach_task_self(), jpid, &gTask); if (result != KERN_SUCCESS) { print_error("attach: task_for_pid(%d) failed: '%s' (%d)\n", (int)jpid, mach_error_string(result), result); - THROW_NEW_DEBUGGER_EXCEPTION("Can't attach to the process. Could be caused by an incorrect pid or lack of privileges."); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Could be caused by an incorrect pid or lack of privileges."); } putTask(env, this_obj, gTask); + // Allocate an exception port. + result = mach_port_allocate(mach_task_self(), + MACH_PORT_RIGHT_RECEIVE, + &tgt_exception_port); + if (result != KERN_SUCCESS) { + print_error("attach: mach_port_allocate() for tgt_exception_port failed: '%s' (%d)\n", + mach_error_string(result), result); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Couldn't allocate an exception port."); + } + + // Enable the new exception port to send messages. + result = mach_port_insert_right (mach_task_self(), + tgt_exception_port, + tgt_exception_port, + MACH_MSG_TYPE_MAKE_SEND); + if (result != KERN_SUCCESS) { + print_error("attach: mach_port_insert_right() failed for port 0x%x: '%s' (%d)\n", + tgt_exception_port, mach_error_string(result), result); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Couldn't add send privileges to the exception port."); + } + + // Save the existing original exception ports registered with the target + // process (for later restoration while detaching from the process). + result = task_get_exception_ports(gTask, + EXC_MASK_ALL, + exception_saved_state.saved_masks, + &exception_saved_state.saved_exception_types_count, + exception_saved_state.saved_ports, + exception_saved_state.saved_behaviors, + exception_saved_state.saved_flavors); + + if (result != KERN_SUCCESS) { + print_error("attach: task_get_exception_ports() failed: '%s' (%d)\n", + mach_error_string(result), result); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Could not get the target exception ports."); + } + + // register the exception port to be used for all future exceptions with the + // target process. + result = task_set_exception_ports(gTask, + EXC_MASK_ALL, + tgt_exception_port, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, + THREAD_STATE_NONE); + + if (result != KERN_SUCCESS) { + print_error("attach: task_set_exception_ports() failed -- port 0x%x: '%s' (%d)\n", + tgt_exception_port, mach_error_string(result), result); + mach_port_deallocate(mach_task_self(), gTask); + mach_port_deallocate(mach_task_self(), tgt_exception_port); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Could not register the exception port " + "with the target process."); + } + // use ptrace to stop the process // on os x, ptrace only needs to be called on the process, not the individual threads if (ptrace_attach(jpid) != true) { + print_error("attach: ptrace failure in attaching to %d\n", (int)jpid); mach_port_deallocate(mach_task_self(), gTask); - THROW_NEW_DEBUGGER_EXCEPTION("Can't attach to the process"); + mach_port_deallocate(mach_task_self(), tgt_exception_port); + THROW_NEW_DEBUGGER_EXCEPTION("Can't ptrace attach to the process"); + } + + if (wait_for_exception() != true) { + mach_port_deallocate(mach_task_self(), gTask); + mach_port_deallocate(mach_task_self(), tgt_exception_port); + THROW_NEW_DEBUGGER_EXCEPTION( + "Can't attach to the process. Issues with reception of the exception message."); + } + + // suspend all the threads in the task + result = task_suspend(gTask); + if (result != KERN_SUCCESS) { + print_error("attach: task_suspend() failed: '%s' (%d)\n", + mach_error_string(result), result); + mach_port_deallocate(mach_task_self(), gTask); + mach_port_deallocate(mach_task_self(), tgt_exception_port); + THROW_NEW_DEBUGGER_EXCEPTION("Can't attach. Unable to suspend all the threads in the task."); } id symbolicator = nil; @@ -745,13 +900,15 @@ JNF_COCOA_ENTER(env); putSymbolicator(env, this_obj, symbolicator); if (symbolicator == nil) { + mach_port_deallocate(mach_task_self(), gTask); + mach_port_deallocate(mach_task_self(), tgt_exception_port); THROW_NEW_DEBUGGER_EXCEPTION("Can't attach symbolicator to the process"); } JNF_COCOA_EXIT(env); } -/** For core file, +/** For core file, called from Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_attach0__Ljava_lang_String_2Ljava_lang_String_2 */ static void fillLoadObjects(JNIEnv* env, jobject this_obj, struct ps_prochandle* ph) { int n = 0, i = 0; @@ -811,6 +968,19 @@ Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_attach0__Ljava_lang_String_2L fillLoadObjects(env, this_obj, ph); } +static void detach_cleanup(task_t gTask, JNIEnv *env, jobject this_obj, bool throw_exception) { + mach_port_deallocate(mach_task_self(), tgt_exception_port); + mach_port_deallocate(mach_task_self(), gTask); + + id symbolicator = getSymbolicator(env, this_obj); + if (symbolicator != nil) { + CFRelease(symbolicator); + } + if (throw_exception) { + THROW_NEW_DEBUGGER_EXCEPTION("Cannot detach."); + } +} + /* * Class: sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal * Method: detach0 @@ -827,26 +997,55 @@ Java_sun_jvm_hotspot_debugger_bsd_BsdDebuggerLocal_detach0( return; } JNF_COCOA_ENTER(env); - task_t gTask = getTask(env, this_obj); - // detach from the ptraced process causing it to resume execution - int pid; - kern_return_t k_res; - k_res = pid_for_task(gTask, &pid); - if (k_res != KERN_SUCCESS) { - print_error("detach: pid_for_task(%d) failed (%d)\n", pid, k_res); - } - else { - int res = ptrace(PT_DETACH, pid, 0, 0); - if (res < 0) { - print_error("detach: ptrace(PT_DETACH, %d) failed (%d)\n", pid, res); + task_t gTask = getTask(env, this_obj); + kern_return_t k_res = 0; + + // Restore the pre-saved original exception ports registered with the target process + for (uint32_t i = 0; i < exception_saved_state.saved_exception_types_count; ++i) { + k_res = task_set_exception_ports(gTask, + exception_saved_state.saved_masks[i], + exception_saved_state.saved_ports[i], + exception_saved_state.saved_behaviors[i], + exception_saved_state.saved_flavors[i]); + if (k_res != KERN_SUCCESS) { + print_error("detach: task_set_exception_ports failed with %d while " + "restoring the target exception ports.\n", k_res); + detach_cleanup(gTask, env, this_obj, true); } } - mach_port_deallocate(mach_task_self(), gTask); - id symbolicator = getSymbolicator(env, this_obj); - if (symbolicator != nil) { - CFRelease(symbolicator); + // detach from the ptraced process causing it to resume execution + int pid; + k_res = pid_for_task(gTask, &pid); + if (k_res != KERN_SUCCESS) { + print_error("detach: pid_for_task(%d) failed (%d)\n", pid, k_res); + detach_cleanup(gTask, env, this_obj, true); } + else { + errno = 0; + ptrace(PT_DETACH, pid, (caddr_t)1, 0); + if (errno != 0) { + print_error("detach: ptrace(PT_DETACH,...) failed: %s", strerror(errno)); + detach_cleanup(gTask, env, this_obj, true); + } + } + + // reply to the previous exception message + k_res = mach_msg(&rep_msg.header, + MACH_SEND_MSG| MACH_SEND_INTERRUPT, + rep_msg.header.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (k_res != MACH_MSG_SUCCESS) { + print_error("detach: mach_msg() for replying to pending exceptions failed: '%s' (%d)\n", + mach_error_string(k_res), k_res); + detach_cleanup(gTask, env, this_obj, true); + } + + detach_cleanup(gTask, env, this_obj, false); + JNF_COCOA_EXIT(env); }