summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Light <allight@google.com>2020-01-23 15:39:08 -0800
committerTreehugger Robot <treehugger-gerrit@google.com>2020-01-29 23:31:10 +0000
commitfc58809f7b932d86234130be15487017dc37b0cf (patch)
treed003dedec73d77473aff6b2039860bc76a69d898
parentd00f129f1b7148f01efe6e9283a72d6ec8f0edd3 (diff)
downloadplatform_art-fc58809f7b932d86234130be15487017dc37b0cf.tar.gz
platform_art-fc58809f7b932d86234130be15487017dc37b0cf.tar.bz2
platform_art-fc58809f7b932d86234130be15487017dc37b0cf.zip
Remove old JDWP implementation from ART
The old 'internal' JDWP implementation hasn't been used for a few releases and it's a lot of code that's barely being tested and is at risk of bit-rot. To simplify the runtime and remove potentially buggy code this removes it. We also needed to rewrite the DdmThreadNotification code since it relied on the suspension functionality from the old debugger and was generally unsafe. Test: ./test.py --host Test: atest --test-mapping cts/tests/jdwp/TEST_MAPPING Test: atest --test-mapping cts/hostsidetests/jdwptunnel/TEST_MAPPING Test: Manual ddms Bug: 119034743 Change-Id: I775f310a009141296b730e4a6c2503506a329481
-rw-r--r--adbconnection/adbconnection.cc16
-rwxr-xr-xbuild/apex/art_apex_test.py1
-rw-r--r--cmdline/cmdline_parser_test.cc5
-rw-r--r--cmdline/cmdline_types.h5
-rw-r--r--compiler/utils/assembler_thumb_test_expected.cc.inc2
-rw-r--r--libartbase/base/endian_utils.h (renamed from runtime/jdwp/jdwp_bits.h)48
-rw-r--r--openjdkjvmti/ti_redefine.cc15
-rw-r--r--openjdkjvmti/ti_redefine.h1
-rw-r--r--runtime/Android.bp11
-rw-r--r--runtime/art_method.cc3
-rw-r--r--runtime/class_linker.cc3
-rw-r--r--runtime/debugger.cc4265
-rw-r--r--runtime/debugger.h745
-rw-r--r--runtime/entrypoints/quick/quick_trampoline_entrypoints.cc13
-rw-r--r--runtime/entrypoints_order_test.cc4
-rw-r--r--runtime/gc/allocation_record.cc12
-rw-r--r--runtime/gc/allocation_record.h1
-rw-r--r--runtime/hprof/hprof.cc3
-rw-r--r--runtime/interpreter/mterp/mterp.cc1
-rw-r--r--runtime/jdwp/README.txt11
-rw-r--r--runtime/jdwp/jdwp.h518
-rw-r--r--runtime/jdwp/jdwp_adb.cc466
-rw-r--r--runtime/jdwp/jdwp_constants.h249
-rw-r--r--runtime/jdwp/jdwp_event.cc1385
-rw-r--r--runtime/jdwp/jdwp_event.h113
-rw-r--r--runtime/jdwp/jdwp_expand_buf.cc191
-rw-r--r--runtime/jdwp/jdwp_expand_buf.h70
-rw-r--r--runtime/jdwp/jdwp_handler.cc1714
-rw-r--r--runtime/jdwp/jdwp_main.cc784
-rw-r--r--runtime/jdwp/jdwp_options_test.cc79
-rw-r--r--runtime/jdwp/jdwp_priv.h127
-rw-r--r--runtime/jdwp/jdwp_request.cc186
-rw-r--r--runtime/jdwp/jdwp_socket.cc534
-rw-r--r--runtime/jdwp/object_registry-inl.h37
-rw-r--r--runtime/jdwp/object_registry.cc287
-rw-r--r--runtime/jdwp/object_registry.h136
-rw-r--r--runtime/jdwp_provider.h1
-rw-r--r--runtime/native/dalvik_system_VMDebug.cc8
-rw-r--r--runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc70
-rw-r--r--runtime/parsed_options.cc4
-rw-r--r--runtime/runtime.cc15
-rw-r--r--runtime/runtime_options.h1
-rw-r--r--runtime/suspend_reason.h2
-rw-r--r--runtime/thread.cc40
-rw-r--r--runtime/thread.h41
-rw-r--r--runtime/thread_list.cc194
-rw-r--r--runtime/thread_list.h17
-rwxr-xr-xtest/etc/run-test-jar8
48 files changed, 188 insertions, 12254 deletions
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index 2234ce4fa24..c512b0678be 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -26,6 +26,8 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/mutex.h"
+#include "base/socket_peer_is_trusted.h"
+#include "debugger.h"
#include "jni/java_vm_ext.h"
#include "jni/jni_env_ext.h"
#include "mirror/throwable.h"
@@ -35,20 +37,24 @@
#include "scoped_thread_state_change-inl.h"
#include "well_known_classes.h"
-#include "jdwp/jdwp_priv.h"
-
#include "fd_transport.h"
#include "poll.h"
#include <sys/ioctl.h>
#include <sys/socket.h>
+#include <sys/uio.h>
#include <sys/un.h>
#include <sys/eventfd.h>
#include <jni.h>
namespace adbconnection {
+static constexpr size_t kJdwpHeaderLen = 11U;
+/* DDM support */
+static constexpr uint8_t kJdwpDdmCmdSet = 199U; // 0xc7, or 'G'+128
+static constexpr uint8_t kJdwpDdmCmd = 1U;
+
// Messages sent from the transport
using dt_fd_forward::kListenStartMessage;
using dt_fd_forward::kListenEndMessage;
@@ -333,7 +339,7 @@ void AdbConnectionState::SendDdmPacket(uint32_t id,
// the adb_write_event_fd_ will ensure that the adb_connection_socket_ will not go away until
// after we have sent our data.
static constexpr uint32_t kDdmPacketHeaderSize =
- kJDWPHeaderLen // jdwp command packet size
+ kJdwpHeaderLen // jdwp command packet size
+ sizeof(uint32_t) // Type
+ sizeof(uint32_t); // length
alignas(sizeof(uint32_t)) std::array<uint8_t, kDdmPacketHeaderSize> pkt;
@@ -352,9 +358,9 @@ void AdbConnectionState::SendDdmPacket(uint32_t id,
switch (packet_type) {
case DdmPacketType::kCmd: {
// Now the cmd-set
- *(pkt_data++) = kJDWPDdmCmdSet;
+ *(pkt_data++) = kJdwpDdmCmdSet;
// Now the command
- *(pkt_data++) = kJDWPDdmCmd;
+ *(pkt_data++) = kJdwpDdmCmd;
break;
}
case DdmPacketType::kReply: {
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index f2ec6e43780..d80db3c90c0 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -904,7 +904,6 @@ class TestingTargetChecker:
self._checker.check_art_test_executable('instrumentation_test')
self._checker.check_art_test_executable('intern_table_test')
self._checker.check_art_test_executable('java_vm_ext_test')
- self._checker.check_art_test_executable('jdwp_options_test')
self._checker.check_art_test_executable('jit_memory_region_test')
self._checker.check_art_test_executable('jni_internal_test')
self._checker.check_art_test_executable('large_object_space_test')
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 49e0a4f2fe0..37dcd16ce3d 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -391,11 +391,6 @@ TEST_F(CmdlineParserTest, TestJdwpProviderDefault) {
EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kDefaultJdwpProvider, opt_args, M::JdwpProvider);
} // TEST_F
-TEST_F(CmdlineParserTest, TestJdwpProviderInternal) {
- const char* opt_args = "-XjdwpProvider:internal";
- EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider);
-} // TEST_F
-
TEST_F(CmdlineParserTest, TestJdwpProviderNone) {
const char* opt_args = "-XjdwpProvider:none";
EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kNone, opt_args, M::JdwpProvider);
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index 11193302d3c..25902f1809c 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -30,10 +30,10 @@
// Includes for the types that are being specialized
#include <string>
#include "base/time_utils.h"
+#include "base/logging.h"
#include "experimental_flags.h"
#include "gc/collector_type.h"
#include "gc/space/large_object_space.h"
-#include "jdwp/jdwp.h"
#include "jdwp_provider.h"
#include "jit/profile_saver_options.h"
#include "plugin.h"
@@ -74,13 +74,10 @@ struct CmdlineType<JdwpProvider> : CmdlineTypeParser<JdwpProvider> {
if (option == "help") {
return Result::Usage(
"Example: -XjdwpProvider:none to disable JDWP\n"
- "Example: -XjdwpProvider:internal for internal jdwp implementation\n"
"Example: -XjdwpProvider:adbconnection for adb connection mediated jdwp implementation\n"
"Example: -XjdwpProvider:default for the default jdwp implementation\n");
} else if (option == "default") {
return Result::Success(JdwpProvider::kDefaultJdwpProvider);
- } else if (option == "internal") {
- return Result::Success(JdwpProvider::kInternal);
} else if (option == "adbconnection") {
return Result::Success(JdwpProvider::kAdbConnection);
} else if (option == "none") {
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index 49ac2f571dc..cc0b5d51899 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -153,7 +153,7 @@ const char* const VixlJniHelpersResults[] = {
" 21c: f8d9 8034 ldr.w r8, [r9, #52] ; 0x34\n",
" 220: 4770 bx lr\n",
" 222: 4660 mov r0, ip\n",
- " 224: f8d9 c2ec ldr.w ip, [r9, #748] ; 0x2ec\n",
+ " 224: f8d9 c2e4 ldr.w ip, [r9, #740] ; 0x2e4\n",
" 228: 47e0 blx ip\n",
nullptr
};
diff --git a/runtime/jdwp/jdwp_bits.h b/libartbase/base/endian_utils.h
index 33b98f3efe5..6c19ef96ae5 100644
--- a/runtime/jdwp/jdwp_bits.h
+++ b/libartbase/base/endian_utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,41 +14,36 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_JDWP_JDWP_BITS_H_
-#define ART_RUNTIME_JDWP_JDWP_BITS_H_
+#ifndef ART_LIBARTBASE_BASE_ENDIAN_UTILS_H_
+#define ART_LIBARTBASE_BASE_ENDIAN_UTILS_H_
-#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <string>
+#include <endian.h>
#include <vector>
namespace art {
-namespace JDWP {
-
-static inline uint32_t Get4BE(unsigned char const* pSrc) {
+constexpr uint32_t Get4BE(unsigned char const* pSrc) {
return (pSrc[0] << 24) | (pSrc[1] << 16) | (pSrc[2] << 8) | pSrc[3];
}
-static inline void Append1BE(std::vector<uint8_t>& bytes, uint8_t value) {
+inline void Append1BE(std::vector<uint8_t>& bytes, uint8_t value) {
bytes.push_back(value);
}
-static inline void Append2BE(std::vector<uint8_t>& bytes, uint16_t value) {
+inline void Append2BE(std::vector<uint8_t>& bytes, uint16_t value) {
bytes.push_back(static_cast<uint8_t>(value >> 8));
bytes.push_back(static_cast<uint8_t>(value));
}
-static inline void Append4BE(std::vector<uint8_t>& bytes, uint32_t value) {
+inline void Append4BE(std::vector<uint8_t>& bytes, uint32_t value) {
bytes.push_back(static_cast<uint8_t>(value >> 24));
bytes.push_back(static_cast<uint8_t>(value >> 16));
bytes.push_back(static_cast<uint8_t>(value >> 8));
bytes.push_back(static_cast<uint8_t>(value));
}
-static inline void Append8BE(std::vector<uint8_t>& bytes, uint64_t value) {
+inline void Append8BE(std::vector<uint8_t>& bytes, uint64_t value) {
bytes.push_back(static_cast<uint8_t>(value >> 56));
bytes.push_back(static_cast<uint8_t>(value >> 48));
bytes.push_back(static_cast<uint8_t>(value >> 40));
@@ -59,7 +54,7 @@ static inline void Append8BE(std::vector<uint8_t>& bytes, uint64_t value) {
bytes.push_back(static_cast<uint8_t>(value));
}
-static inline void AppendUtf16BE(std::vector<uint8_t>& bytes, const uint16_t* chars,
+inline void AppendUtf16BE(std::vector<uint8_t>& bytes, const uint16_t* chars,
size_t char_count) {
Append4BE(bytes, char_count);
for (size_t i = 0; i < char_count; ++i) {
@@ -67,7 +62,7 @@ static inline void AppendUtf16BE(std::vector<uint8_t>& bytes, const uint16_t* ch
}
}
-static inline void AppendUtf16CompressedBE(std::vector<uint8_t>& bytes,
+inline void AppendUtf16CompressedBE(std::vector<uint8_t>& bytes,
const uint8_t* chars, size_t char_count) {
Append4BE(bytes, char_count);
for (size_t i = 0; i < char_count; ++i) {
@@ -76,18 +71,18 @@ static inline void AppendUtf16CompressedBE(std::vector<uint8_t>& bytes,
}
// @deprecated
-static inline void Set1(uint8_t* buf, uint8_t val) {
+inline void Set1(uint8_t* buf, uint8_t val) {
*buf = val;
}
// @deprecated
-static inline void Set2BE(uint8_t* buf, uint16_t val) {
+inline void Set2BE(uint8_t* buf, uint16_t val) {
*buf++ = (uint8_t)(val >> 8);
*buf = (uint8_t)(val);
}
// @deprecated
-static inline void Set4BE(uint8_t* buf, uint32_t val) {
+inline void Set4BE(uint8_t* buf, uint32_t val) {
*buf++ = (uint8_t)(val >> 24);
*buf++ = (uint8_t)(val >> 16);
*buf++ = (uint8_t)(val >> 8);
@@ -95,7 +90,7 @@ static inline void Set4BE(uint8_t* buf, uint32_t val) {
}
// @deprecated
-static inline void Set8BE(uint8_t* buf, uint64_t val) {
+inline void Set8BE(uint8_t* buf, uint64_t val) {
*buf++ = (uint8_t)(val >> 56);
*buf++ = (uint8_t)(val >> 48);
*buf++ = (uint8_t)(val >> 40);
@@ -106,28 +101,25 @@ static inline void Set8BE(uint8_t* buf, uint64_t val) {
*buf = (uint8_t)(val);
}
-static inline void Write1BE(uint8_t** dst, uint8_t value) {
+inline void Write1BE(uint8_t** dst, uint8_t value) {
Set1(*dst, value);
*dst += sizeof(value);
}
-static inline void Write2BE(uint8_t** dst, uint16_t value) {
+inline void Write2BE(uint8_t** dst, uint16_t value) {
Set2BE(*dst, value);
*dst += sizeof(value);
}
-static inline void Write4BE(uint8_t** dst, uint32_t value) {
+inline void Write4BE(uint8_t** dst, uint32_t value) {
Set4BE(*dst, value);
*dst += sizeof(value);
}
-static inline void Write8BE(uint8_t** dst, uint64_t value) {
+inline void Write8BE(uint8_t** dst, uint64_t value) {
Set8BE(*dst, value);
*dst += sizeof(value);
}
-
-} // namespace JDWP
-
} // namespace art
-#endif // ART_RUNTIME_JDWP_JDWP_BITS_H_
+#endif // ART_LIBARTBASE_BASE_ENDIAN_UTILS_H_
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 28018d7fe87..d9181592278 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -83,10 +83,6 @@
#include "handle_scope.h"
#include "instrumentation.h"
#include "intern_table.h"
-#include "jdwp/jdwp.h"
-#include "jdwp/jdwp_constants.h"
-#include "jdwp/jdwp_event.h"
-#include "jdwp/object_registry.h"
#include "jit/jit.h"
#include "jit/jit_code_cache.h"
#include "jni/jni_env_ext-inl.h"
@@ -2155,19 +2151,8 @@ void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() {
BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass().Ptr());
}
-void Redefiner::ClassRedefinition::UnregisterBreakpoints() {
- if (LIKELY(!art::Dbg::IsDebuggerActive())) {
- return;
- }
- art::JDWP::JdwpState* state = art::Dbg::GetJdwpState();
- if (state != nullptr) {
- state->UnregisterLocationEventsOnClass(GetMirrorClass());
- }
-}
-
void Redefiner::UnregisterAllBreakpoints() {
for (Redefiner::ClassRedefinition& redef : redefinitions_) {
- redef.UnregisterBreakpoints();
redef.UnregisterJvmtiBreakpoints();
}
}
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index e419e573536..d7c7b89899d 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -256,7 +256,6 @@ class Redefiner {
void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
- void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
// This should be done with all threads suspended.
void UnregisterJvmtiBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
diff --git a/runtime/Android.bp b/runtime/Android.bp
index b48357b3cbe..17cbd9d243e 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -140,13 +140,6 @@ libart_cc_defaults {
"interpreter/shadow_frame.cc",
"interpreter/unstarted_runtime.cc",
"java_frame_root_info.cc",
- "jdwp/jdwp_event.cc",
- "jdwp/jdwp_expand_buf.cc",
- "jdwp/jdwp_handler.cc",
- "jdwp/jdwp_main.cc",
- "jdwp/jdwp_request.cc",
- "jdwp/jdwp_socket.cc",
- "jdwp/object_registry.cc",
"jit/debugger_interface.cc",
"jit/jit.cc",
"jit/jit_code_cache.cc",
@@ -396,7 +389,6 @@ libart_cc_defaults {
target: {
android: {
srcs: [
- "jdwp/jdwp_adb.cc",
"monitor_android.cc",
"runtime_android.cc",
"thread_android.cc",
@@ -535,8 +527,6 @@ gensrcs {
"instrumentation.h",
"indirect_reference_table.h",
"jdwp_provider.h",
- "jdwp/jdwp.h",
- "jdwp/jdwp_constants.h",
"jni_id_type.h",
"lock_word.h",
"oat_file.h",
@@ -689,7 +679,6 @@ art_cc_test {
"intern_table_test.cc",
"interpreter/safe_math_test.cc",
"interpreter/unstarted_runtime_test.cc",
- "jdwp/jdwp_options_test.cc",
"jit/jit_memory_region_test.cc",
"jit/profile_saver_test.cc",
"jit/profiling_info_test.cc",
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index d975a3017e8..a74d1ad7121 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -332,8 +332,7 @@ void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue*
// Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent
// cycling around the various JIT/Interpreter methods that handle method invocation.
if (UNLIKELY(!runtime->IsStarted() ||
- (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()) ||
- Dbg::IsForcedInterpreterNeededForCalling(self, this))) {
+ (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) {
if (IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(
self, this, nullptr, args, result, /*stay_in_interpreter=*/ true);
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 4d35a03180b..56ae30799f0 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3499,8 +3499,7 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void*
return true;
}
- if (Thread::Current()->IsForceInterpreter() ||
- Dbg::IsForcedInterpreterNeededForCalling(Thread::Current(), method)) {
+ if (Thread::Current()->IsForceInterpreter()) {
// Force the use of interpreter when it is required by the debugger.
return true;
}
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 95361b5b825..f218c4eaec8 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -29,7 +29,9 @@
#include "arch/context.h"
#include "art_field-inl.h"
#include "art_method-inl.h"
+#include "base/endian_utils.h"
#include "base/enums.h"
+#include "base/logging.h"
#include "base/memory_tool.h"
#include "base/safe_map.h"
#include "base/strlcpy.h"
@@ -52,8 +54,6 @@
#include "gc/space/space-inl.h"
#include "handle_scope-inl.h"
#include "instrumentation.h"
-#include "jdwp/jdwp_priv.h"
-#include "jdwp/object_registry-inl.h"
#include "jni/jni_internal.h"
#include "jvalue-inl.h"
#include "mirror/array-alloc-inl.h"
@@ -74,18 +74,19 @@
#include "reflective_handle.h"
#include "reflective_handle_scope-inl.h"
#include "runtime-inl.h"
+#include "runtime_callbacks.h"
#include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
#include "stack.h"
+#include "thread.h"
#include "thread_list.h"
+#include "thread_pool.h"
#include "well_known_classes.h"
namespace art {
using android::base::StringPrintf;
-// The key identifying the debugger to update instrumentation.
-static constexpr const char* kDbgInstrumentationKey = "Debugger";
-
// Limit alloc_record_count to the 2BE value (64k-1) that is the limit of the current protocol.
static uint16_t CappedAllocRecordCount(size_t alloc_record_count) {
const size_t cap = 0xffff;
@@ -95,223 +96,9 @@ static uint16_t CappedAllocRecordCount(size_t alloc_record_count) {
return alloc_record_count;
}
-class Breakpoint : public ValueObject {
- public:
- Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind)
- : method_(method->GetCanonicalMethod(kRuntimePointerSize)),
- dex_pc_(dex_pc),
- deoptimization_kind_(deoptimization_kind) {
- CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing ||
- deoptimization_kind_ == DeoptimizationRequest::kSelectiveDeoptimization ||
- deoptimization_kind_ == DeoptimizationRequest::kFullDeoptimization);
- }
-
- Breakpoint(const Breakpoint& other) REQUIRES_SHARED(Locks::mutator_lock_)
- : method_(other.method_),
- dex_pc_(other.dex_pc_),
- deoptimization_kind_(other.deoptimization_kind_) {}
-
- // Method() is called from root visiting, do not use ScopedObjectAccess here or it can cause
- // GC to deadlock if another thread tries to call SuspendAll while the GC is in a runnable state.
- ArtMethod* Method() const {
- return method_;
- }
-
- uint32_t DexPc() const {
- return dex_pc_;
- }
-
- DeoptimizationRequest::Kind GetDeoptimizationKind() const {
- return deoptimization_kind_;
- }
-
- // Returns true if the method of this breakpoint and the passed in method should be considered the
- // same. That is, they are either the same method or they are copied from the same method.
- bool IsInMethod(ArtMethod* m) const REQUIRES_SHARED(Locks::mutator_lock_) {
- return method_ == m->GetCanonicalMethod(kRuntimePointerSize);
- }
-
- private:
- // The location of this breakpoint.
- ArtMethod* method_;
- uint32_t dex_pc_;
-
- // Indicates whether breakpoint needs full deoptimization or selective deoptimization.
- DeoptimizationRequest::Kind deoptimization_kind_;
-};
-
-static std::ostream& operator<<(std::ostream& os, const Breakpoint& rhs)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- os << StringPrintf("Breakpoint[%s @%#x]", ArtMethod::PrettyMethod(rhs.Method()).c_str(),
- rhs.DexPc());
- return os;
-}
-
-class DebugInstrumentationListener final : public instrumentation::InstrumentationListener {
- public:
- DebugInstrumentationListener() {}
- virtual ~DebugInstrumentationListener() {}
-
- void MethodEntered(Thread* thread,
- Handle<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t dex_pc)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- if (method->IsNative()) {
- // TODO: post location events is a suspension point and native method entry stubs aren't.
- return;
- }
- if (IsListeningToDexPcMoved()) {
- // We also listen to kDexPcMoved instrumentation event so we know the DexPcMoved method is
- // going to be called right after us. To avoid sending JDWP events twice for this location,
- // we report the event in DexPcMoved. However, we must remind this is method entry so we
- // send the METHOD_ENTRY event. And we can also group it with other events for this location
- // like BREAKPOINT or SINGLE_STEP (or even METHOD_EXIT if this is a RETURN instruction).
- thread->SetDebugMethodEntry();
- } else if (IsListeningToMethodExit() && IsReturn(method, dex_pc)) {
- // We also listen to kMethodExited instrumentation event and the current instruction is a
- // RETURN so we know the MethodExited method is going to be called right after us. To avoid
- // sending JDWP events twice for this location, we report the event(s) in MethodExited.
- // However, we must remind this is method entry so we send the METHOD_ENTRY event. And we can
- // also group it with other events for this location like BREAKPOINT or SINGLE_STEP.
- thread->SetDebugMethodEntry();
- } else {
- Dbg::UpdateDebugger(thread, this_object.Get(), method, 0, Dbg::kMethodEntry, nullptr);
- }
- }
-
- void MethodExited(Thread* thread,
- Handle<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t dex_pc,
- instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED,
- JValue& return_value)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- if (method->IsNative()) {
- // TODO: post location events is a suspension point and native method entry stubs aren't.
- return;
- }
- uint32_t events = Dbg::kMethodExit;
- if (thread->IsDebugMethodEntry()) {
- // It is also the method entry.
- DCHECK(IsReturn(method, dex_pc));
- events |= Dbg::kMethodEntry;
- thread->ClearDebugMethodEntry();
- }
- Dbg::UpdateDebugger(thread, this_object.Get(), method, dex_pc, events, &return_value);
- }
-
- void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Object> this_object ATTRIBUTE_UNUSED,
- ArtMethod* method,
- uint32_t dex_pc)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- // We're not recorded to listen to this kind of event, so complain.
- LOG(ERROR) << "Unexpected method unwind event in debugger " << ArtMethod::PrettyMethod(method)
- << " " << dex_pc;
- }
-
- void DexPcMoved(Thread* thread,
- Handle<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t new_dex_pc)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- if (IsListeningToMethodExit() && IsReturn(method, new_dex_pc)) {
- // We also listen to kMethodExited instrumentation event and the current instruction is a
- // RETURN so we know the MethodExited method is going to be called right after us. Like in
- // MethodEntered, we delegate event reporting to MethodExited.
- // Besides, if this RETURN instruction is the only one in the method, we can send multiple
- // JDWP events in the same packet: METHOD_ENTRY, METHOD_EXIT, BREAKPOINT and/or SINGLE_STEP.
- // Therefore, we must not clear the debug method entry flag here.
- } else {
- uint32_t events = 0;
- if (thread->IsDebugMethodEntry()) {
- // It is also the method entry.
- events = Dbg::kMethodEntry;
- thread->ClearDebugMethodEntry();
- }
- Dbg::UpdateDebugger(thread, this_object.Get(), method, new_dex_pc, events, nullptr);
- }
- }
-
- void FieldRead(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t dex_pc,
- ArtField* field)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- Dbg::PostFieldAccessEvent(method, dex_pc, this_object.Get(), field);
- }
-
- void FieldWritten(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t dex_pc,
- ArtField* field,
- const JValue& field_value)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- Dbg::PostFieldModificationEvent(method, dex_pc, this_object.Get(), field, &field_value);
- }
-
- void ExceptionThrown(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Throwable> exception_object)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- Dbg::PostException(exception_object.Get());
- }
-
- // We only care about branches in the Jit.
- void Branch(Thread* /*thread*/, ArtMethod* method, uint32_t dex_pc, int32_t dex_pc_offset)
- override REQUIRES_SHARED(Locks::mutator_lock_) {
- LOG(ERROR) << "Unexpected branch event in debugger " << ArtMethod::PrettyMethod(method)
- << " " << dex_pc << ", " << dex_pc_offset;
- }
-
- // TODO Might be worth it to post ExceptionCatch event.
- void ExceptionHandled(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Throwable> throwable ATTRIBUTE_UNUSED) override {
- LOG(ERROR) << "Unexpected exception handled event in debugger";
- }
-
- // TODO Might be worth it to implement this.
- void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED,
- const ShadowFrame& frame ATTRIBUTE_UNUSED) override {
- LOG(ERROR) << "Unexpected WatchedFramePop event in debugger";
- }
-
- private:
- static bool IsReturn(ArtMethod* method, uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) {
- return method->DexInstructions().InstructionAt(dex_pc).IsReturn();
- }
-
- static bool IsListeningToDexPcMoved() REQUIRES_SHARED(Locks::mutator_lock_) {
- return IsListeningTo(instrumentation::Instrumentation::kDexPcMoved);
- }
-
- static bool IsListeningToMethodExit() REQUIRES_SHARED(Locks::mutator_lock_) {
- return IsListeningTo(instrumentation::Instrumentation::kMethodExited);
- }
-
- static bool IsListeningTo(instrumentation::Instrumentation::InstrumentationEvent event)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return (Dbg::GetInstrumentationEvents() & event) != 0;
- }
-
- DISALLOW_COPY_AND_ASSIGN(DebugInstrumentationListener);
-} gDebugInstrumentationListener;
-
// JDWP is allowed unless the Zygote forbids it.
static bool gJdwpAllowed = true;
-// Was there a -Xrunjdwp or -agentlib:jdwp= argument on the command line?
-static bool gJdwpConfigured = false;
-
-// JDWP options for debugging. Only valid if IsJdwpConfigured() is true.
-static JDWP::JdwpOptions gJdwpOptions;
-
-// Runtime JDWP state.
-static JDWP::JdwpState* gJdwpState = nullptr;
-static bool gDebuggerConnected; // debugger or DDMS is connected.
-
static bool gDdmThreadNotification = false;
// DDMS GC-related settings.
@@ -321,287 +108,7 @@ static Dbg::HpsgWhat gDdmHpsgWhat;
static Dbg::HpsgWhen gDdmNhsgWhen = Dbg::HPSG_WHEN_NEVER;
static Dbg::HpsgWhat gDdmNhsgWhat;
-bool Dbg::gDebuggerActive = false;
-bool Dbg::gDisposed = false;
-ObjectRegistry* Dbg::gRegistry = nullptr;
-DebuggerActiveMethodInspectionCallback Dbg::gDebugActiveCallback;
-DebuggerDdmCallback Dbg::gDebugDdmCallback;
-InternalDebuggerControlCallback Dbg::gDebuggerControlCallback;
-
-// Deoptimization support.
-std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_;
-size_t Dbg::full_deoptimization_event_count_ = 0;
-
-// Instrumentation event reference counters.
-size_t Dbg::dex_pc_change_event_ref_count_ = 0;
-size_t Dbg::method_enter_event_ref_count_ = 0;
-size_t Dbg::method_exit_event_ref_count_ = 0;
-size_t Dbg::field_read_event_ref_count_ = 0;
-size_t Dbg::field_write_event_ref_count_ = 0;
-size_t Dbg::exception_catch_event_ref_count_ = 0;
-uint32_t Dbg::instrumentation_events_ = 0;
-
Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_;
-Dbg::DbgClassLoadCallback Dbg::class_load_callback_;
-
-void DebuggerDdmCallback::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
- if (gJdwpState == nullptr) {
- VLOG(jdwp) << "Debugger thread not active, ignoring DDM send: " << type;
- } else {
- iovec vec[1];
- vec[0].iov_base = reinterpret_cast<void*>(const_cast<uint8_t*>(data.data()));
- vec[0].iov_len = data.size();
- gJdwpState->DdmSendChunkV(type, vec, 1);
- }
-}
-
-bool DebuggerActiveMethodInspectionCallback::IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED) {
- return Dbg::IsDebuggerActive();
-}
-
-bool DebuggerActiveMethodInspectionCallback::IsMethodSafeToJit(ArtMethod* m) {
- return !Dbg::MethodHasAnyBreakpoints(m);
-}
-
-bool DebuggerActiveMethodInspectionCallback::MethodNeedsDebugVersion(
- ArtMethod* m ATTRIBUTE_UNUSED) {
- return Dbg::IsDebuggerActive();
-}
-
-void InternalDebuggerControlCallback::StartDebugger() {
- // Release the mutator lock.
- ScopedThreadStateChange stsc(art::Thread::Current(), kNative);
- Dbg::StartJdwp();
-}
-
-void InternalDebuggerControlCallback::StopDebugger() {
- Dbg::StopJdwp();
-}
-
-bool InternalDebuggerControlCallback::IsDebuggerConfigured() {
- return Dbg::IsJdwpConfigured();
-}
-
-// Breakpoints.
-static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_);
-
-void DebugInvokeReq::VisitRoots(RootVisitor* visitor, const RootInfo& root_info) {
- receiver.VisitRootIfNonNull(visitor, root_info); // null for static method call.
- klass.VisitRoot(visitor, root_info);
-}
-
-void SingleStepControl::AddDexPc(uint32_t dex_pc) {
- dex_pcs_.insert(dex_pc);
-}
-
-bool SingleStepControl::ContainsDexPc(uint32_t dex_pc) const {
- return dex_pcs_.find(dex_pc) == dex_pcs_.end();
-}
-
-static bool IsBreakpoint(ArtMethod* m, uint32_t dex_pc)
- REQUIRES(!Locks::breakpoint_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ReaderMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_);
- for (size_t i = 0, e = gBreakpoints.size(); i < e; ++i) {
- if (gBreakpoints[i].DexPc() == dex_pc && gBreakpoints[i].IsInMethod(m)) {
- VLOG(jdwp) << "Hit breakpoint #" << i << ": " << gBreakpoints[i];
- return true;
- }
- }
- return false;
-}
-
-static bool IsSuspendedForDebugger(ScopedObjectAccessUnchecked& soa, Thread* thread)
- REQUIRES(!Locks::thread_suspend_count_lock_) {
- MutexLock mu(soa.Self(), *Locks::thread_suspend_count_lock_);
- // A thread may be suspended for GC; in this code, we really want to know whether
- // there's a debugger suspension active.
- return thread->IsSuspended() && thread->GetDebugSuspendCount() > 0;
-}
-
-static ObjPtr<mirror::Array> DecodeNonNullArray(JDWP::RefTypeId id, JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Object> o = Dbg::GetObjectRegistry()->Get<mirror::Object>(id, error);
- if (o == nullptr) {
- *error = JDWP::ERR_INVALID_OBJECT;
- return nullptr;
- }
- if (!o->IsArrayInstance()) {
- *error = JDWP::ERR_INVALID_ARRAY;
- return nullptr;
- }
- *error = JDWP::ERR_NONE;
- return o->AsArray();
-}
-
-static ObjPtr<mirror::Class> DecodeClass(JDWP::RefTypeId id, JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Object> o = Dbg::GetObjectRegistry()->Get<mirror::Object>(id, error);
- if (o == nullptr) {
- *error = JDWP::ERR_INVALID_OBJECT;
- return nullptr;
- }
- if (!o->IsClass()) {
- *error = JDWP::ERR_INVALID_CLASS;
- return nullptr;
- }
- *error = JDWP::ERR_NONE;
- return o->AsClass();
-}
-
-static Thread* DecodeThread(ScopedObjectAccessUnchecked& soa, JDWP::ObjectId thread_id,
- JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_) {
- ObjPtr<mirror::Object> thread_peer =
- Dbg::GetObjectRegistry()->Get<mirror::Object>(thread_id, error);
- if (thread_peer == nullptr) {
- // This isn't even an object.
- *error = JDWP::ERR_INVALID_OBJECT;
- return nullptr;
- }
-
- ObjPtr<mirror::Class> java_lang_Thread =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread);
- if (!java_lang_Thread->IsAssignableFrom(thread_peer->GetClass())) {
- // This isn't a thread.
- *error = JDWP::ERR_INVALID_THREAD;
- return nullptr;
- }
-
- MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
- Thread* thread = Thread::FromManagedThread(soa, thread_peer);
- // If thread is null then this a java.lang.Thread without a Thread*. Must be a un-started or a
- // zombie.
- *error = (thread == nullptr) ? JDWP::ERR_THREAD_NOT_ALIVE : JDWP::ERR_NONE;
- return thread;
-}
-
-static JDWP::JdwpTag BasicTagFromDescriptor(const char* descriptor) {
- // JDWP deliberately uses the descriptor characters' ASCII values for its enum.
- // Note that by "basic" we mean that we don't get more specific than JT_OBJECT.
- return static_cast<JDWP::JdwpTag>(descriptor[0]);
-}
-
-static JDWP::JdwpTag BasicTagFromClass(ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::string temp;
- const char* descriptor = klass->GetDescriptor(&temp);
- return BasicTagFromDescriptor(descriptor);
-}
-
-static JDWP::JdwpTag TagFromClass(const ScopedObjectAccessUnchecked& soa, ObjPtr<mirror::Class> c)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK(c != nullptr);
- if (c->IsArrayClass()) {
- return JDWP::JT_ARRAY;
- }
- if (c->IsStringClass()) {
- return JDWP::JT_STRING;
- }
- if (c->IsClassClass()) {
- return JDWP::JT_CLASS_OBJECT;
- }
- {
- ObjPtr<mirror::Class> thread_class =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread);
- if (thread_class->IsAssignableFrom(c)) {
- return JDWP::JT_THREAD;
- }
- }
- {
- ObjPtr<mirror::Class> thread_group_class =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ThreadGroup);
- if (thread_group_class->IsAssignableFrom(c)) {
- return JDWP::JT_THREAD_GROUP;
- }
- }
- {
- ObjPtr<mirror::Class> class_loader_class =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ClassLoader);
- if (class_loader_class->IsAssignableFrom(c)) {
- return JDWP::JT_CLASS_LOADER;
- }
- }
- return JDWP::JT_OBJECT;
-}
-
-/*
- * Objects declared to hold Object might actually hold a more specific
- * type. The debugger may take a special interest in these (e.g. it
- * wants to display the contents of Strings), so we want to return an
- * appropriate tag.
- *
- * Null objects are tagged JT_OBJECT.
- */
-JDWP::JdwpTag Dbg::TagFromObject(const ScopedObjectAccessUnchecked& soa, ObjPtr<mirror::Object> o) {
- return (o == nullptr) ? JDWP::JT_OBJECT : TagFromClass(soa, o->GetClass());
-}
-
-static bool IsPrimitiveTag(JDWP::JdwpTag tag) {
- switch (tag) {
- case JDWP::JT_BOOLEAN:
- case JDWP::JT_BYTE:
- case JDWP::JT_CHAR:
- case JDWP::JT_FLOAT:
- case JDWP::JT_DOUBLE:
- case JDWP::JT_INT:
- case JDWP::JT_LONG:
- case JDWP::JT_SHORT:
- case JDWP::JT_VOID:
- return true;
- default:
- return false;
- }
-}
-
-void Dbg::StartJdwp() {
- if (!gJdwpAllowed || !IsJdwpConfigured()) {
- // No JDWP for you!
- return;
- }
-
- CHECK(gRegistry == nullptr);
- gRegistry = new ObjectRegistry;
-
- {
- // Setup the Ddm listener
- ScopedObjectAccess soa(Thread::Current());
- Runtime::Current()->GetRuntimeCallbacks()->AddDdmCallback(&gDebugDdmCallback);
- }
-
- // Init JDWP if the debugger is enabled. This may connect out to a
- // debugger, passively listen for a debugger, or block waiting for a
- // debugger.
- gJdwpState = JDWP::JdwpState::Create(&gJdwpOptions);
- if (gJdwpState == nullptr) {
- // We probably failed because some other process has the port already, which means that
- // if we don't abort the user is likely to think they're talking to us when they're actually
- // talking to that other process.
- LOG(FATAL) << "Debugger thread failed to initialize";
- }
-
- // If a debugger has already attached, send the "welcome" message.
- // This may cause us to suspend all threads.
- if (gJdwpState->IsActive()) {
- ScopedObjectAccess soa(Thread::Current());
- gJdwpState->PostVMStart();
- }
-}
-
-void Dbg::StopJdwp() {
- // Post VM_DEATH event before the JDWP connection is closed (either by the JDWP thread or the
- // destruction of gJdwpState).
- if (gJdwpState != nullptr && gJdwpState->IsActive()) {
- gJdwpState->PostVMDeath();
- }
- // Prevent the JDWP thread from processing JDWP incoming packets after we close the connection.
- Dispose();
- delete gJdwpState;
- gJdwpState = nullptr;
- delete gRegistry;
- gRegistry = nullptr;
-}
void Dbg::GcDidFinish() {
if (gDdmHpifWhen != HPIF_WHEN_NEVER) {
@@ -629,2980 +136,8 @@ bool Dbg::IsJdwpAllowed() {
return gJdwpAllowed;
}
-DebugInvokeReq* Dbg::GetInvokeReq() {
- return Thread::Current()->GetInvokeReq();
-}
-
-Thread* Dbg::GetDebugThread() {
- return (gJdwpState != nullptr) ? gJdwpState->GetDebugThread() : nullptr;
-}
-
-void Dbg::ClearWaitForEventThread() {
- gJdwpState->ReleaseJdwpTokenForEvent();
-}
-
-void Dbg::Connected() {
- CHECK(!gDebuggerConnected);
- VLOG(jdwp) << "JDWP has attached";
- gDebuggerConnected = true;
- gDisposed = false;
-}
-
-bool Dbg::RequiresDeoptimization() {
- // We don't need deoptimization if everything runs with interpreter after
- // enabling -Xint mode.
- return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly();
-}
-
-void Dbg::GoActive() {
- // Enable all debugging features, including scans for breakpoints.
- // This is a no-op if we're already active.
- // Only called from the JDWP handler thread.
- if (IsDebuggerActive()) {
- return;
- }
-
- Thread* const self = Thread::Current();
- {
- // TODO: dalvik only warned if there were breakpoints left over. clear in Dbg::Disconnected?
- ReaderMutexLock mu(self, *Locks::breakpoint_lock_);
- CHECK_EQ(gBreakpoints.size(), 0U);
- }
-
- {
- MutexLock mu(self, *Locks::deoptimization_lock_);
- CHECK_EQ(deoptimization_requests_.size(), 0U);
- CHECK_EQ(full_deoptimization_event_count_, 0U);
- CHECK_EQ(dex_pc_change_event_ref_count_, 0U);
- CHECK_EQ(method_enter_event_ref_count_, 0U);
- CHECK_EQ(method_exit_event_ref_count_, 0U);
- CHECK_EQ(field_read_event_ref_count_, 0U);
- CHECK_EQ(field_write_event_ref_count_, 0U);
- CHECK_EQ(exception_catch_event_ref_count_, 0U);
- }
-
- Runtime* runtime = Runtime::Current();
- // Best effort deoptimization if the runtime is non-Java debuggable. This happens when
- // ro.debuggable is set, but the application is not debuggable, or when a standalone
- // dalvikvm invocation is not passed the debuggable option (-Xcompiler-option --debuggable).
- //
- // The performance cost of this is non-negligible during native-debugging due to the
- // forced JIT, so we keep the AOT code in that case in exchange for limited native debugging.
- ScopedSuspendAll ssa(__FUNCTION__);
- if (!runtime->IsJavaDebuggable() &&
- !runtime->GetInstrumentation()->IsForcedInterpretOnly() &&
- !runtime->IsNativeDebuggable()) {
- runtime->DeoptimizeBootImage();
- }
-
- if (RequiresDeoptimization()) {
- runtime->GetInstrumentation()->EnableDeoptimization();
- }
- instrumentation_events_ = 0;
- Runtime::DoAndMaybeSwitchInterpreter([=](){ gDebuggerActive = true; });
- runtime->GetRuntimeCallbacks()->AddClassLoadCallback(Dbg::GetClassLoadCallback());
- runtime->GetRuntimeCallbacks()->AddMethodInspectionCallback(&gDebugActiveCallback);
- LOG(INFO) << "Debugger is active";
-}
-
-void Dbg::Disconnected() {
- CHECK(gDebuggerConnected);
-
- LOG(INFO) << "Debugger is no longer active";
-
- // Suspend all threads and exclusively acquire the mutator lock. Remove the debugger as a listener
- // and clear the object registry.
- Runtime* runtime = Runtime::Current();
- Thread* self = Thread::Current();
- {
- // Required for DisableDeoptimization.
- gc::ScopedGCCriticalSection gcs(self,
- gc::kGcCauseInstrumentation,
- gc::kCollectorTypeInstrumentation);
- ScopedSuspendAll ssa(__FUNCTION__);
- // Debugger may not be active at this point.
- if (IsDebuggerActive()) {
- {
- // Since we're going to disable deoptimization, we clear the deoptimization requests queue.
- // This prevents us from having any pending deoptimization request when the debugger attaches
- // to us again while no event has been requested yet.
- MutexLock mu(self, *Locks::deoptimization_lock_);
- deoptimization_requests_.clear();
- full_deoptimization_event_count_ = 0U;
- }
- if (instrumentation_events_ != 0) {
- runtime->GetInstrumentation()->RemoveListener(&gDebugInstrumentationListener,
- instrumentation_events_);
- instrumentation_events_ = 0;
- }
- if (RequiresDeoptimization()) {
- runtime->GetInstrumentation()->DisableDeoptimization(kDbgInstrumentationKey);
- }
- Runtime::DoAndMaybeSwitchInterpreter([=](){ gDebuggerActive = false; });
- runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(Dbg::GetClassLoadCallback());
- runtime->GetRuntimeCallbacks()->RemoveMethodInspectionCallback(
- &gDebugActiveCallback);
- }
- }
-
- {
- ScopedObjectAccess soa(self);
- gRegistry->Clear();
- }
-
- gDebuggerConnected = false;
-}
-
-void Dbg::ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options) {
- CHECK_NE(jdwp_options.transport, JDWP::kJdwpTransportUnknown);
- gJdwpOptions = jdwp_options;
- gJdwpConfigured = true;
- Runtime::Current()->GetRuntimeCallbacks()->AddDebuggerControlCallback(&gDebuggerControlCallback);
-}
-
-bool Dbg::IsJdwpConfigured() {
- return gJdwpConfigured;
-}
-
-int64_t Dbg::LastDebuggerActivity() {
- return gJdwpState->LastDebuggerActivity();
-}
-
-void Dbg::UndoDebuggerSuspensions() {
- Runtime::Current()->GetThreadList()->UndoDebuggerSuspensions();
-}
-
-std::string Dbg::GetClassName(JDWP::RefTypeId class_id) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(class_id, &error);
- if (o == nullptr) {
- if (error == JDWP::ERR_NONE) {
- return "null";
- } else {
- return StringPrintf("invalid object %p", reinterpret_cast<void*>(class_id));
- }
- }
- if (!o->IsClass()) {
- return StringPrintf("non-class %p", o.Ptr()); // This is only used for debugging output anyway.
- }
- return GetClassName(o->AsClass());
-}
-
-std::string Dbg::GetClassName(ObjPtr<mirror::Class> klass) {
- if (klass == nullptr) {
- return "null";
- }
- std::string temp;
- return DescriptorToName(klass->GetDescriptor(&temp));
-}
-
-JDWP::JdwpError Dbg::GetClassObject(JDWP::RefTypeId id, JDWP::ObjectId* class_object_id) {
- JDWP::JdwpError status;
- ObjPtr<mirror::Class> c = DecodeClass(id, &status);
- if (c == nullptr) {
- *class_object_id = 0;
- return status;
- }
- *class_object_id = gRegistry->Add(c);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetSuperclass(JDWP::RefTypeId id, JDWP::RefTypeId* superclass_id) {
- JDWP::JdwpError status;
- ObjPtr<mirror::Class> c = DecodeClass(id, &status);
- if (c == nullptr) {
- *superclass_id = 0;
- return status;
- }
- if (c->IsInterface()) {
- // http://code.google.com/p/android/issues/detail?id=20856
- *superclass_id = 0;
- } else {
- *superclass_id = gRegistry->Add(c->GetSuperClass());
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetClassLoader(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(id, &error);
- if (c == nullptr) {
- return error;
- }
- expandBufAddObjectId(pReply, gRegistry->Add(c->GetClassLoader()));
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetModifiers(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(id, &error);
- if (c == nullptr) {
- return error;
- }
-
- uint32_t access_flags = c->GetAccessFlags() & kAccJavaFlagsMask;
-
- // Set ACC_SUPER. Dex files don't contain this flag but only classes are supposed to have it set,
- // not interfaces.
- // Class.getModifiers doesn't return it, but JDWP does, so we set it here.
- if ((access_flags & kAccInterface) == 0) {
- access_flags |= kAccSuper;
- }
-
- expandBufAdd4BE(pReply, access_flags);
-
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetMonitorInfo(JDWP::ObjectId object_id, JDWP::ExpandBuf* reply) {
- JDWP::JdwpError error;
- Thread* self = Thread::Current();
- StackHandleScope<1u> hs(self);
- Handle<mirror::Object> o = hs.NewHandle(gRegistry->Get<mirror::Object>(object_id, &error));
- if (o == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
-
- // Ensure all threads are suspended while we read objects' lock words.
- CHECK_EQ(self->GetState(), kRunnable);
-
- MonitorInfo monitor_info;
- {
- ScopedThreadSuspension sts(self, kSuspended);
- ScopedSuspendAll ssa(__FUNCTION__);
- monitor_info = MonitorInfo(o.Get());
- }
- if (monitor_info.owner_ != nullptr) {
- expandBufAddObjectId(reply, gRegistry->Add(monitor_info.owner_->GetPeerFromOtherThread()));
- } else {
- expandBufAddObjectId(reply, gRegistry->Add(nullptr));
- }
- expandBufAdd4BE(reply, monitor_info.entry_count_);
- expandBufAdd4BE(reply, monitor_info.waiters_.size());
- for (size_t i = 0; i < monitor_info.waiters_.size(); ++i) {
- expandBufAddObjectId(reply, gRegistry->Add(monitor_info.waiters_[i]->GetPeerFromOtherThread()));
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetOwnedMonitors(JDWP::ObjectId thread_id,
- std::vector<JDWP::ObjectId>* monitors,
- std::vector<uint32_t>* stack_depths) {
- struct OwnedMonitorVisitor : public StackVisitor {
- OwnedMonitorVisitor(Thread* thread, Context* context,
- std::vector<JDWP::ObjectId>* monitor_vector,
- std::vector<uint32_t>* stack_depth_vector)
- REQUIRES_SHARED(Locks::mutator_lock_)
- : StackVisitor(thread, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
- current_stack_depth(0),
- monitors(monitor_vector),
- stack_depths(stack_depth_vector) {}
-
- // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses
- // annotalysis.
- bool VisitFrame() override NO_THREAD_SAFETY_ANALYSIS {
- if (!GetMethod()->IsRuntimeMethod()) {
- Monitor::VisitLocks(this, AppendOwnedMonitors, this);
- ++current_stack_depth;
- }
- return true;
- }
-
- static void AppendOwnedMonitors(ObjPtr<mirror::Object> owned_monitor, void* arg)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- OwnedMonitorVisitor* visitor = reinterpret_cast<OwnedMonitorVisitor*>(arg);
- visitor->monitors->push_back(gRegistry->Add(owned_monitor));
- visitor->stack_depths->push_back(visitor->current_stack_depth);
- }
-
- size_t current_stack_depth;
- std::vector<JDWP::ObjectId>* const monitors;
- std::vector<uint32_t>* const stack_depths;
- };
-
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (thread == nullptr) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
- std::unique_ptr<Context> context(Context::Create());
- OwnedMonitorVisitor visitor(thread, context.get(), monitors, stack_depths);
- visitor.WalkStack();
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetContendedMonitor(JDWP::ObjectId thread_id,
- JDWP::ObjectId* contended_monitor) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- *contended_monitor = 0;
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (thread == nullptr) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
- ObjPtr<mirror::Object> contended_monitor_obj = Monitor::GetContendedMonitor(thread);
- // Add() requires the thread_list_lock_ not held to avoid the lock
- // level violation.
- *contended_monitor = gRegistry->Add(contended_monitor_obj);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetInstanceCounts(const std::vector<JDWP::RefTypeId>& class_ids,
- std::vector<uint64_t>* counts) {
- gc::Heap* heap = Runtime::Current()->GetHeap();
- heap->CollectGarbage(/* clear_soft_references= */ false, gc::GcCause::kGcCauseDebugger);
- VariableSizedHandleScope hs(Thread::Current());
- std::vector<Handle<mirror::Class>> classes;
- counts->clear();
- for (size_t i = 0; i < class_ids.size(); ++i) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_ids[i], &error);
- if (c == nullptr) {
- return error;
- }
- classes.push_back(hs.NewHandle(c));
- counts->push_back(0);
- }
- heap->CountInstances(classes, false, &(*counts)[0]);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetInstances(JDWP::RefTypeId class_id, int32_t max_count,
- std::vector<JDWP::ObjectId>* instances) {
- gc::Heap* heap = Runtime::Current()->GetHeap();
- // We only want reachable instances, so do a GC.
- heap->CollectGarbage(/* clear_soft_references= */ false, gc::GcCause::kGcCauseDebugger);
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
- VariableSizedHandleScope hs(Thread::Current());
- std::vector<Handle<mirror::Object>> raw_instances;
- Runtime::Current()->GetHeap()->GetInstances(hs,
- hs.NewHandle(c),
- /* use_is_assignable_from= */ false,
- max_count,
- raw_instances);
- for (size_t i = 0; i < raw_instances.size(); ++i) {
- instances->push_back(gRegistry->Add(raw_instances[i].Get()));
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetReferringObjects(JDWP::ObjectId object_id, int32_t max_count,
- std::vector<JDWP::ObjectId>* referring_objects) {
- gc::Heap* heap = Runtime::Current()->GetHeap();
- heap->CollectGarbage(/* clear_soft_references= */ false, gc::GcCause::kGcCauseDebugger);
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- if (o == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- VariableSizedHandleScope hs(Thread::Current());
- std::vector<Handle<mirror::Object>> raw_instances;
- heap->GetReferringObjects(hs, hs.NewHandle(o), max_count, raw_instances);
- for (size_t i = 0; i < raw_instances.size(); ++i) {
- referring_objects->push_back(gRegistry->Add(raw_instances[i].Get()));
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::DisableCollection(JDWP::ObjectId object_id) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- if (o == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- gRegistry->DisableCollection(object_id);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::EnableCollection(JDWP::ObjectId object_id) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- // Unlike DisableCollection, JDWP specs do not state an invalid object causes an error. The RI
- // also ignores these cases and never return an error. However it's not obvious why this command
- // should behave differently from DisableCollection and IsCollected commands. So let's be more
- // strict and return an error if this happens.
- if (o == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- gRegistry->EnableCollection(object_id);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::IsCollected(JDWP::ObjectId object_id, bool* is_collected) {
- *is_collected = true;
- if (object_id == 0) {
- // Null object id is invalid.
- return JDWP::ERR_INVALID_OBJECT;
- }
- // JDWP specs state an INVALID_OBJECT error is returned if the object ID is not valid. However
- // the RI seems to ignore this and assume object has been collected.
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- if (o != nullptr) {
- *is_collected = gRegistry->IsCollected(object_id);
- }
- return JDWP::ERR_NONE;
-}
-
-void Dbg::DisposeObject(JDWP::ObjectId object_id, uint32_t reference_count) {
- gRegistry->DisposeObject(object_id, reference_count);
-}
-
-JDWP::JdwpTypeTag Dbg::GetTypeTag(ObjPtr<mirror::Class> klass) {
- DCHECK(klass != nullptr);
- if (klass->IsArrayClass()) {
- return JDWP::TT_ARRAY;
- } else if (klass->IsInterface()) {
- return JDWP::TT_INTERFACE;
- } else {
- return JDWP::TT_CLASS;
- }
-}
-
-JDWP::JdwpError Dbg::GetReflectedType(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
-
- JDWP::JdwpTypeTag type_tag = GetTypeTag(c);
- expandBufAdd1(pReply, type_tag);
- expandBufAddRefTypeId(pReply, class_id);
- return JDWP::ERR_NONE;
-}
-
-// Get the complete list of reference classes (i.e. all classes except
-// the primitive types).
-// Returns a newly-allocated buffer full of RefTypeId values.
-class ClassListCreator : public ClassVisitor {
- public:
- explicit ClassListCreator(std::vector<JDWP::RefTypeId>* classes) : classes_(classes) {}
-
- bool operator()(ObjPtr<mirror::Class> c) override REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!c->IsPrimitive()) {
- classes_->push_back(Dbg::GetObjectRegistry()->AddRefType(c));
- }
- return true;
- }
-
- private:
- std::vector<JDWP::RefTypeId>* const classes_;
-};
-
-void Dbg::GetClassList(std::vector<JDWP::RefTypeId>* classes) {
- ClassListCreator clc(classes);
- Runtime::Current()->GetClassLinker()->VisitClassesWithoutClassesLock(&clc);
-}
-
-JDWP::JdwpError Dbg::GetClassInfo(JDWP::RefTypeId class_id, JDWP::JdwpTypeTag* pTypeTag,
- uint32_t* pStatus, std::string* pDescriptor) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
-
- if (c->IsArrayClass()) {
- *pStatus = JDWP::CS_VERIFIED | JDWP::CS_PREPARED;
- *pTypeTag = JDWP::TT_ARRAY;
- } else {
- if (c->IsErroneous()) {
- *pStatus = JDWP::CS_ERROR;
- } else {
- *pStatus = JDWP::CS_VERIFIED | JDWP::CS_PREPARED | JDWP::CS_INITIALIZED;
- }
- *pTypeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
- }
-
- if (pDescriptor != nullptr) {
- std::string temp;
- *pDescriptor = c->GetDescriptor(&temp);
- }
- return JDWP::ERR_NONE;
-}
-
-void Dbg::FindLoadedClassBySignature(const char* descriptor, std::vector<JDWP::RefTypeId>* ids) {
- std::vector<ObjPtr<mirror::Class>> classes;
- Runtime::Current()->GetClassLinker()->LookupClasses(descriptor, classes);
- ids->clear();
- for (ObjPtr<mirror::Class> c : classes) {
- ids->push_back(gRegistry->Add(c));
- }
-}
-
-JDWP::JdwpError Dbg::GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- if (o == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
-
- JDWP::JdwpTypeTag type_tag = GetTypeTag(o->GetClass());
- JDWP::RefTypeId type_id = gRegistry->AddRefType(o->GetClass());
-
- expandBufAdd1(pReply, type_tag);
- expandBufAddRefTypeId(pReply, type_id);
-
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetSignature(JDWP::RefTypeId class_id, std::string* signature) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
- std::string temp;
- *signature = c->GetDescriptor(&temp);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetSourceDebugExtension(JDWP::RefTypeId class_id,
- std::string* extension_data) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
- StackHandleScope<1> hs(Thread::Current());
- Handle<mirror::Class> klass(hs.NewHandle(c));
- const char* data = annotations::GetSourceDebugExtension(klass);
- if (data == nullptr) {
- return JDWP::ERR_ABSENT_INFORMATION;
- }
- *extension_data = data;
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetSourceFile(JDWP::RefTypeId class_id, std::string* result) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
- const char* source_file = c->GetSourceFile();
- if (source_file == nullptr) {
- return JDWP::ERR_ABSENT_INFORMATION;
- }
- *result = source_file;
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetObjectTag(JDWP::ObjectId object_id, uint8_t* tag) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(object_id, &error);
- if (error != JDWP::ERR_NONE) {
- *tag = JDWP::JT_VOID;
- return error;
- }
- *tag = TagFromObject(soa, o);
- return JDWP::ERR_NONE;
-}
-
-size_t Dbg::GetTagWidth(JDWP::JdwpTag tag) {
- switch (tag) {
- case JDWP::JT_VOID:
- return 0;
- case JDWP::JT_BYTE:
- case JDWP::JT_BOOLEAN:
- return 1;
- case JDWP::JT_CHAR:
- case JDWP::JT_SHORT:
- return 2;
- case JDWP::JT_FLOAT:
- case JDWP::JT_INT:
- return 4;
- case JDWP::JT_ARRAY:
- case JDWP::JT_OBJECT:
- case JDWP::JT_STRING:
- case JDWP::JT_THREAD:
- case JDWP::JT_THREAD_GROUP:
- case JDWP::JT_CLASS_LOADER:
- case JDWP::JT_CLASS_OBJECT:
- return sizeof(JDWP::ObjectId);
- case JDWP::JT_DOUBLE:
- case JDWP::JT_LONG:
- return 8;
- default:
- LOG(FATAL) << "Unknown tag " << tag;
- UNREACHABLE();
- }
-}
-
-JDWP::JdwpError Dbg::GetArrayLength(JDWP::ObjectId array_id, int32_t* length) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Array> a = DecodeNonNullArray(array_id, &error);
- if (a == nullptr) {
- return error;
- }
- *length = a->GetLength();
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::OutputArray(JDWP::ObjectId array_id,
- int offset,
- int count,
- JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Array> a = DecodeNonNullArray(array_id, &error);
- if (a == nullptr) {
- return error;
- }
-
- if (offset < 0 || count < 0 || offset > a->GetLength() || a->GetLength() - offset < count) {
- LOG(WARNING) << __FUNCTION__ << " access out of bounds: offset=" << offset << "; count=" << count;
- return JDWP::ERR_INVALID_LENGTH;
- }
- JDWP::JdwpTag element_tag = BasicTagFromClass(a->GetClass()->GetComponentType());
- expandBufAdd1(pReply, element_tag);
- expandBufAdd4BE(pReply, count);
-
- if (IsPrimitiveTag(element_tag)) {
- size_t width = GetTagWidth(element_tag);
- uint8_t* dst = expandBufAddSpace(pReply, count * width);
- if (width == 8) {
- const uint64_t* src8 = reinterpret_cast<uint64_t*>(a->GetRawData(sizeof(uint64_t), 0));
- for (int i = 0; i < count; ++i) JDWP::Write8BE(&dst, src8[offset + i]);
- } else if (width == 4) {
- const uint32_t* src4 = reinterpret_cast<uint32_t*>(a->GetRawData(sizeof(uint32_t), 0));
- for (int i = 0; i < count; ++i) JDWP::Write4BE(&dst, src4[offset + i]);
- } else if (width == 2) {
- const uint16_t* src2 = reinterpret_cast<uint16_t*>(a->GetRawData(sizeof(uint16_t), 0));
- for (int i = 0; i < count; ++i) JDWP::Write2BE(&dst, src2[offset + i]);
- } else {
- const uint8_t* src = reinterpret_cast<uint8_t*>(a->GetRawData(sizeof(uint8_t), 0));
- memcpy(dst, &src[offset * width], count * width);
- }
- } else {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- ObjPtr<mirror::ObjectArray<mirror::Object>> oa = a->AsObjectArray<mirror::Object>();
- for (int i = 0; i < count; ++i) {
- ObjPtr<mirror::Object> element = oa->Get(offset + i);
- JDWP::JdwpTag specific_tag = (element != nullptr) ? TagFromObject(soa, element)
- : element_tag;
- expandBufAdd1(pReply, specific_tag);
- expandBufAddObjectId(pReply, gRegistry->Add(element));
- }
- }
-
- return JDWP::ERR_NONE;
-}
-
-template <typename T>
-static void CopyArrayData(ObjPtr<mirror::Array> a, JDWP::Request* src, int offset, int count)
- NO_THREAD_SAFETY_ANALYSIS {
- // TODO: fix when annotalysis correctly handles non-member functions.
- DCHECK(a->GetClass()->IsPrimitiveArray());
-
- T* dst = reinterpret_cast<T*>(a->GetRawData(sizeof(T), offset));
- for (int i = 0; i < count; ++i) {
- *dst++ = src->ReadValue(sizeof(T));
- }
-}
-
-JDWP::JdwpError Dbg::SetArrayElements(JDWP::ObjectId array_id, int offset, int count,
- JDWP::Request* request) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Array> dst = DecodeNonNullArray(array_id, &error);
- if (dst == nullptr) {
- return error;
- }
-
- if (offset < 0 || count < 0 || offset > dst->GetLength() || dst->GetLength() - offset < count) {
- LOG(WARNING) << __FUNCTION__ << " access out of bounds: offset=" << offset << "; count=" << count;
- return JDWP::ERR_INVALID_LENGTH;
- }
- JDWP::JdwpTag element_tag = BasicTagFromClass(dst->GetClass()->GetComponentType());
-
- if (IsPrimitiveTag(element_tag)) {
- size_t width = GetTagWidth(element_tag);
- if (width == 8) {
- CopyArrayData<uint64_t>(dst, request, offset, count);
- } else if (width == 4) {
- CopyArrayData<uint32_t>(dst, request, offset, count);
- } else if (width == 2) {
- CopyArrayData<uint16_t>(dst, request, offset, count);
- } else {
- CopyArrayData<uint8_t>(dst, request, offset, count);
- }
- } else {
- ObjPtr<mirror::ObjectArray<mirror::Object>> oa = dst->AsObjectArray<mirror::Object>();
- for (int i = 0; i < count; ++i) {
- JDWP::ObjectId id = request->ReadObjectId();
- ObjPtr<mirror::Object> o = gRegistry->Get<mirror::Object>(id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- // Check if the object's type is compatible with the array's type.
- if (o != nullptr && !o->InstanceOf(oa->GetClass()->GetComponentType())) {
- return JDWP::ERR_TYPE_MISMATCH;
- }
- oa->Set<false>(offset + i, o);
- }
- }
-
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::CreateString(const std::string& str, JDWP::ObjectId* new_string_id) {
- Thread* self = Thread::Current();
- ObjPtr<mirror::String> new_string = mirror::String::AllocFromModifiedUtf8(self, str.c_str());
- if (new_string == nullptr) {
- DCHECK(self->IsExceptionPending());
- self->ClearException();
- LOG(ERROR) << "Could not allocate string";
- *new_string_id = 0;
- return JDWP::ERR_OUT_OF_MEMORY;
- }
- *new_string_id = gRegistry->Add(new_string);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::CreateObject(JDWP::RefTypeId class_id, JDWP::ObjectId* new_object_id) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- *new_object_id = 0;
- return error;
- }
- Thread* self = Thread::Current();
- ObjPtr<mirror::Object> new_object;
- if (c->IsStringClass()) {
- // Special case for java.lang.String.
- gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
- new_object = mirror::String::AllocEmptyString(self, allocator_type);
- } else {
- new_object = c->AllocObject(self);
- }
- if (new_object == nullptr) {
- DCHECK(self->IsExceptionPending());
- self->ClearException();
- LOG(ERROR) << "Could not allocate object of type " << mirror::Class::PrettyDescriptor(c);
- *new_object_id = 0;
- return JDWP::ERR_OUT_OF_MEMORY;
- }
- *new_object_id = gRegistry->Add(new_object);
- return JDWP::ERR_NONE;
-}
-
-/*
- * Used by Eclipse's "Display" view to evaluate "new byte[5]" to get "(byte[]) [0, 0, 0, 0, 0]".
- */
-JDWP::JdwpError Dbg::CreateArrayObject(JDWP::RefTypeId array_class_id, uint32_t length,
- JDWP::ObjectId* new_array_id) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(array_class_id, &error);
- if (c == nullptr) {
- *new_array_id = 0;
- return error;
- }
- Thread* self = Thread::Current();
- gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
- ObjPtr<mirror::Array> new_array =
- mirror::Array::Alloc(self, c, length, c->GetComponentSizeShift(), allocator_type);
- if (new_array == nullptr) {
- DCHECK(self->IsExceptionPending());
- self->ClearException();
- LOG(ERROR) << "Could not allocate array of type " << mirror::Class::PrettyDescriptor(c);
- *new_array_id = 0;
- return JDWP::ERR_OUT_OF_MEMORY;
- }
- *new_array_id = gRegistry->Add(new_array);
- return JDWP::ERR_NONE;
-}
-
-JDWP::FieldId Dbg::ToFieldId(const ArtField* f) {
- return static_cast<JDWP::FieldId>(reinterpret_cast<uintptr_t>(f));
-}
-
-static JDWP::MethodId ToMethodId(ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return static_cast<JDWP::MethodId>(
- reinterpret_cast<uintptr_t>(m->GetCanonicalMethod(kRuntimePointerSize)));
-}
-
-static ArtField* FromFieldId(JDWP::FieldId fid)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return reinterpret_cast<ArtField*>(static_cast<uintptr_t>(fid));
-}
-
-static ArtMethod* FromMethodId(JDWP::MethodId mid)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return reinterpret_cast<ArtMethod*>(static_cast<uintptr_t>(mid));
-}
-
-bool Dbg::MatchThread(JDWP::ObjectId expected_thread_id, Thread* event_thread) {
- CHECK(event_thread != nullptr);
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> expected_thread_peer = gRegistry->Get<mirror::Object>(
- expected_thread_id, &error);
- return expected_thread_peer == event_thread->GetPeerFromOtherThread();
-}
-
-bool Dbg::MatchLocation(const JDWP::JdwpLocation& expected_location,
- const JDWP::EventLocation& event_location) {
- if (expected_location.dex_pc != event_location.dex_pc) {
- return false;
- }
- ArtMethod* m = FromMethodId(expected_location.method_id);
- return m == event_location.method;
-}
-
-bool Dbg::MatchType(ObjPtr<mirror::Class> event_class, JDWP::RefTypeId class_id) {
- if (event_class == nullptr) {
- return false;
- }
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> expected_class = DecodeClass(class_id, &error);
- CHECK(expected_class != nullptr);
- return expected_class->IsAssignableFrom(event_class);
-}
-
-bool Dbg::MatchField(JDWP::RefTypeId expected_type_id,
- JDWP::FieldId expected_field_id,
- ArtField* event_field) {
- ArtField* expected_field = FromFieldId(expected_field_id);
- if (expected_field != event_field) {
- return false;
- }
- return Dbg::MatchType(event_field->GetDeclaringClass(), expected_type_id);
-}
-
-bool Dbg::MatchInstance(JDWP::ObjectId expected_instance_id,
- ObjPtr<mirror::Object> event_instance) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> modifier_instance =
- gRegistry->Get<mirror::Object>(expected_instance_id, &error);
- return modifier_instance == event_instance;
-}
-
-void Dbg::SetJdwpLocation(JDWP::JdwpLocation* location, ArtMethod* m, uint32_t dex_pc) {
- if (m == nullptr) {
- memset(location, 0, sizeof(*location));
- } else {
- ObjPtr<mirror::Class> c = m->GetDeclaringClass();
- location->type_tag = GetTypeTag(c);
- location->class_id = gRegistry->AddRefType(c);
- // The RI Seems to return 0 for all obsolete methods. For compatibility we shall do the same.
- location->method_id = m->IsObsolete() ? 0 : ToMethodId(m);
- location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint64_t>(-1) : dex_pc;
- }
-}
-
-std::string Dbg::GetMethodName(JDWP::MethodId method_id) {
- ArtMethod* m = FromMethodId(method_id);
- if (m == nullptr) {
- return "null";
- }
- return m->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetName();
-}
-
-bool Dbg::IsMethodObsolete(JDWP::MethodId method_id) {
- ArtMethod* m = FromMethodId(method_id);
- if (m == nullptr) {
- // NB Since we return 0 as MID for obsolete methods we want to default to true here.
- return true;
- }
- return m->IsObsolete();
-}
-
-std::string Dbg::GetFieldName(JDWP::FieldId field_id) {
- ArtField* f = FromFieldId(field_id);
- if (f == nullptr) {
- return "null";
- }
- return f->GetName();
-}
-
-/*
- * Augment the access flags for synthetic methods and fields by setting
- * the (as described by the spec) "0xf0000000 bit". Also, strip out any
- * flags not specified by the Java programming language.
- */
-static uint32_t MangleAccessFlags(uint32_t accessFlags) {
- accessFlags &= kAccJavaFlagsMask;
- if ((accessFlags & kAccSynthetic) != 0) {
- accessFlags |= 0xf0000000;
- }
- return accessFlags;
-}
-
-/*
- * Circularly shifts registers so that arguments come first. Debuggers
- * expect slots to begin with arguments, but dex code places them at
- * the end.
- */
-static uint16_t MangleSlot(uint16_t slot, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CodeItemDataAccessor accessor(m->DexInstructionData());
- if (!accessor.HasCodeItem()) {
- // We should not get here for a method without code (native, proxy or abstract). Log it and
- // return the slot as is since all registers are arguments.
- LOG(WARNING) << "Trying to mangle slot for method without code " << m->PrettyMethod();
- return slot;
- }
- uint16_t ins_size = accessor.InsSize();
- uint16_t locals_size = accessor.RegistersSize() - ins_size;
- if (slot >= locals_size) {
- return slot - locals_size;
- } else {
- return slot + ins_size;
- }
-}
-
-static size_t GetMethodNumArgRegistersIncludingThis(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t num_registers = ArtMethod::NumArgRegisters(method->GetShorty());
- if (!method->IsStatic()) {
- ++num_registers;
- }
- return num_registers;
-}
-
-/*
- * Circularly shifts registers so that arguments come last. Reverts
- * slots to dex style argument placement.
- */
-static uint16_t DemangleSlot(uint16_t slot, ArtMethod* m, JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CodeItemDataAccessor accessor(m->DexInstructionData());
- if (!accessor.HasCodeItem()) {
- // We should not get here for a method without code (native, proxy or abstract). Log it and
- // return the slot as is since all registers are arguments.
- LOG(WARNING) << "Trying to demangle slot for method without code "
- << m->PrettyMethod();
- uint16_t vreg_count = GetMethodNumArgRegistersIncludingThis(m);
- if (slot < vreg_count) {
- *error = JDWP::ERR_NONE;
- return slot;
- }
- } else {
- if (slot < accessor.RegistersSize()) {
- uint16_t ins_size = accessor.InsSize();
- uint16_t locals_size = accessor.RegistersSize() - ins_size;
- *error = JDWP::ERR_NONE;
- return (slot < ins_size) ? slot + locals_size : slot - ins_size;
- }
- }
-
- // Slot is invalid in the method.
- LOG(ERROR) << "Invalid local slot " << slot << " for method " << m->PrettyMethod();
- *error = JDWP::ERR_INVALID_SLOT;
- return DexFile::kDexNoIndex16;
-}
-
-JDWP::JdwpError Dbg::OutputDeclaredFields(JDWP::RefTypeId class_id, bool with_generic,
- JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
-
- size_t instance_field_count = c->NumInstanceFields();
- size_t static_field_count = c->NumStaticFields();
-
- expandBufAdd4BE(pReply, instance_field_count + static_field_count);
-
- for (size_t i = 0; i < instance_field_count + static_field_count; ++i) {
- ArtField* f = (i < instance_field_count) ? c->GetInstanceField(i) :
- c->GetStaticField(i - instance_field_count);
- expandBufAddFieldId(pReply, ToFieldId(f));
- expandBufAddUtf8String(pReply, f->GetName());
- expandBufAddUtf8String(pReply, f->GetTypeDescriptor());
- if (with_generic) {
- static const char genericSignature[1] = "";
- expandBufAddUtf8String(pReply, genericSignature);
- }
- expandBufAdd4BE(pReply, MangleAccessFlags(f->GetAccessFlags()));
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::OutputDeclaredMethods(JDWP::RefTypeId class_id, bool with_generic,
- JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
-
- expandBufAdd4BE(pReply, c->NumMethods());
-
- auto* cl = Runtime::Current()->GetClassLinker();
- auto ptr_size = cl->GetImagePointerSize();
- for (ArtMethod& m : c->GetMethods(ptr_size)) {
- expandBufAddMethodId(pReply, ToMethodId(&m));
- expandBufAddUtf8String(pReply, m.GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetName());
- expandBufAddUtf8String(
- pReply, m.GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetSignature().ToString());
- if (with_generic) {
- const char* generic_signature = "";
- expandBufAddUtf8String(pReply, generic_signature);
- }
- expandBufAdd4BE(pReply, MangleAccessFlags(m.GetAccessFlags()));
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::OutputDeclaredInterfaces(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) {
- JDWP::JdwpError error;
- Thread* self = Thread::Current();
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
- size_t interface_count = c->NumDirectInterfaces();
- expandBufAdd4BE(pReply, interface_count);
- for (size_t i = 0; i < interface_count; ++i) {
- ObjPtr<mirror::Class> interface = mirror::Class::GetDirectInterface(self, c, i);
- DCHECK(interface != nullptr);
- expandBufAddRefTypeId(pReply, gRegistry->AddRefType(interface));
- }
- return JDWP::ERR_NONE;
-}
-
-void Dbg::OutputLineTable(JDWP::RefTypeId, JDWP::MethodId method_id, JDWP::ExpandBuf* pReply) {
- ArtMethod* m = FromMethodId(method_id);
- CodeItemDebugInfoAccessor accessor(m->DexInstructionDebugInfo());
- uint64_t start, end;
- if (!accessor.HasCodeItem()) {
- DCHECK(m->IsNative() || m->IsProxyMethod());
- start = -1;
- end = -1;
- } else {
- start = 0;
- // Return the index of the last instruction
- end = accessor.InsnsSizeInCodeUnits() - 1;
- }
-
- expandBufAdd8BE(pReply, start);
- expandBufAdd8BE(pReply, end);
-
- // Add numLines later
- size_t numLinesOffset = expandBufGetLength(pReply);
- expandBufAdd4BE(pReply, 0);
-
- int numItems = 0;
- accessor.DecodeDebugPositionInfo([&](const DexFile::PositionInfo& entry) {
- expandBufAdd8BE(pReply, entry.address_);
- expandBufAdd4BE(pReply, entry.line_);
- numItems++;
- return false;
- });
-
- JDWP::Set4BE(expandBufGetBuffer(pReply) + numLinesOffset, numItems);
-}
-
-void Dbg::OutputVariableTable(JDWP::RefTypeId, JDWP::MethodId method_id, bool with_generic,
- JDWP::ExpandBuf* pReply) {
- ArtMethod* m = FromMethodId(method_id);
- CodeItemDebugInfoAccessor accessor(m->DexInstructionDebugInfo());
-
- // arg_count considers doubles and longs to take 2 units.
- // variable_count considers everything to take 1 unit.
- expandBufAdd4BE(pReply, GetMethodNumArgRegistersIncludingThis(m));
-
- // We don't know the total number of variables yet, so leave a blank and update it later.
- size_t variable_count_offset = expandBufGetLength(pReply);
- expandBufAdd4BE(pReply, 0);
-
- size_t variable_count = 0;
-
- if (accessor.HasCodeItem()) {
- accessor.DecodeDebugLocalInfo(m->IsStatic(),
- m->GetDexMethodIndex(),
- [&](const DexFile::LocalInfo& entry)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint16_t slot = entry.reg_;
- VLOG(jdwp) << StringPrintf(" %2zd: %d(%d) '%s' '%s' '%s' actual slot=%d mangled slot=%d",
- variable_count,
- entry.start_address_,
- entry.end_address_ - entry.start_address_,
- entry.name_,
- entry.descriptor_, entry.signature_,
- slot,
- MangleSlot(slot, m));
-
- slot = MangleSlot(slot, m);
-
- expandBufAdd8BE(pReply, entry.start_address_);
- expandBufAddUtf8String(pReply, entry.name_);
- expandBufAddUtf8String(pReply, entry.descriptor_);
- if (with_generic) {
- expandBufAddUtf8String(pReply, entry.signature_);
- }
- expandBufAdd4BE(pReply, entry.end_address_- entry.start_address_);
- expandBufAdd4BE(pReply, slot);
-
- ++variable_count;
- });
- }
-
- JDWP::Set4BE(expandBufGetBuffer(pReply) + variable_count_offset, variable_count);
-}
-
-void Dbg::OutputMethodReturnValue(JDWP::MethodId method_id, const JValue* return_value,
- JDWP::ExpandBuf* pReply) {
- ArtMethod* m = FromMethodId(method_id);
- JDWP::JdwpTag tag = BasicTagFromDescriptor(m->GetShorty());
- OutputJValue(tag, return_value, pReply);
-}
-
-void Dbg::OutputFieldValue(JDWP::FieldId field_id, const JValue* field_value,
- JDWP::ExpandBuf* pReply) {
- ArtField* f = FromFieldId(field_id);
- JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor());
- OutputJValue(tag, field_value, pReply);
-}
-
-JDWP::JdwpError Dbg::GetBytecodes(JDWP::RefTypeId, JDWP::MethodId method_id,
- std::vector<uint8_t>* bytecodes) {
- ArtMethod* m = FromMethodId(method_id);
- if (m == nullptr) {
- return JDWP::ERR_INVALID_METHODID;
- }
- CodeItemDataAccessor accessor(m->DexInstructionData());
- size_t byte_count = accessor.InsnsSizeInCodeUnits() * 2;
- const uint8_t* begin = reinterpret_cast<const uint8_t*>(accessor.Insns());
- const uint8_t* end = begin + byte_count;
- for (const uint8_t* p = begin; p != end; ++p) {
- bytecodes->push_back(*p);
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpTag Dbg::GetFieldBasicTag(JDWP::FieldId field_id) {
- return BasicTagFromDescriptor(FromFieldId(field_id)->GetTypeDescriptor());
-}
-
-JDWP::JdwpTag Dbg::GetStaticFieldBasicTag(JDWP::FieldId field_id) {
- return BasicTagFromDescriptor(FromFieldId(field_id)->GetTypeDescriptor());
-}
-
-static JValue GetArtFieldValue(ArtField* f, ObjPtr<mirror::Object> o)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Primitive::Type fieldType = f->GetTypeAsPrimitiveType();
- JValue field_value;
- switch (fieldType) {
- case Primitive::kPrimBoolean:
- field_value.SetZ(f->GetBoolean(o));
- return field_value;
-
- case Primitive::kPrimByte:
- field_value.SetB(f->GetByte(o));
- return field_value;
-
- case Primitive::kPrimChar:
- field_value.SetC(f->GetChar(o));
- return field_value;
-
- case Primitive::kPrimShort:
- field_value.SetS(f->GetShort(o));
- return field_value;
-
- case Primitive::kPrimInt:
- case Primitive::kPrimFloat:
- // Int and Float must be treated as 32-bit values in JDWP.
- field_value.SetI(f->GetInt(o));
- return field_value;
-
- case Primitive::kPrimLong:
- case Primitive::kPrimDouble:
- // Long and Double must be treated as 64-bit values in JDWP.
- field_value.SetJ(f->GetLong(o));
- return field_value;
-
- case Primitive::kPrimNot:
- field_value.SetL(f->GetObject(o));
- return field_value;
-
- case Primitive::kPrimVoid:
- LOG(FATAL) << "Attempt to read from field of type 'void'";
- UNREACHABLE();
- }
- LOG(FATAL) << "Attempt to read from field of unknown type";
- UNREACHABLE();
-}
-
-static JDWP::JdwpError GetFieldValueImpl(JDWP::RefTypeId ref_type_id, JDWP::ObjectId object_id,
- JDWP::FieldId field_id, JDWP::ExpandBuf* pReply,
- bool is_static)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Class> c = DecodeClass(ref_type_id, &error);
- if (ref_type_id != 0 && c == nullptr) {
- return error;
- }
-
- Thread* self = Thread::Current();
- StackHandleScope<2> hs(self);
- MutableHandle<mirror::Object>
- o(hs.NewHandle(Dbg::GetObjectRegistry()->Get<mirror::Object>(object_id, &error)));
- if ((!is_static && o == nullptr) || error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- ArtField* f = FromFieldId(field_id);
-
- ObjPtr<mirror::Class> receiver_class = c;
- if (receiver_class == nullptr && o != nullptr) {
- receiver_class = o->GetClass();
- }
-
- // TODO: should we give up now if receiver_class is null?
- if (receiver_class != nullptr && !f->GetDeclaringClass()->IsAssignableFrom(receiver_class)) {
- LOG(INFO) << "ERR_INVALID_FIELDID: " << f->PrettyField() << " "
- << receiver_class->PrettyClass();
- return JDWP::ERR_INVALID_FIELDID;
- }
-
- // Ensure the field's class is initialized.
- Handle<mirror::Class> klass(hs.NewHandle(f->GetDeclaringClass()));
- if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, klass, true, false)) {
- LOG(WARNING) << "Not able to initialize class for SetValues: "
- << mirror::Class::PrettyClass(klass.Get());
- }
-
- // The RI only enforces the static/non-static mismatch in one direction.
- // TODO: should we change the tests and check both?
- if (is_static) {
- if (!f->IsStatic()) {
- return JDWP::ERR_INVALID_FIELDID;
- }
- } else {
- if (f->IsStatic()) {
- LOG(WARNING) << "Ignoring non-nullptr receiver for ObjectReference.GetValues"
- << " on static field " << f->PrettyField();
- }
- }
- if (f->IsStatic()) {
- o.Assign(f->GetDeclaringClass());
- }
-
- JValue field_value(GetArtFieldValue(f, o.Get()));
- JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor());
- Dbg::OutputJValue(tag, &field_value, pReply);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id,
- JDWP::ExpandBuf* pReply) {
- return GetFieldValueImpl(0, object_id, field_id, pReply, false);
-}
-
-JDWP::JdwpError Dbg::GetStaticFieldValue(JDWP::RefTypeId ref_type_id, JDWP::FieldId field_id,
- JDWP::ExpandBuf* pReply) {
- return GetFieldValueImpl(ref_type_id, 0, field_id, pReply, true);
-}
-
-static JDWP::JdwpError SetArtFieldValue(ArtField* f,
- ObjPtr<mirror::Object> o,
- uint64_t value,
- int width)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Primitive::Type fieldType = f->GetTypeAsPrimitiveType();
- // Debugging only happens at runtime so we know we are not running in a transaction.
- static constexpr bool kNoTransactionMode = false;
- switch (fieldType) {
- case Primitive::kPrimBoolean:
- CHECK_EQ(width, 1);
- f->SetBoolean<kNoTransactionMode>(o, static_cast<uint8_t>(value));
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimByte:
- CHECK_EQ(width, 1);
- f->SetByte<kNoTransactionMode>(o, static_cast<uint8_t>(value));
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimChar:
- CHECK_EQ(width, 2);
- f->SetChar<kNoTransactionMode>(o, static_cast<uint16_t>(value));
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimShort:
- CHECK_EQ(width, 2);
- f->SetShort<kNoTransactionMode>(o, static_cast<int16_t>(value));
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimInt:
- case Primitive::kPrimFloat:
- CHECK_EQ(width, 4);
- // Int and Float must be treated as 32-bit values in JDWP.
- f->SetInt<kNoTransactionMode>(o, static_cast<int32_t>(value));
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimLong:
- case Primitive::kPrimDouble:
- CHECK_EQ(width, 8);
- // Long and Double must be treated as 64-bit values in JDWP.
- f->SetLong<kNoTransactionMode>(o, value);
- return JDWP::ERR_NONE;
-
- case Primitive::kPrimNot: {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> v = Dbg::GetObjectRegistry()->Get<mirror::Object>(value, &error);
- if (error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- if (v != nullptr) {
- ObjPtr<mirror::Class> field_type;
- {
- StackHandleScope<2> hs(Thread::Current());
- HandleWrapperObjPtr<mirror::Object> h_v(hs.NewHandleWrapper(&v));
- HandleWrapperObjPtr<mirror::Object> h_o(hs.NewHandleWrapper(&o));
- field_type = f->ResolveType();
- }
- if (!field_type->IsAssignableFrom(v->GetClass())) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- }
- f->SetObject<kNoTransactionMode>(o, v);
- return JDWP::ERR_NONE;
- }
-
- case Primitive::kPrimVoid:
- LOG(FATAL) << "Attempt to write to field of type 'void'";
- UNREACHABLE();
- }
- LOG(FATAL) << "Attempt to write to field of unknown type";
- UNREACHABLE();
-}
-
-static JDWP::JdwpError SetFieldValueImpl(JDWP::ObjectId object_id, JDWP::FieldId field_id,
- uint64_t value, int width, bool is_static)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JDWP::JdwpError error;
- Thread* self = Thread::Current();
- StackHandleScope<2> hs(self);
- MutableHandle<mirror::Object>
- o(hs.NewHandle(Dbg::GetObjectRegistry()->Get<mirror::Object>(object_id, &error)));
- if ((!is_static && o == nullptr) || error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- ArtField* f = FromFieldId(field_id);
-
- // Ensure the field's class is initialized.
- Handle<mirror::Class> klass(hs.NewHandle(f->GetDeclaringClass()));
- if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, klass, true, false)) {
- LOG(WARNING) << "Not able to initialize class for SetValues: "
- << mirror::Class::PrettyClass(klass.Get());
- }
-
- // The RI only enforces the static/non-static mismatch in one direction.
- // TODO: should we change the tests and check both?
- if (is_static) {
- if (!f->IsStatic()) {
- return JDWP::ERR_INVALID_FIELDID;
- }
- } else {
- if (f->IsStatic()) {
- LOG(WARNING) << "Ignoring non-nullptr receiver for ObjectReference.SetValues"
- << " on static field " << f->PrettyField();
- }
- }
- if (f->IsStatic()) {
- o.Assign(f->GetDeclaringClass());
- }
- return SetArtFieldValue(f, o.Get(), value, width);
-}
-
-JDWP::JdwpError Dbg::SetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id, uint64_t value,
- int width) {
- return SetFieldValueImpl(object_id, field_id, value, width, false);
-}
-
-JDWP::JdwpError Dbg::SetStaticFieldValue(JDWP::FieldId field_id, uint64_t value, int width) {
- return SetFieldValueImpl(0, field_id, value, width, true);
-}
-
-JDWP::JdwpError Dbg::StringToUtf8(JDWP::ObjectId string_id, std::string* str) {
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> obj = gRegistry->Get<mirror::Object>(string_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (obj == nullptr) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- ObjPtr<mirror::Class> java_lang_String =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_String);
- if (!java_lang_String->IsAssignableFrom(obj->GetClass())) {
- // This isn't a string.
- return JDWP::ERR_INVALID_STRING;
- }
- }
- *str = obj->AsString()->ToModifiedUtf8();
- return JDWP::ERR_NONE;
-}
-
-void Dbg::OutputJValue(JDWP::JdwpTag tag, const JValue* return_value, JDWP::ExpandBuf* pReply) {
- if (IsPrimitiveTag(tag)) {
- expandBufAdd1(pReply, tag);
- if (tag == JDWP::JT_BOOLEAN || tag == JDWP::JT_BYTE) {
- expandBufAdd1(pReply, return_value->GetI());
- } else if (tag == JDWP::JT_CHAR || tag == JDWP::JT_SHORT) {
- expandBufAdd2BE(pReply, return_value->GetI());
- } else if (tag == JDWP::JT_FLOAT || tag == JDWP::JT_INT) {
- expandBufAdd4BE(pReply, return_value->GetI());
- } else if (tag == JDWP::JT_DOUBLE || tag == JDWP::JT_LONG) {
- expandBufAdd8BE(pReply, return_value->GetJ());
- } else {
- CHECK_EQ(tag, JDWP::JT_VOID);
- }
- } else {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- ObjPtr<mirror::Object> value = return_value->GetL();
- expandBufAdd1(pReply, TagFromObject(soa, value));
- expandBufAddObjectId(pReply, gRegistry->Add(value));
- }
-}
-
-JDWP::JdwpError Dbg::GetThreadName(JDWP::ObjectId thread_id, std::string* name) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE && error != JDWP::ERR_THREAD_NOT_ALIVE) {
- return error;
- }
-
- // We still need to report the zombie threads' names, so we can't just call Thread::GetThreadName.
- ObjPtr<mirror::Object> thread_object = gRegistry->Get<mirror::Object>(thread_id, &error);
- CHECK(thread_object != nullptr) << error;
- ArtField* java_lang_Thread_name_field =
- jni::DecodeArtField(WellKnownClasses::java_lang_Thread_name);
- ObjPtr<mirror::String> s(java_lang_Thread_name_field->GetObject(thread_object)->AsString());
- if (s != nullptr) {
- *name = s->ToModifiedUtf8();
- }
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetThreadGroup(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> thread_object = gRegistry->Get<mirror::Object>(thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- ScopedAssertNoThreadSuspension ants("Debugger: GetThreadGroup");
- // Okay, so it's an object, but is it actually a thread?
- DecodeThread(soa, thread_id, &error);
- if (error == JDWP::ERR_THREAD_NOT_ALIVE) {
- // Zombie threads are in the null group.
- expandBufAddObjectId(pReply, JDWP::ObjectId(0));
- error = JDWP::ERR_NONE;
- } else if (error == JDWP::ERR_NONE) {
- ObjPtr<mirror::Class> c = soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread);
- CHECK(c != nullptr);
- ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group);
- CHECK(f != nullptr);
- ObjPtr<mirror::Object> group = f->GetObject(thread_object);
- CHECK(group != nullptr);
- JDWP::ObjectId thread_group_id = gRegistry->Add(group);
- expandBufAddObjectId(pReply, thread_group_id);
- }
- return error;
-}
-
-static ObjPtr<mirror::Object> DecodeThreadGroup(ScopedObjectAccessUnchecked& soa,
- JDWP::ObjectId thread_group_id,
- JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Object> thread_group =
- Dbg::GetObjectRegistry()->Get<mirror::Object>(thread_group_id, error);
- if (*error != JDWP::ERR_NONE) {
- return nullptr;
- }
- if (thread_group == nullptr) {
- *error = JDWP::ERR_INVALID_OBJECT;
- return nullptr;
- }
- ObjPtr<mirror::Class> c =
- soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ThreadGroup);
- CHECK(c != nullptr);
- if (!c->IsAssignableFrom(thread_group->GetClass())) {
- // This is not a java.lang.ThreadGroup.
- *error = JDWP::ERR_INVALID_THREAD_GROUP;
- return nullptr;
- }
- *error = JDWP::ERR_NONE;
- return thread_group;
-}
-
-JDWP::JdwpError Dbg::GetThreadGroupName(JDWP::ObjectId thread_group_id, JDWP::ExpandBuf* pReply) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> thread_group = DecodeThreadGroup(soa, thread_group_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- ScopedAssertNoThreadSuspension ants("Debugger: GetThreadGroupName");
- ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_name);
- CHECK(f != nullptr);
- ObjPtr<mirror::String> s = f->GetObject(thread_group)->AsString();
-
- std::string thread_group_name(s->ToModifiedUtf8());
- expandBufAddUtf8String(pReply, thread_group_name);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetThreadGroupParent(JDWP::ObjectId thread_group_id, JDWP::ExpandBuf* pReply) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> thread_group = DecodeThreadGroup(soa, thread_group_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- ObjPtr<mirror::Object> parent;
- {
- ScopedAssertNoThreadSuspension ants("Debugger: GetThreadGroupParent");
- ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_parent);
- CHECK(f != nullptr);
- parent = f->GetObject(thread_group);
- }
- JDWP::ObjectId parent_group_id = gRegistry->Add(parent);
- expandBufAddObjectId(pReply, parent_group_id);
- return JDWP::ERR_NONE;
-}
-
-static void GetChildThreadGroups(ObjPtr<mirror::Object> thread_group,
- std::vector<JDWP::ObjectId>* child_thread_group_ids)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK(thread_group != nullptr);
-
- // Get the int "ngroups" count of this thread group...
- ArtField* ngroups_field = jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_ngroups);
- CHECK(ngroups_field != nullptr);
- const int32_t size = ngroups_field->GetInt(thread_group);
- if (size == 0) {
- return;
- }
-
- // Get the ThreadGroup[] "groups" out of this thread group...
- ArtField* groups_field = jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_groups);
- ObjPtr<mirror::Object> groups_array = groups_field->GetObject(thread_group);
-
- CHECK(groups_array != nullptr);
- CHECK(groups_array->IsObjectArray());
-
- ObjPtr<mirror::ObjectArray<mirror::Object>> groups_array_as_array =
- groups_array->AsObjectArray<mirror::Object>();
-
- // Copy the first 'size' elements out of the array into the result.
- ObjectRegistry* registry = Dbg::GetObjectRegistry();
- for (int32_t i = 0; i < size; ++i) {
- child_thread_group_ids->push_back(registry->Add(groups_array_as_array->Get(i)));
- }
-}
-
-JDWP::JdwpError Dbg::GetThreadGroupChildren(JDWP::ObjectId thread_group_id,
- JDWP::ExpandBuf* pReply) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> thread_group = DecodeThreadGroup(soa, thread_group_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
-
- // Add child threads.
- {
- std::vector<JDWP::ObjectId> child_thread_ids;
- GetThreads(thread_group, &child_thread_ids);
- expandBufAdd4BE(pReply, child_thread_ids.size());
- for (JDWP::ObjectId child_thread_id : child_thread_ids) {
- expandBufAddObjectId(pReply, child_thread_id);
- }
- }
-
- // Add child thread groups.
- {
- std::vector<JDWP::ObjectId> child_thread_groups_ids;
- GetChildThreadGroups(thread_group, &child_thread_groups_ids);
- expandBufAdd4BE(pReply, child_thread_groups_ids.size());
- for (JDWP::ObjectId child_thread_group_id : child_thread_groups_ids) {
- expandBufAddObjectId(pReply, child_thread_group_id);
- }
- }
-
- return JDWP::ERR_NONE;
-}
-
-JDWP::ObjectId Dbg::GetSystemThreadGroupId() {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- ArtField* f = jni::DecodeArtField(WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup);
- ObjPtr<mirror::Object> group = f->GetObject(f->GetDeclaringClass());
- return gRegistry->Add(group);
-}
-
-JDWP::JdwpThreadStatus Dbg::ToJdwpThreadStatus(ThreadState state) {
- switch (state) {
- case kBlocked:
- return JDWP::TS_MONITOR;
- case kNative:
- case kRunnable:
- case kSuspended:
- return JDWP::TS_RUNNING;
- case kSleeping:
- return JDWP::TS_SLEEPING;
- case kStarting:
- case kTerminated:
- return JDWP::TS_ZOMBIE;
- case kTimedWaiting:
- case kWaitingForTaskProcessor:
- case kWaitingForLockInflation:
- case kWaitingForCheckPointsToRun:
- case kWaitingForDebuggerSend:
- case kWaitingForDebuggerSuspension:
- case kWaitingForDebuggerToAttach:
- case kWaitingForDeoptimization:
- case kWaitingForGcToComplete:
- case kWaitingForGetObjectsAllocated:
- case kWaitingForJniOnLoad:
- case kWaitingForMethodTracingStart:
- case kWaitingForSignalCatcherOutput:
- case kWaitingForVisitObjects:
- case kWaitingInMainDebuggerLoop:
- case kWaitingInMainSignalCatcherLoop:
- case kWaitingPerformingGc:
- case kWaitingWeakGcRootRead:
- case kWaitingForGcThreadFlip:
- case kNativeForAbort:
- case kWaiting:
- return JDWP::TS_WAIT;
- // Don't add a 'default' here so the compiler can spot incompatible enum changes.
- }
- LOG(FATAL) << "Unknown thread state: " << state;
- UNREACHABLE();
-}
-
-JDWP::JdwpError Dbg::GetThreadStatus(JDWP::ObjectId thread_id, JDWP::JdwpThreadStatus* pThreadStatus,
- JDWP::JdwpSuspendStatus* pSuspendStatus) {
- ScopedObjectAccess soa(Thread::Current());
-
- *pSuspendStatus = JDWP::SUSPEND_STATUS_NOT_SUSPENDED;
-
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- if (error == JDWP::ERR_THREAD_NOT_ALIVE) {
- *pThreadStatus = JDWP::TS_ZOMBIE;
- return JDWP::ERR_NONE;
- }
- return error;
- }
-
- if (IsSuspendedForDebugger(soa, thread)) {
- *pSuspendStatus = JDWP::SUSPEND_STATUS_SUSPENDED;
- }
-
- *pThreadStatus = ToJdwpThreadStatus(thread->GetState());
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetThreadDebugSuspendCount(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply) {
- ScopedObjectAccess soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- MutexLock mu2(soa.Self(), *Locks::thread_suspend_count_lock_);
- expandBufAdd4BE(pReply, thread->GetDebugSuspendCount());
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::Interrupt(JDWP::ObjectId thread_id) {
- ScopedObjectAccess soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- thread->Interrupt(soa.Self());
- return JDWP::ERR_NONE;
-}
-
-static bool IsInDesiredThreadGroup(ObjPtr<mirror::Object> desired_thread_group,
- ObjPtr<mirror::Object> peer)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Do we want threads from all thread groups?
- if (desired_thread_group == nullptr) {
- return true;
- }
- ArtField* thread_group_field = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_group);
- DCHECK(thread_group_field != nullptr);
- ObjPtr<mirror::Object> group = thread_group_field->GetObject(peer);
- return (group == desired_thread_group);
-}
-
-void Dbg::GetThreads(ObjPtr<mirror::Object> thread_group, std::vector<JDWP::ObjectId>* thread_ids) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- std::list<Thread*> all_threads_list;
- {
- MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
- all_threads_list = Runtime::Current()->GetThreadList()->GetList();
- }
- for (Thread* t : all_threads_list) {
- if (t == Dbg::GetDebugThread()) {
- // Skip the JDWP thread. Some debuggers get bent out of shape when they can't suspend and
- // query all threads, so it's easier if we just don't tell them about this thread.
- continue;
- }
- if (t->IsStillStarting()) {
- // This thread is being started (and has been registered in the thread list). However, it is
- // not completely started yet so we must ignore it.
- continue;
- }
- ObjPtr<mirror::Object> peer = t->GetPeerFromOtherThread();
- if (peer == nullptr) {
- // peer might be null if the thread is still starting up. We can't tell the debugger about
- // this thread yet.
- // TODO: if we identified threads to the debugger by their Thread*
- // rather than their peer's mirror::Object*, we could fix this.
- // Doing so might help us report ZOMBIE threads too.
- continue;
- }
- if (IsInDesiredThreadGroup(thread_group, peer)) {
- thread_ids->push_back(gRegistry->Add(peer));
- }
- }
-}
-
-static int GetStackDepth(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_) {
- size_t depth = 0u;
- StackVisitor::WalkStack(
- [&depth](const StackVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!visitor->GetMethod()->IsRuntimeMethod()) {
- ++depth;
- }
- return true;
- },
- thread,
- /* context= */ nullptr,
- StackVisitor::StackWalkKind::kIncludeInlinedFrames);
- return depth;
-}
-
-JDWP::JdwpError Dbg::GetThreadFrameCount(JDWP::ObjectId thread_id, size_t* result) {
- ScopedObjectAccess soa(Thread::Current());
- JDWP::JdwpError error;
- *result = 0;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
- *result = GetStackDepth(thread);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::GetThreadFrames(JDWP::ObjectId thread_id,
- const size_t start_frame,
- const size_t frame_count,
- JDWP::ExpandBuf* buf) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
-
- expandBufAdd4BE(buf, frame_count);
-
- size_t depth = 0u;
- StackVisitor::WalkStack(
- [&](StackVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (visitor->GetMethod()->IsRuntimeMethod()) {
- return true; // The debugger can't do anything useful with a frame that has no Method*.
- }
- if (depth >= start_frame + frame_count) {
- return false;
- }
- if (depth >= start_frame) {
- JDWP::FrameId frame_id(visitor->GetFrameId());
- JDWP::JdwpLocation location;
- SetJdwpLocation(&location, visitor->GetMethod(), visitor->GetDexPc());
- VLOG(jdwp)
- << StringPrintf(" Frame %3zd: id=%3" PRIu64 " ", depth, frame_id) << location;
- expandBufAdd8BE(buf, frame_id);
- expandBufAddLocation(buf, location);
- }
- ++depth;
- return true;
- },
- thread,
- /* context= */ nullptr,
- StackVisitor::StackWalkKind::kIncludeInlinedFrames);
-
- return JDWP::ERR_NONE;
-}
-
-JDWP::ObjectId Dbg::GetThreadSelfId() {
- return GetThreadId(Thread::Current());
-}
-
-JDWP::ObjectId Dbg::GetThreadId(Thread* thread) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- return gRegistry->Add(thread->GetPeerFromOtherThread());
-}
-
-void Dbg::SuspendVM() {
- // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335.
- gc::ScopedGCCriticalSection gcs(Thread::Current(),
- gc::kGcCauseDebugger,
- gc::kCollectorTypeDebugger);
- Runtime::Current()->GetThreadList()->SuspendAllForDebugger();
-}
-
-void Dbg::ResumeVM() {
- Runtime::Current()->GetThreadList()->ResumeAllForDebugger();
-}
-
-JDWP::JdwpError Dbg::SuspendThread(JDWP::ObjectId thread_id, bool request_suspension) {
- Thread* self = Thread::Current();
- ScopedLocalRef<jobject> peer(self->GetJniEnv(), nullptr);
- {
- ScopedObjectAccess soa(self);
- JDWP::JdwpError error;
- peer.reset(soa.AddLocalReference<jobject>(gRegistry->Get<mirror::Object>(thread_id, &error)));
- }
- if (peer.get() == nullptr) {
- return JDWP::ERR_THREAD_NOT_ALIVE;
- }
- // Suspend thread to build stack trace.
- bool timed_out;
- ThreadList* thread_list = Runtime::Current()->GetThreadList();
- Thread* thread = thread_list->SuspendThreadByPeer(peer.get(),
- request_suspension,
- SuspendReason::kForDebugger,
- &timed_out);
- if (thread != nullptr) {
- return JDWP::ERR_NONE;
- } else if (timed_out) {
- return JDWP::ERR_INTERNAL;
- } else {
- return JDWP::ERR_THREAD_NOT_ALIVE;
- }
-}
-
-void Dbg::ResumeThread(JDWP::ObjectId thread_id) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- ObjPtr<mirror::Object> peer = gRegistry->Get<mirror::Object>(thread_id, &error);
- CHECK(peer != nullptr) << error;
- Thread* thread;
- {
- MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
- thread = Thread::FromManagedThread(soa, peer);
- }
- if (thread == nullptr) {
- LOG(WARNING) << "No such thread for resume: " << peer;
- return;
- }
- bool needs_resume;
- {
- MutexLock mu2(soa.Self(), *Locks::thread_suspend_count_lock_);
- needs_resume = thread->GetDebugSuspendCount() > 0;
- }
- if (needs_resume) {
- bool resumed = Runtime::Current()->GetThreadList()->Resume(thread, SuspendReason::kForDebugger);
- DCHECK(resumed);
- }
-}
-
-void Dbg::SuspendSelf() {
- Runtime::Current()->GetThreadList()->SuspendSelfForDebugger();
-}
-
-JDWP::JdwpError Dbg::GetThisObject(JDWP::ObjectId thread_id, JDWP::FrameId frame_id,
- JDWP::ObjectId* result) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
- std::unique_ptr<Context> context(Context::Create());
- ObjPtr<mirror::Object> this_object = nullptr;
- StackVisitor::WalkStack(
- [&](art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (frame_id != stack_visitor->GetFrameId()) {
- return true; // continue
- } else {
- this_object = stack_visitor->GetThisObject();
- return false;
- }
- },
- thread,
- context.get(),
- art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
- *result = gRegistry->Add(this_object);
- return JDWP::ERR_NONE;
-}
-
-template <typename FrameHandler>
-static JDWP::JdwpError FindAndHandleNonNativeFrame(Thread* thread,
- JDWP::FrameId frame_id,
- const FrameHandler& handler)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JDWP::JdwpError result = JDWP::ERR_INVALID_FRAMEID;
- std::unique_ptr<Context> context(Context::Create());
- StackVisitor::WalkStack(
- [&](art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- if (stack_visitor->GetFrameId() != frame_id) {
- return true; // Not our frame, carry on.
- }
- ArtMethod* m = stack_visitor->GetMethod();
- if (m->IsNative()) {
- // We can't read/write local value from/into native method.
- result = JDWP::ERR_OPAQUE_FRAME;
- } else {
- // We found our frame.
- result = handler(stack_visitor);
- }
- return false;
- },
- thread,
- context.get(),
- art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
- return result;
-}
-
-JDWP::JdwpError Dbg::GetLocalValues(JDWP::Request* request, JDWP::ExpandBuf* pReply) {
- JDWP::ObjectId thread_id = request->ReadThreadId();
- JDWP::FrameId frame_id = request->ReadFrameId();
-
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
-
- return FindAndHandleNonNativeFrame(
- thread,
- frame_id,
- [&](art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- // Read the values from visitor's context.
- int32_t slot_count = request->ReadSigned32("slot count");
- expandBufAdd4BE(pReply, slot_count); /* "int values" */
- for (int32_t i = 0; i < slot_count; ++i) {
- uint32_t slot = request->ReadUnsigned32("slot");
- JDWP::JdwpTag reqSigByte = request->ReadTag();
-
- VLOG(jdwp) << " --> slot " << slot << " " << reqSigByte;
-
- size_t width = Dbg::GetTagWidth(reqSigByte);
- uint8_t* ptr = expandBufAddSpace(pReply, width + 1);
- error = Dbg::GetLocalValue(*stack_visitor, soa, slot, reqSigByte, ptr, width);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- }
- return JDWP::ERR_NONE;
- });
-}
-
-constexpr JDWP::JdwpError kStackFrameLocalAccessError = JDWP::ERR_ABSENT_INFORMATION;
-
-static std::string GetStackContextAsString(const StackVisitor& visitor)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return StringPrintf(" at DEX pc 0x%08x in method %s", visitor.GetDexPc(false),
- ArtMethod::PrettyMethod(visitor.GetMethod()).c_str());
-}
-
-static JDWP::JdwpError FailGetLocalValue(const StackVisitor& visitor, uint16_t vreg,
- JDWP::JdwpTag tag)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- LOG(ERROR) << "Failed to read " << tag << " local from register v" << vreg
- << GetStackContextAsString(visitor);
- return kStackFrameLocalAccessError;
-}
-
-JDWP::JdwpError Dbg::GetLocalValue(const StackVisitor& visitor, ScopedObjectAccessUnchecked& soa,
- int slot, JDWP::JdwpTag tag, uint8_t* buf, size_t width) {
- ArtMethod* m = visitor.GetMethod();
- JDWP::JdwpError error = JDWP::ERR_NONE;
- uint16_t vreg = DemangleSlot(slot, m, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- // TODO: check that the tag is compatible with the actual type of the slot!
- switch (tag) {
- case JDWP::JT_BOOLEAN: {
- CHECK_EQ(width, 1U);
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kIntVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get boolean local " << vreg << " = " << intVal;
- JDWP::Set1(buf + 1, intVal != 0);
- break;
- }
- case JDWP::JT_BYTE: {
- CHECK_EQ(width, 1U);
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kIntVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get byte local " << vreg << " = " << intVal;
- JDWP::Set1(buf + 1, intVal);
- break;
- }
- case JDWP::JT_SHORT:
- case JDWP::JT_CHAR: {
- CHECK_EQ(width, 2U);
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kIntVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get short/char local " << vreg << " = " << intVal;
- JDWP::Set2BE(buf + 1, intVal);
- break;
- }
- case JDWP::JT_INT: {
- CHECK_EQ(width, 4U);
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kIntVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get int local " << vreg << " = " << intVal;
- JDWP::Set4BE(buf + 1, intVal);
- break;
- }
- case JDWP::JT_FLOAT: {
- CHECK_EQ(width, 4U);
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kFloatVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get float local " << vreg << " = " << intVal;
- JDWP::Set4BE(buf + 1, intVal);
- break;
- }
- case JDWP::JT_ARRAY:
- case JDWP::JT_CLASS_LOADER:
- case JDWP::JT_CLASS_OBJECT:
- case JDWP::JT_OBJECT:
- case JDWP::JT_STRING:
- case JDWP::JT_THREAD:
- case JDWP::JT_THREAD_GROUP: {
- CHECK_EQ(width, sizeof(JDWP::ObjectId));
- uint32_t intVal;
- if (!visitor.GetVReg(m, vreg, kReferenceVReg, &intVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- ObjPtr<mirror::Object> o = reinterpret_cast<mirror::Object*>(intVal);
- VLOG(jdwp) << "get " << tag << " object local " << vreg << " = " << o;
- if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o.Ptr())) {
- LOG(FATAL) << StringPrintf("Found invalid object %#" PRIxPTR " in register v%u",
- reinterpret_cast<uintptr_t>(o.Ptr()), vreg)
- << GetStackContextAsString(visitor);
- UNREACHABLE();
- }
- tag = TagFromObject(soa, o);
- JDWP::SetObjectId(buf + 1, gRegistry->Add(o));
- break;
- }
- case JDWP::JT_DOUBLE: {
- CHECK_EQ(width, 8U);
- uint64_t longVal;
- if (!visitor.GetVRegPair(m, vreg, kDoubleLoVReg, kDoubleHiVReg, &longVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get double local " << vreg << " = " << longVal;
- JDWP::Set8BE(buf + 1, longVal);
- break;
- }
- case JDWP::JT_LONG: {
- CHECK_EQ(width, 8U);
- uint64_t longVal;
- if (!visitor.GetVRegPair(m, vreg, kLongLoVReg, kLongHiVReg, &longVal)) {
- return FailGetLocalValue(visitor, vreg, tag);
- }
- VLOG(jdwp) << "get long local " << vreg << " = " << longVal;
- JDWP::Set8BE(buf + 1, longVal);
- break;
- }
- default:
- LOG(FATAL) << "Unknown tag " << tag;
- UNREACHABLE();
- }
-
- // Prepend tag, which may have been updated.
- JDWP::Set1(buf, tag);
- return JDWP::ERR_NONE;
-}
-
-JDWP::JdwpError Dbg::SetLocalValues(JDWP::Request* request) {
- JDWP::ObjectId thread_id = request->ReadThreadId();
- JDWP::FrameId frame_id = request->ReadFrameId();
-
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- if (!IsSuspendedForDebugger(soa, thread)) {
- return JDWP::ERR_THREAD_NOT_SUSPENDED;
- }
-
- return FindAndHandleNonNativeFrame(
- thread,
- frame_id,
- [&](art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- // Writes the values into visitor's context.
- int32_t slot_count = request->ReadSigned32("slot count");
- for (int32_t i = 0; i < slot_count; ++i) {
- uint32_t slot = request->ReadUnsigned32("slot");
- JDWP::JdwpTag sigByte = request->ReadTag();
- size_t width = Dbg::GetTagWidth(sigByte);
- uint64_t value = request->ReadValue(width);
-
- VLOG(jdwp) << " --> slot " << slot << " " << sigByte << " " << value;
- error = Dbg::SetLocalValue(thread, *stack_visitor, slot, sigByte, value, width);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- }
- return JDWP::ERR_NONE;
- });
-}
-
-template<typename T>
-static JDWP::JdwpError FailSetLocalValue(const StackVisitor& visitor, uint16_t vreg,
- JDWP::JdwpTag tag, T value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- LOG(ERROR) << "Failed to write " << tag << " local " << value
- << " (0x" << std::hex << value << ") into register v" << vreg
- << GetStackContextAsString(visitor);
- return kStackFrameLocalAccessError;
-}
-
-JDWP::JdwpError Dbg::SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
- JDWP::JdwpTag tag, uint64_t value, size_t width) {
- ArtMethod* m = visitor.GetMethod();
- JDWP::JdwpError error = JDWP::ERR_NONE;
- uint16_t vreg = DemangleSlot(slot, m, &error);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
- // TODO: check that the tag is compatible with the actual type of the slot!
- switch (tag) {
- case JDWP::JT_BOOLEAN:
- case JDWP::JT_BYTE:
- CHECK_EQ(width, 1U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
- }
- break;
- case JDWP::JT_SHORT:
- case JDWP::JT_CHAR:
- CHECK_EQ(width, 2U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
- }
- break;
- case JDWP::JT_INT:
- CHECK_EQ(width, 4U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kIntVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
- }
- break;
- case JDWP::JT_FLOAT:
- CHECK_EQ(width, 4U);
- if (!visitor.SetVReg(m, vreg, static_cast<uint32_t>(value), kFloatVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, static_cast<uint32_t>(value));
- }
- break;
- case JDWP::JT_ARRAY:
- case JDWP::JT_CLASS_LOADER:
- case JDWP::JT_CLASS_OBJECT:
- case JDWP::JT_OBJECT:
- case JDWP::JT_STRING:
- case JDWP::JT_THREAD:
- case JDWP::JT_THREAD_GROUP: {
- CHECK_EQ(width, sizeof(JDWP::ObjectId));
- ObjPtr<mirror::Object> o =
- gRegistry->Get<mirror::Object>(static_cast<JDWP::ObjectId>(value), &error);
- if (error != JDWP::ERR_NONE) {
- VLOG(jdwp) << tag << " object " << o << " is an invalid object";
- return JDWP::ERR_INVALID_OBJECT;
- }
- if (!visitor.SetVRegReference(m, vreg, o)) {
- return FailSetLocalValue(visitor, vreg, tag, reinterpret_cast<uintptr_t>(o.Ptr()));
- }
- break;
- }
- case JDWP::JT_DOUBLE: {
- CHECK_EQ(width, 8U);
- if (!visitor.SetVRegPair(m, vreg, value, kDoubleLoVReg, kDoubleHiVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, value);
- }
- break;
- }
- case JDWP::JT_LONG: {
- CHECK_EQ(width, 8U);
- if (!visitor.SetVRegPair(m, vreg, value, kLongLoVReg, kLongHiVReg)) {
- return FailSetLocalValue(visitor, vreg, tag, value);
- }
- break;
- }
- default:
- LOG(FATAL) << "Unknown tag " << tag;
- UNREACHABLE();
- }
-
- // If we set the local variable in a compiled frame, we need to trigger a deoptimization of
- // the stack so we continue execution with the interpreter using the new value(s) of the updated
- // local variable(s). To achieve this, we install instrumentation exit stub on each method of the
- // thread's stack. The stub will cause the deoptimization to happen.
- if (!visitor.IsShadowFrame() && thread->HasDebuggerShadowFrames()) {
- Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(thread);
- }
-
- return JDWP::ERR_NONE;
-}
-
-static void SetEventLocation(JDWP::EventLocation* location, ArtMethod* m, uint32_t dex_pc)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(location != nullptr);
- if (m == nullptr) {
- memset(location, 0, sizeof(*location));
- } else {
- location->method = m->GetCanonicalMethod(kRuntimePointerSize);
- location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint32_t>(-1) : dex_pc;
- }
-}
-
-void Dbg::PostLocationEvent(ArtMethod* m,
- int dex_pc,
- ObjPtr<mirror::Object> this_object,
- int event_flags,
- const JValue* return_value) {
- if (!IsDebuggerActive()) {
- return;
- }
- DCHECK(m != nullptr);
- DCHECK_EQ(m->IsStatic(), this_object == nullptr);
- JDWP::EventLocation location;
- SetEventLocation(&location, m, dex_pc);
-
- // We need to be sure no exception is pending when calling JdwpState::PostLocationEvent.
- // This is required to be able to call JNI functions to create JDWP ids. To achieve this,
- // we temporarily clear the current thread's exception (if any) and will restore it after
- // the call.
- // Note: the only way to get a pending exception here is to suspend on a move-exception
- // instruction.
- Thread* const self = Thread::Current();
- StackHandleScope<1> hs(self);
- Handle<mirror::Throwable> pending_exception(hs.NewHandle(self->GetException()));
- self->ClearException();
- if (kIsDebugBuild && pending_exception != nullptr) {
- const Instruction& instr = location.method->DexInstructions().InstructionAt(location.dex_pc);
- CHECK_EQ(Instruction::MOVE_EXCEPTION, instr.Opcode());
- }
-
- gJdwpState->PostLocationEvent(&location, this_object, event_flags, return_value);
-
- if (pending_exception != nullptr) {
- self->SetException(pending_exception.Get());
- }
-}
-
-void Dbg::PostFieldAccessEvent(ArtMethod* m,
- int dex_pc,
- ObjPtr<mirror::Object> this_object,
- ArtField* f) {
- // TODO We should send events for native methods.
- if (!IsDebuggerActive() || m->IsNative()) {
- return;
- }
- DCHECK(m != nullptr);
- DCHECK(f != nullptr);
- JDWP::EventLocation location;
- SetEventLocation(&location, m, dex_pc);
-
- gJdwpState->PostFieldEvent(&location, f, this_object, nullptr, false);
-}
-
-void Dbg::PostFieldModificationEvent(ArtMethod* m,
- int dex_pc,
- ObjPtr<mirror::Object> this_object,
- ArtField* f,
- const JValue* field_value) {
- // TODO We should send events for native methods.
- if (!IsDebuggerActive() || m->IsNative()) {
- return;
- }
- DCHECK(m != nullptr);
- DCHECK(f != nullptr);
- DCHECK(field_value != nullptr);
- JDWP::EventLocation location;
- SetEventLocation(&location, m, dex_pc);
-
- gJdwpState->PostFieldEvent(&location, f, this_object, field_value, true);
-}
-
-void Dbg::PostException(ObjPtr<mirror::Throwable> exception_object) {
- if (!IsDebuggerActive()) {
- return;
- }
- Thread* const self = Thread::Current();
- StackHandleScope<2> handle_scope(self);
- Handle<mirror::Throwable> h_exception(handle_scope.NewHandle(exception_object));
- MutableHandle<mirror::Object> this_at_throw = handle_scope.NewHandle<mirror::Object>(nullptr);
- std::unique_ptr<Context> context(Context::Create());
-
- ArtMethod* catch_method = nullptr;
- ArtMethod* throw_method = nullptr;
- uint32_t catch_dex_pc = dex::kDexNoIndex;
- uint32_t throw_dex_pc = dex::kDexNoIndex;
- StackVisitor::WalkStack(
- /**
- * Finds the location where this exception will be caught. We search until we reach the top
- * frame, in which case this exception is considered uncaught.
- */
- [&](const art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- ArtMethod* method = stack_visitor->GetMethod();
- DCHECK(method != nullptr);
- if (method->IsRuntimeMethod()) {
- // Ignore callee save method.
- DCHECK(method->IsCalleeSaveMethod());
- return true;
- }
-
- uint32_t dex_pc = stack_visitor->GetDexPc();
- if (throw_method == nullptr) {
- // First Java method found. It is either the method that threw the exception,
- // or the Java native method that is reporting an exception thrown by
- // native code.
- this_at_throw.Assign(stack_visitor->GetThisObject());
- throw_method = method;
- throw_dex_pc = dex_pc;
- }
-
- if (dex_pc != dex::kDexNoIndex) {
- StackHandleScope<1> hs(stack_visitor->GetThread());
- uint32_t found_dex_pc;
- Handle<mirror::Class> exception_class(hs.NewHandle(h_exception->GetClass()));
- bool unused_clear_exception;
- found_dex_pc = method->FindCatchBlock(exception_class, dex_pc, &unused_clear_exception);
- if (found_dex_pc != dex::kDexNoIndex) {
- catch_method = method;
- catch_dex_pc = found_dex_pc;
- return false; // End stack walk.
- }
- }
- return true; // Continue stack walk.
- },
- self,
- context.get(),
- art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
-
- JDWP::EventLocation exception_throw_location;
- SetEventLocation(&exception_throw_location, throw_method, throw_dex_pc);
- JDWP::EventLocation exception_catch_location;
- SetEventLocation(&exception_catch_location, catch_method, catch_dex_pc);
-
- gJdwpState->PostException(&exception_throw_location,
- h_exception.Get(),
- &exception_catch_location,
- this_at_throw.Get());
-}
-
-void Dbg::PostClassPrepare(ObjPtr<mirror::Class> c) {
- if (!IsDebuggerActive()) {
- return;
- }
- gJdwpState->PostClassPrepare(c);
-}
-
-void Dbg::UpdateDebugger(Thread* thread,
- ObjPtr<mirror::Object> this_object,
- ArtMethod* m,
- uint32_t dex_pc,
- int event_flags,
- const JValue* return_value) {
- if (!IsDebuggerActive() || dex_pc == static_cast<uint32_t>(-2) /* fake method exit */) {
- return;
- }
-
- if (IsBreakpoint(m, dex_pc)) {
- event_flags |= kBreakpoint;
- }
-
- // If the debugger is single-stepping one of our threads, check to
- // see if we're that thread and we've reached a step point.
- const SingleStepControl* single_step_control = thread->GetSingleStepControl();
- if (single_step_control != nullptr) {
- CHECK(!m->IsNative());
- if (single_step_control->GetStepDepth() == JDWP::SD_INTO) {
- // Step into method calls. We break when the line number
- // or method pointer changes. If we're in SS_MIN mode, we
- // always stop.
- if (single_step_control->GetMethod() != m) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS new method";
- } else if (single_step_control->GetStepSize() == JDWP::SS_MIN) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS new instruction";
- } else if (single_step_control->ContainsDexPc(dex_pc)) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS new line";
- }
- } else if (single_step_control->GetStepDepth() == JDWP::SD_OVER) {
- // Step over method calls. We break when the line number is
- // different and the frame depth is <= the original frame
- // depth. (We can't just compare on the method, because we
- // might get unrolled past it by an exception, and it's tricky
- // to identify recursion.)
-
- int stack_depth = GetStackDepth(thread);
-
- if (stack_depth < single_step_control->GetStackDepth()) {
- // Popped up one or more frames, always trigger.
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS method pop";
- } else if (stack_depth == single_step_control->GetStackDepth()) {
- // Same depth, see if we moved.
- if (single_step_control->GetStepSize() == JDWP::SS_MIN) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS new instruction";
- } else if (single_step_control->ContainsDexPc(dex_pc)) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS new line";
- }
- }
- } else {
- CHECK_EQ(single_step_control->GetStepDepth(), JDWP::SD_OUT);
- // Return from the current method. We break when the frame
- // depth pops up.
-
- // This differs from the "method exit" break in that it stops
- // with the PC at the next instruction in the returned-to
- // function, rather than the end of the returning function.
-
- int stack_depth = GetStackDepth(thread);
- if (stack_depth < single_step_control->GetStackDepth()) {
- event_flags |= kSingleStep;
- VLOG(jdwp) << "SS method pop";
- }
- }
- }
-
- // If there's something interesting going on, see if it matches one
- // of the debugger filters.
- if (event_flags != 0) {
- Dbg::PostLocationEvent(m, dex_pc, this_object, event_flags, return_value);
- }
-}
-
-size_t* Dbg::GetReferenceCounterForEvent(uint32_t instrumentation_event) {
- switch (instrumentation_event) {
- case instrumentation::Instrumentation::kMethodEntered:
- return &method_enter_event_ref_count_;
- case instrumentation::Instrumentation::kMethodExited:
- return &method_exit_event_ref_count_;
- case instrumentation::Instrumentation::kDexPcMoved:
- return &dex_pc_change_event_ref_count_;
- case instrumentation::Instrumentation::kFieldRead:
- return &field_read_event_ref_count_;
- case instrumentation::Instrumentation::kFieldWritten:
- return &field_write_event_ref_count_;
- case instrumentation::Instrumentation::kExceptionThrown:
- return &exception_catch_event_ref_count_;
- default:
- return nullptr;
- }
-}
-
-// Process request while all mutator threads are suspended.
-void Dbg::ProcessDeoptimizationRequest(const DeoptimizationRequest& request) {
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- switch (request.GetKind()) {
- case DeoptimizationRequest::kNothing:
- LOG(WARNING) << "Ignoring empty deoptimization request.";
- break;
- case DeoptimizationRequest::kRegisterForEvent:
- VLOG(jdwp) << StringPrintf("Add debugger as listener for instrumentation event 0x%x",
- request.InstrumentationEvent());
- instrumentation->AddListener(&gDebugInstrumentationListener, request.InstrumentationEvent());
- instrumentation_events_ |= request.InstrumentationEvent();
- break;
- case DeoptimizationRequest::kUnregisterForEvent:
- VLOG(jdwp) << StringPrintf("Remove debugger as listener for instrumentation event 0x%x",
- request.InstrumentationEvent());
- instrumentation->RemoveListener(&gDebugInstrumentationListener,
- request.InstrumentationEvent());
- instrumentation_events_ &= ~request.InstrumentationEvent();
- break;
- case DeoptimizationRequest::kFullDeoptimization:
- VLOG(jdwp) << "Deoptimize the world ...";
- instrumentation->DeoptimizeEverything(kDbgInstrumentationKey);
- VLOG(jdwp) << "Deoptimize the world DONE";
- break;
- case DeoptimizationRequest::kFullUndeoptimization:
- VLOG(jdwp) << "Undeoptimize the world ...";
- instrumentation->UndeoptimizeEverything(kDbgInstrumentationKey);
- VLOG(jdwp) << "Undeoptimize the world DONE";
- break;
- case DeoptimizationRequest::kSelectiveDeoptimization:
- VLOG(jdwp) << "Deoptimize method " << ArtMethod::PrettyMethod(request.Method()) << " ...";
- instrumentation->Deoptimize(request.Method());
- VLOG(jdwp) << "Deoptimize method " << ArtMethod::PrettyMethod(request.Method()) << " DONE";
- break;
- case DeoptimizationRequest::kSelectiveUndeoptimization:
- VLOG(jdwp) << "Undeoptimize method " << ArtMethod::PrettyMethod(request.Method()) << " ...";
- instrumentation->Undeoptimize(request.Method());
- VLOG(jdwp) << "Undeoptimize method " << ArtMethod::PrettyMethod(request.Method()) << " DONE";
- break;
- default:
- LOG(FATAL) << "Unsupported deoptimization request kind " << request.GetKind();
- UNREACHABLE();
- }
-}
-
-void Dbg::RequestDeoptimization(const DeoptimizationRequest& req) {
- if (req.GetKind() == DeoptimizationRequest::kNothing) {
- // Nothing to do.
- return;
- }
- MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_);
- RequestDeoptimizationLocked(req);
-}
-
-void Dbg::RequestDeoptimizationLocked(const DeoptimizationRequest& req) {
- switch (req.GetKind()) {
- case DeoptimizationRequest::kRegisterForEvent: {
- DCHECK_NE(req.InstrumentationEvent(), 0u);
- size_t* counter = GetReferenceCounterForEvent(req.InstrumentationEvent());
- CHECK(counter != nullptr) << StringPrintf("No counter for instrumentation event 0x%x",
- req.InstrumentationEvent());
- if (*counter == 0) {
- VLOG(jdwp) << StringPrintf("Queue request #%zd to start listening to instrumentation event 0x%x",
- deoptimization_requests_.size(), req.InstrumentationEvent());
- deoptimization_requests_.push_back(req);
- }
- *counter = *counter + 1;
- break;
- }
- case DeoptimizationRequest::kUnregisterForEvent: {
- DCHECK_NE(req.InstrumentationEvent(), 0u);
- size_t* counter = GetReferenceCounterForEvent(req.InstrumentationEvent());
- CHECK(counter != nullptr) << StringPrintf("No counter for instrumentation event 0x%x",
- req.InstrumentationEvent());
- *counter = *counter - 1;
- if (*counter == 0) {
- VLOG(jdwp) << StringPrintf("Queue request #%zd to stop listening to instrumentation event 0x%x",
- deoptimization_requests_.size(), req.InstrumentationEvent());
- deoptimization_requests_.push_back(req);
- }
- break;
- }
- case DeoptimizationRequest::kFullDeoptimization: {
- DCHECK(req.Method() == nullptr);
- if (full_deoptimization_event_count_ == 0) {
- VLOG(jdwp) << "Queue request #" << deoptimization_requests_.size()
- << " for full deoptimization";
- deoptimization_requests_.push_back(req);
- }
- ++full_deoptimization_event_count_;
- break;
- }
- case DeoptimizationRequest::kFullUndeoptimization: {
- DCHECK(req.Method() == nullptr);
- DCHECK_GT(full_deoptimization_event_count_, 0U);
- --full_deoptimization_event_count_;
- if (full_deoptimization_event_count_ == 0) {
- VLOG(jdwp) << "Queue request #" << deoptimization_requests_.size()
- << " for full undeoptimization";
- deoptimization_requests_.push_back(req);
- }
- break;
- }
- case DeoptimizationRequest::kSelectiveDeoptimization: {
- DCHECK(req.Method() != nullptr);
- VLOG(jdwp) << "Queue request #" << deoptimization_requests_.size()
- << " for deoptimization of " << req.Method()->PrettyMethod();
- deoptimization_requests_.push_back(req);
- break;
- }
- case DeoptimizationRequest::kSelectiveUndeoptimization: {
- DCHECK(req.Method() != nullptr);
- VLOG(jdwp) << "Queue request #" << deoptimization_requests_.size()
- << " for undeoptimization of " << req.Method()->PrettyMethod();
- deoptimization_requests_.push_back(req);
- break;
- }
- default: {
- LOG(FATAL) << "Unknown deoptimization request kind " << req.GetKind();
- UNREACHABLE();
- }
- }
-}
-
-void Dbg::ManageDeoptimization() {
- Thread* const self = Thread::Current();
- {
- // Avoid suspend/resume if there is no pending request.
- MutexLock mu(self, *Locks::deoptimization_lock_);
- if (deoptimization_requests_.empty()) {
- return;
- }
- }
- CHECK_EQ(self->GetState(), kRunnable);
- ScopedThreadSuspension sts(self, kWaitingForDeoptimization);
- // Required for ProcessDeoptimizationRequest.
- gc::ScopedGCCriticalSection gcs(self,
- gc::kGcCauseInstrumentation,
- gc::kCollectorTypeInstrumentation);
- // We need to suspend mutator threads first.
- ScopedSuspendAll ssa(__FUNCTION__);
- const ThreadState old_state = self->SetStateUnsafe(kRunnable);
- {
- MutexLock mu(self, *Locks::deoptimization_lock_);
- size_t req_index = 0;
- for (DeoptimizationRequest& request : deoptimization_requests_) {
- VLOG(jdwp) << "Process deoptimization request #" << req_index++;
- ProcessDeoptimizationRequest(request);
- }
- deoptimization_requests_.clear();
- }
- CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable);
-}
-
-static const Breakpoint* FindFirstBreakpointForMethod(ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_, Locks::breakpoint_lock_) {
- for (Breakpoint& breakpoint : gBreakpoints) {
- if (breakpoint.IsInMethod(m)) {
- return &breakpoint;
- }
- }
- return nullptr;
-}
-
-bool Dbg::MethodHasAnyBreakpoints(ArtMethod* method) {
- ReaderMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_);
- return FindFirstBreakpointForMethod(method) != nullptr;
-}
-
-// Sanity checks all existing breakpoints on the same method.
-static void SanityCheckExistingBreakpoints(ArtMethod* m,
- DeoptimizationRequest::Kind deoptimization_kind)
- REQUIRES_SHARED(Locks::mutator_lock_, Locks::breakpoint_lock_) {
- for (const Breakpoint& breakpoint : gBreakpoints) {
- if (breakpoint.IsInMethod(m)) {
- CHECK_EQ(deoptimization_kind, breakpoint.GetDeoptimizationKind());
- }
- }
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- if (deoptimization_kind == DeoptimizationRequest::kFullDeoptimization) {
- // We should have deoptimized everything but not "selectively" deoptimized this method.
- CHECK(instrumentation->AreAllMethodsDeoptimized());
- CHECK(!instrumentation->IsDeoptimized(m));
- } else if (deoptimization_kind == DeoptimizationRequest::kSelectiveDeoptimization) {
- // We should have "selectively" deoptimized this method.
- // Note: while we have not deoptimized everything for this method, we may have done it for
- // another event.
- CHECK(instrumentation->IsDeoptimized(m));
- } else {
- // This method does not require deoptimization.
- CHECK_EQ(deoptimization_kind, DeoptimizationRequest::kNothing);
- CHECK(!instrumentation->IsDeoptimized(m));
- }
-}
-
-// Returns the deoptimization kind required to set a breakpoint in a method.
-// If a breakpoint has already been set, we also return the first breakpoint
-// through the given 'existing_brkpt' pointer.
-static DeoptimizationRequest::Kind GetRequiredDeoptimizationKind(Thread* self,
- ArtMethod* m,
- const Breakpoint** existing_brkpt)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!Dbg::RequiresDeoptimization()) {
- // We already run in interpreter-only mode so we don't need to deoptimize anything.
- VLOG(jdwp) << "No need for deoptimization when fully running with interpreter for method "
- << ArtMethod::PrettyMethod(m);
- return DeoptimizationRequest::kNothing;
- }
- const Breakpoint* first_breakpoint;
- {
- ReaderMutexLock mu(self, *Locks::breakpoint_lock_);
- first_breakpoint = FindFirstBreakpointForMethod(m);
- *existing_brkpt = first_breakpoint;
- }
-
- if (first_breakpoint == nullptr) {
- // There is no breakpoint on this method yet: we need to deoptimize. If this method is default,
- // we deoptimize everything; otherwise we deoptimize only this method. We
- // deoptimize with defaults because we do not know everywhere they are used. It is possible some
- // of the copies could be missed.
- // TODO Deoptimizing on default methods might not be necessary in all cases.
- bool need_full_deoptimization = m->IsDefault();
- if (need_full_deoptimization) {
- VLOG(jdwp) << "Need full deoptimization because of copying of method "
- << ArtMethod::PrettyMethod(m);
- return DeoptimizationRequest::kFullDeoptimization;
- } else {
- // We don't need to deoptimize if the method has not been compiled.
- const bool is_compiled = m->HasAnyCompiledCode();
- if (is_compiled) {
- VLOG(jdwp) << "Need selective deoptimization for compiled method "
- << ArtMethod::PrettyMethod(m);
- return DeoptimizationRequest::kSelectiveDeoptimization;
- } else {
- // Method is not compiled: we don't need to deoptimize.
- VLOG(jdwp) << "No need for deoptimization for non-compiled method "
- << ArtMethod::PrettyMethod(m);
- return DeoptimizationRequest::kNothing;
- }
- }
- } else {
- // There is at least one breakpoint for this method: we don't need to deoptimize.
- // Let's check that all breakpoints are configured the same way for deoptimization.
- VLOG(jdwp) << "Breakpoint already set: no deoptimization is required";
- DeoptimizationRequest::Kind deoptimization_kind = first_breakpoint->GetDeoptimizationKind();
- if (kIsDebugBuild) {
- ReaderMutexLock mu(self, *Locks::breakpoint_lock_);
- SanityCheckExistingBreakpoints(m, deoptimization_kind);
- }
- return DeoptimizationRequest::kNothing;
- }
-}
-
-// Installs a breakpoint at the specified location. Also indicates through the deoptimization
-// request if we need to deoptimize.
-void Dbg::WatchLocation(const JDWP::JdwpLocation* location, DeoptimizationRequest* req) {
- Thread* const self = Thread::Current();
- ArtMethod* m = FromMethodId(location->method_id);
- DCHECK(m != nullptr) << "No method for method id " << location->method_id;
-
- const Breakpoint* existing_breakpoint = nullptr;
- const DeoptimizationRequest::Kind deoptimization_kind =
- GetRequiredDeoptimizationKind(self, m, &existing_breakpoint);
- req->SetKind(deoptimization_kind);
- if (deoptimization_kind == DeoptimizationRequest::kSelectiveDeoptimization) {
- req->SetMethod(m);
- } else {
- CHECK(deoptimization_kind == DeoptimizationRequest::kNothing ||
- deoptimization_kind == DeoptimizationRequest::kFullDeoptimization);
- req->SetMethod(nullptr);
- }
-
- {
- WriterMutexLock mu(self, *Locks::breakpoint_lock_);
- // If there is at least one existing breakpoint on the same method, the new breakpoint
- // must have the same deoptimization kind than the existing breakpoint(s).
- DeoptimizationRequest::Kind breakpoint_deoptimization_kind;
- if (existing_breakpoint != nullptr) {
- breakpoint_deoptimization_kind = existing_breakpoint->GetDeoptimizationKind();
- } else {
- breakpoint_deoptimization_kind = deoptimization_kind;
- }
- gBreakpoints.push_back(Breakpoint(m, location->dex_pc, breakpoint_deoptimization_kind));
- VLOG(jdwp) << "Set breakpoint #" << (gBreakpoints.size() - 1) << ": "
- << gBreakpoints[gBreakpoints.size() - 1];
- }
-}
-
-// Uninstalls a breakpoint at the specified location. Also indicates through the deoptimization
-// request if we need to undeoptimize.
-void Dbg::UnwatchLocation(const JDWP::JdwpLocation* location, DeoptimizationRequest* req) {
- WriterMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_);
- ArtMethod* m = FromMethodId(location->method_id);
- DCHECK(m != nullptr) << "No method for method id " << location->method_id;
- DeoptimizationRequest::Kind deoptimization_kind = DeoptimizationRequest::kNothing;
- for (size_t i = 0, e = gBreakpoints.size(); i < e; ++i) {
- if (gBreakpoints[i].DexPc() == location->dex_pc && gBreakpoints[i].IsInMethod(m)) {
- VLOG(jdwp) << "Removed breakpoint #" << i << ": " << gBreakpoints[i];
- deoptimization_kind = gBreakpoints[i].GetDeoptimizationKind();
- DCHECK_EQ(deoptimization_kind == DeoptimizationRequest::kSelectiveDeoptimization,
- Runtime::Current()->GetInstrumentation()->IsDeoptimized(m));
- gBreakpoints.erase(gBreakpoints.begin() + i);
- break;
- }
- }
- const Breakpoint* const existing_breakpoint = FindFirstBreakpointForMethod(m);
- if (existing_breakpoint == nullptr) {
- // There is no more breakpoint on this method: we need to undeoptimize.
- if (deoptimization_kind == DeoptimizationRequest::kFullDeoptimization) {
- // This method required full deoptimization: we need to undeoptimize everything.
- req->SetKind(DeoptimizationRequest::kFullUndeoptimization);
- req->SetMethod(nullptr);
- } else if (deoptimization_kind == DeoptimizationRequest::kSelectiveDeoptimization) {
- // This method required selective deoptimization: we need to undeoptimize only that method.
- req->SetKind(DeoptimizationRequest::kSelectiveUndeoptimization);
- req->SetMethod(m);
- } else {
- // This method had no need for deoptimization: do nothing.
- CHECK_EQ(deoptimization_kind, DeoptimizationRequest::kNothing);
- req->SetKind(DeoptimizationRequest::kNothing);
- req->SetMethod(nullptr);
- }
- } else {
- // There is at least one breakpoint for this method: we don't need to undeoptimize.
- req->SetKind(DeoptimizationRequest::kNothing);
- req->SetMethod(nullptr);
- if (kIsDebugBuild) {
- SanityCheckExistingBreakpoints(m, deoptimization_kind);
- }
- }
-}
-
-bool Dbg::IsForcedInterpreterNeededForCallingImpl(Thread* thread, ArtMethod* m) {
- const SingleStepControl* const ssc = thread->GetSingleStepControl();
- if (ssc == nullptr) {
- // If we are not single-stepping, then we don't have to force interpreter.
- return false;
- }
- if (Runtime::Current()->GetInstrumentation()->InterpretOnly()) {
- // If we are in interpreter only mode, then we don't have to force interpreter.
- return false;
- }
-
- if (!m->IsNative() && !m->IsProxyMethod()) {
- // If we want to step into a method, then we have to force interpreter on that call.
- if (ssc->GetStepDepth() == JDWP::SD_INTO) {
- return true;
- }
- }
- return false;
-}
-
-bool Dbg::IsForcedInterpreterNeededForResolutionImpl(Thread* thread, ArtMethod* m) {
- instrumentation::Instrumentation* const instrumentation =
- Runtime::Current()->GetInstrumentation();
- // If we are in interpreter only mode, then we don't have to force interpreter.
- if (instrumentation->InterpretOnly()) {
- return false;
- }
- // We can only interpret pure Java method.
- if (m->IsNative() || m->IsProxyMethod()) {
- return false;
- }
- const SingleStepControl* const ssc = thread->GetSingleStepControl();
- if (ssc != nullptr) {
- // If we want to step into a method, then we have to force interpreter on that call.
- if (ssc->GetStepDepth() == JDWP::SD_INTO) {
- return true;
- }
- // If we are stepping out from a static initializer, by issuing a step
- // in or step over, that was implicitly invoked by calling a static method,
- // then we need to step into that method. Having a lower stack depth than
- // the one the single step control has indicates that the step originates
- // from the static initializer.
- if (ssc->GetStepDepth() != JDWP::SD_OUT &&
- ssc->GetStackDepth() > GetStackDepth(thread)) {
- return true;
- }
- }
- // There are cases where we have to force interpreter on deoptimized methods,
- // because in some cases the call will not be performed by invoking an entry
- // point that has been replaced by the deoptimization, but instead by directly
- // invoking the compiled code of the method, for example.
- return instrumentation->IsDeoptimized(m);
-}
-
-bool Dbg::IsForcedInstrumentationNeededForResolutionImpl(Thread* thread, ArtMethod* m) {
- // The upcall can be null and in that case we don't need to do anything.
- if (m == nullptr) {
- return false;
- }
- instrumentation::Instrumentation* const instrumentation =
- Runtime::Current()->GetInstrumentation();
- // If we are in interpreter only mode, then we don't have to force interpreter.
- if (instrumentation->InterpretOnly()) {
- return false;
- }
- // We can only interpret pure Java method.
- if (m->IsNative() || m->IsProxyMethod()) {
- return false;
- }
- const SingleStepControl* const ssc = thread->GetSingleStepControl();
- if (ssc != nullptr) {
- // If we are stepping out from a static initializer, by issuing a step
- // out, that was implicitly invoked by calling a static method, then we
- // need to step into the caller of that method. Having a lower stack
- // depth than the one the single step control has indicates that the
- // step originates from the static initializer.
- if (ssc->GetStepDepth() == JDWP::SD_OUT &&
- ssc->GetStackDepth() > GetStackDepth(thread)) {
- return true;
- }
- }
- // If we are returning from a static intializer, that was implicitly
- // invoked by calling a static method and the caller is deoptimized,
- // then we have to deoptimize the stack without forcing interpreter
- // on the static method that was called originally. This problem can
- // be solved easily by forcing instrumentation on the called method,
- // because the instrumentation exit hook will recognise the need of
- // stack deoptimization by calling IsForcedInterpreterNeededForUpcall.
- return instrumentation->IsDeoptimized(m);
-}
-
-bool Dbg::IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m) {
- // The upcall can be null and in that case we don't need to do anything.
- if (m == nullptr) {
- return false;
- }
- // We can only interpret pure Java method.
- if (m->IsNative() || m->IsProxyMethod()) {
- return false;
- }
- const SingleStepControl* const ssc = thread->GetSingleStepControl();
- if (ssc != nullptr) {
- // The debugger is not interested in what is happening under the level
- // of the step, thus we only force interpreter when we are not below of
- // the step.
- if (ssc->GetStackDepth() >= GetStackDepth(thread)) {
- return true;
- }
- }
- if (thread->HasDebuggerShadowFrames()) {
- // We need to deoptimize the stack for the exception handling flow so that
- // we don't miss any deoptimization that should be done when there are
- // debugger shadow frames.
- return true;
- }
- // We have to require stack deoptimization if the upcall is deoptimized.
- return Runtime::Current()->GetInstrumentation()->IsDeoptimized(m);
-}
-
// Do we need to deoptimize the stack to handle an exception?
bool Dbg::IsForcedInterpreterNeededForExceptionImpl(Thread* thread) {
- const SingleStepControl* const ssc = thread->GetSingleStepControl();
- if (ssc != nullptr) {
- // We deopt to step into the catch handler.
- return true;
- }
// Deoptimization is required if at least one method in the stack needs it. However we
// skip frames that will be unwound (thus not executed).
bool needs_deoptimization = false;
@@ -3646,570 +181,6 @@ bool Dbg::IsForcedInterpreterNeededForExceptionImpl(Thread* thread) {
return needs_deoptimization;
}
-// Scoped utility class to suspend a thread so that we may do tasks such as walk its stack. Doesn't
-// cause suspension if the thread is the current thread.
-class ScopedDebuggerThreadSuspension {
- public:
- ScopedDebuggerThreadSuspension(Thread* self, JDWP::ObjectId thread_id)
- REQUIRES(!Locks::thread_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_) :
- thread_(nullptr),
- error_(JDWP::ERR_NONE),
- self_suspend_(false),
- other_suspend_(false) {
- ScopedObjectAccessUnchecked soa(self);
- thread_ = DecodeThread(soa, thread_id, &error_);
- if (error_ == JDWP::ERR_NONE) {
- if (thread_ == soa.Self()) {
- self_suspend_ = true;
- } else {
- Thread* suspended_thread;
- {
- ScopedThreadSuspension sts(self, kWaitingForDebuggerSuspension);
- jobject thread_peer = Dbg::GetObjectRegistry()->GetJObject(thread_id);
- bool timed_out;
- ThreadList* const thread_list = Runtime::Current()->GetThreadList();
- suspended_thread = thread_list->SuspendThreadByPeer(thread_peer,
- /* request_suspension= */ true,
- SuspendReason::kForDebugger,
- &timed_out);
- }
- if (suspended_thread == nullptr) {
- // Thread terminated from under us while suspending.
- error_ = JDWP::ERR_INVALID_THREAD;
- } else {
- CHECK_EQ(suspended_thread, thread_);
- other_suspend_ = true;
- }
- }
- }
- }
-
- Thread* GetThread() const {
- return thread_;
- }
-
- JDWP::JdwpError GetError() const {
- return error_;
- }
-
- ~ScopedDebuggerThreadSuspension() {
- if (other_suspend_) {
- bool resumed = Runtime::Current()->GetThreadList()->Resume(thread_,
- SuspendReason::kForDebugger);
- DCHECK(resumed);
- }
- }
-
- private:
- Thread* thread_;
- JDWP::JdwpError error_;
- bool self_suspend_;
- bool other_suspend_;
-};
-
-JDWP::JdwpError Dbg::ConfigureStep(JDWP::ObjectId thread_id, JDWP::JdwpStepSize step_size,
- JDWP::JdwpStepDepth step_depth) {
- Thread* self = Thread::Current();
- ScopedDebuggerThreadSuspension sts(self, thread_id);
- if (sts.GetError() != JDWP::ERR_NONE) {
- return sts.GetError();
- }
-
- // Work out what ArtMethod* we're in, the current line number, and how deep the stack currently
- // is for step-out.
- struct SingleStepStackVisitor : public StackVisitor {
- explicit SingleStepStackVisitor(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
- : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
- stack_depth(0),
- method(nullptr),
- line_number(-1) {}
-
- // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses
- // annotalysis.
- bool VisitFrame() override NO_THREAD_SAFETY_ANALYSIS {
- ArtMethod* m = GetMethod();
- if (!m->IsRuntimeMethod()) {
- ++stack_depth;
- if (method == nullptr) {
- const DexFile* dex_file = m->GetDexFile();
- method = m;
- if (dex_file != nullptr) {
- line_number = annotations::GetLineNumFromPC(dex_file, m, GetDexPc());
- }
- }
- }
- return true;
- }
-
- int stack_depth;
- ArtMethod* method;
- int32_t line_number;
- };
-
- Thread* const thread = sts.GetThread();
- SingleStepStackVisitor visitor(thread);
- visitor.WalkStack();
-
- // Allocate single step.
- SingleStepControl* single_step_control =
- new (std::nothrow) SingleStepControl(step_size, step_depth,
- visitor.stack_depth, visitor.method);
- if (single_step_control == nullptr) {
- LOG(ERROR) << "Failed to allocate SingleStepControl";
- return JDWP::ERR_OUT_OF_MEMORY;
- }
-
- ArtMethod* m = single_step_control->GetMethod();
- const int32_t line_number = visitor.line_number;
- // Note: if the thread is not running Java code (pure native thread), there is no "current"
- // method on the stack (and no line number either).
- if (m != nullptr && !m->IsNative()) {
- CodeItemDebugInfoAccessor accessor(m->DexInstructionDebugInfo());
- bool last_pc_valid = false;
- uint32_t last_pc = 0u;
- // Find the dex_pc values that correspond to the current line, for line-based single-stepping.
- accessor.DecodeDebugPositionInfo([&](const DexFile::PositionInfo& entry) {
- if (static_cast<int32_t>(entry.line_) == line_number) {
- if (!last_pc_valid) {
- // Everything from this address until the next line change is ours.
- last_pc = entry.address_;
- last_pc_valid = true;
- }
- // Otherwise, if we're already in a valid range for this line,
- // just keep going (shouldn't really happen)...
- } else if (last_pc_valid) { // and the line number is new
- // Add everything from the last entry up until here to the set
- for (uint32_t dex_pc = last_pc; dex_pc < entry.address_; ++dex_pc) {
- single_step_control->AddDexPc(dex_pc);
- }
- last_pc_valid = false;
- }
- return false; // There may be multiple entries for any given line.
- });
- // If the line number was the last in the position table...
- if (last_pc_valid) {
- for (uint32_t dex_pc = last_pc; dex_pc < accessor.InsnsSizeInCodeUnits(); ++dex_pc) {
- single_step_control->AddDexPc(dex_pc);
- }
- }
- }
-
- // Activate single-step in the thread.
- thread->ActivateSingleStepControl(single_step_control);
-
- if (VLOG_IS_ON(jdwp)) {
- VLOG(jdwp) << "Single-step thread: " << *thread;
- VLOG(jdwp) << "Single-step step size: " << single_step_control->GetStepSize();
- VLOG(jdwp) << "Single-step step depth: " << single_step_control->GetStepDepth();
- VLOG(jdwp) << "Single-step current method: "
- << ArtMethod::PrettyMethod(single_step_control->GetMethod());
- VLOG(jdwp) << "Single-step current line: " << line_number;
- VLOG(jdwp) << "Single-step current stack depth: " << single_step_control->GetStackDepth();
- VLOG(jdwp) << "Single-step dex_pc values:";
- for (uint32_t dex_pc : single_step_control->GetDexPcs()) {
- VLOG(jdwp) << StringPrintf(" %#x", dex_pc);
- }
- }
-
- return JDWP::ERR_NONE;
-}
-
-void Dbg::UnconfigureStep(JDWP::ObjectId thread_id) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- JDWP::JdwpError error;
- Thread* thread = DecodeThread(soa, thread_id, &error);
- if (error == JDWP::ERR_NONE) {
- thread->DeactivateSingleStepControl();
- }
-}
-
-static char JdwpTagToShortyChar(JDWP::JdwpTag tag) {
- switch (tag) {
- default:
- LOG(FATAL) << "unknown JDWP tag: " << PrintableChar(tag);
- UNREACHABLE();
-
- // Primitives.
- case JDWP::JT_BYTE: return 'B';
- case JDWP::JT_CHAR: return 'C';
- case JDWP::JT_FLOAT: return 'F';
- case JDWP::JT_DOUBLE: return 'D';
- case JDWP::JT_INT: return 'I';
- case JDWP::JT_LONG: return 'J';
- case JDWP::JT_SHORT: return 'S';
- case JDWP::JT_VOID: return 'V';
- case JDWP::JT_BOOLEAN: return 'Z';
-
- // Reference types.
- case JDWP::JT_ARRAY:
- case JDWP::JT_OBJECT:
- case JDWP::JT_STRING:
- case JDWP::JT_THREAD:
- case JDWP::JT_THREAD_GROUP:
- case JDWP::JT_CLASS_LOADER:
- case JDWP::JT_CLASS_OBJECT:
- return 'L';
- }
-}
-
-JDWP::JdwpError Dbg::PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thread_id,
- JDWP::ObjectId object_id, JDWP::RefTypeId class_id,
- JDWP::MethodId method_id, uint32_t arg_count,
- uint64_t arg_values[], JDWP::JdwpTag* arg_types,
- uint32_t options) {
- Thread* const self = Thread::Current();
- CHECK_EQ(self, GetDebugThread()) << "This must be called by the JDWP thread";
- const bool resume_all_threads = ((options & JDWP::INVOKE_SINGLE_THREADED) == 0);
-
- ThreadList* thread_list = Runtime::Current()->GetThreadList();
- Thread* targetThread = nullptr;
- {
- ScopedObjectAccessUnchecked soa(self);
- JDWP::JdwpError error;
- targetThread = DecodeThread(soa, thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- LOG(ERROR) << "InvokeMethod request for invalid thread id " << thread_id;
- return error;
- }
- if (targetThread->GetInvokeReq() != nullptr) {
- // Thread is already invoking a method on behalf of the debugger.
- LOG(ERROR) << "InvokeMethod request for thread already invoking a method: " << *targetThread;
- return JDWP::ERR_ALREADY_INVOKING;
- }
- if (!targetThread->IsReadyForDebugInvoke()) {
- // Thread is not suspended by an event so it cannot invoke a method.
- LOG(ERROR) << "InvokeMethod request for thread not stopped by event: " << *targetThread;
- return JDWP::ERR_INVALID_THREAD;
- }
-
- /*
- * According to the JDWP specs, we are expected to resume all threads (or only the
- * target thread) once. So if a thread has been suspended more than once (either by
- * the debugger for an event or by the runtime for GC), it will remain suspended before
- * the invoke is executed. This means the debugger is responsible to properly resume all
- * the threads it has suspended so the target thread can execute the method.
- *
- * However, for compatibility reason with older versions of debuggers (like Eclipse), we
- * fully resume all threads (by canceling *all* debugger suspensions) when the debugger
- * wants us to resume all threads. This is to avoid ending up in deadlock situation.
- *
- * On the other hand, if we are asked to only resume the target thread, then we follow the
- * JDWP specs by resuming that thread only once. This means the thread will remain suspended
- * if it has been suspended more than once before the invoke (and again, this is the
- * responsibility of the debugger to properly resume that thread before invoking a method).
- */
- int suspend_count;
- {
- MutexLock mu2(soa.Self(), *Locks::thread_suspend_count_lock_);
- suspend_count = targetThread->GetSuspendCount();
- }
- if (suspend_count > 1 && resume_all_threads) {
- // The target thread will remain suspended even after we resume it. Let's emit a warning
- // to indicate the invoke won't be executed until the thread is resumed.
- LOG(WARNING) << *targetThread << " suspended more than once (suspend count == "
- << suspend_count << "). This thread will invoke the method only once "
- << "it is fully resumed.";
- }
-
- ObjPtr<mirror::Object> receiver = gRegistry->Get<mirror::Object>(object_id, &error);
- if (error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
-
- gRegistry->Get<mirror::Object>(thread_id, &error);
- if (error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
-
- ObjPtr<mirror::Class> c = DecodeClass(class_id, &error);
- if (c == nullptr) {
- return error;
- }
-
- ArtMethod* m = FromMethodId(method_id);
- if (m->IsStatic() != (receiver == nullptr)) {
- return JDWP::ERR_INVALID_METHODID;
- }
- if (m->IsStatic()) {
- if (m->GetDeclaringClass() != c) {
- return JDWP::ERR_INVALID_METHODID;
- }
- } else {
- if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
- return JDWP::ERR_INVALID_METHODID;
- }
- }
-
- // Check the argument list matches the method.
- uint32_t shorty_len = 0;
- const char* shorty = m->GetShorty(&shorty_len);
- if (shorty_len - 1 != arg_count) {
- return JDWP::ERR_ILLEGAL_ARGUMENT;
- }
-
- {
- StackHandleScope<2> hs(soa.Self());
- HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&receiver));
- HandleWrapperObjPtr<mirror::Class> h_klass(hs.NewHandleWrapper(&c));
- const dex::TypeList* types = m->GetParameterTypeList();
- for (size_t i = 0; i < arg_count; ++i) {
- if (shorty[i + 1] != JdwpTagToShortyChar(arg_types[i])) {
- return JDWP::ERR_ILLEGAL_ARGUMENT;
- }
-
- if (shorty[i + 1] == 'L') {
- // Did we really get an argument of an appropriate reference type?
- ObjPtr<mirror::Class> parameter_type =
- m->ResolveClassFromTypeIndex(types->GetTypeItem(i).type_idx_);
- ObjPtr<mirror::Object> argument = gRegistry->Get<mirror::Object>(arg_values[i], &error);
- if (error != JDWP::ERR_NONE) {
- return JDWP::ERR_INVALID_OBJECT;
- }
- if (argument != nullptr && !argument->InstanceOf(parameter_type)) {
- return JDWP::ERR_ILLEGAL_ARGUMENT;
- }
-
- // Turn the on-the-wire ObjectId into a jobject.
- jvalue& v = reinterpret_cast<jvalue&>(arg_values[i]);
- v.l = gRegistry->GetJObject(arg_values[i]);
- }
- }
- }
-
- // Allocates a DebugInvokeReq.
- DebugInvokeReq* req = new (std::nothrow) DebugInvokeReq(
- request_id, thread_id, receiver, c, m, options, arg_values, arg_count);
- if (req == nullptr) {
- LOG(ERROR) << "Failed to allocate DebugInvokeReq";
- return JDWP::ERR_OUT_OF_MEMORY;
- }
-
- // Attaches the DebugInvokeReq to the target thread so it executes the method when
- // it is resumed. Once the invocation completes, the target thread will delete it before
- // suspending itself (see ThreadList::SuspendSelfForDebugger).
- targetThread->SetDebugInvokeReq(req);
- }
-
- // The fact that we've released the thread list lock is a bit risky --- if the thread goes
- // away we're sitting high and dry -- but we must release this before the UndoDebuggerSuspensions
- // call.
- if (resume_all_threads) {
- VLOG(jdwp) << " Resuming all threads";
- thread_list->UndoDebuggerSuspensions();
- } else {
- VLOG(jdwp) << " Resuming event thread only";
- bool resumed = thread_list->Resume(targetThread, SuspendReason::kForDebugger);
- DCHECK(resumed);
- }
-
- return JDWP::ERR_NONE;
-}
-
-void Dbg::ExecuteMethod(DebugInvokeReq* pReq) {
- Thread* const self = Thread::Current();
- CHECK_NE(self, GetDebugThread()) << "This must be called by the event thread";
-
- ScopedObjectAccess soa(self);
-
- // We can be called while an exception is pending. We need
- // to preserve that across the method invocation.
- StackHandleScope<1> hs(soa.Self());
- Handle<mirror::Throwable> old_exception = hs.NewHandle(soa.Self()->GetException());
- soa.Self()->ClearException();
-
- // Execute the method then sends reply to the debugger.
- ExecuteMethodWithoutPendingException(soa, pReq);
-
- // If an exception was pending before the invoke, restore it now.
- if (old_exception != nullptr) {
- soa.Self()->SetException(old_exception.Get());
- }
-}
-
-// Helper function: write a variable-width value into the output input buffer.
-static void WriteValue(JDWP::ExpandBuf* pReply, int width, uint64_t value) {
- switch (width) {
- case 1:
- expandBufAdd1(pReply, value);
- break;
- case 2:
- expandBufAdd2BE(pReply, value);
- break;
- case 4:
- expandBufAdd4BE(pReply, value);
- break;
- case 8:
- expandBufAdd8BE(pReply, value);
- break;
- default:
- LOG(FATAL) << width;
- UNREACHABLE();
- }
-}
-
-void Dbg::ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) {
- soa.Self()->AssertNoPendingException();
-
- // Translate the method through the vtable, unless the debugger wants to suppress it.
- StackArtMethodHandleScope<2> rhs(soa.Self());
- MutableReflectiveHandle<ArtMethod> m(rhs.NewHandle(pReq->method));
- PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- if ((pReq->options & JDWP::INVOKE_NONVIRTUAL) == 0 && pReq->receiver.Read() != nullptr) {
- MutableReflectiveHandle<ArtMethod> actual_method(rhs.NewHandle(
- pReq->klass.Read()->FindVirtualMethodForVirtualOrInterface(m.Get(), image_pointer_size)));
- if (actual_method.Get() != m.Get()) {
- VLOG(jdwp) << "ExecuteMethod translated " << m->PrettyMethod()
- << " to " << actual_method->PrettyMethod();
- m = actual_method;
- }
- }
- VLOG(jdwp) << "ExecuteMethod " << m->PrettyMethod()
- << " receiver=" << pReq->receiver.Read()
- << " arg_count=" << pReq->arg_count;
- CHECK(m != nullptr);
-
- static_assert(sizeof(jvalue) == sizeof(uint64_t), "jvalue and uint64_t have different sizes.");
-
- // Invoke the method.
- ScopedLocalRef<jobject> ref(soa.Env(), soa.AddLocalReference<jobject>(pReq->receiver.Read()));
- JValue result = InvokeWithJValues(soa, ref.get(), jni::EncodeArtMethod(m),
- reinterpret_cast<jvalue*>(pReq->arg_values.get()));
-
- // Prepare JDWP ids for the reply.
- JDWP::JdwpTag result_tag = BasicTagFromDescriptor(m->GetShorty());
- const bool is_object_result = (result_tag == JDWP::JT_OBJECT);
- StackHandleScope<3> hs(soa.Self());
- Handle<mirror::Object> object_result = hs.NewHandle(is_object_result ? result.GetL() : nullptr);
- Handle<mirror::Throwable> exception = hs.NewHandle(soa.Self()->GetException());
- soa.Self()->ClearException();
-
- if (!IsDebuggerActive()) {
- // The debugger detached: we must not re-suspend threads. We also don't need to fill the reply
- // because it won't be sent either.
- return;
- }
-
- JDWP::ObjectId exceptionObjectId = gRegistry->Add(exception);
- uint64_t result_value = 0;
- if (exceptionObjectId != 0) {
- VLOG(jdwp) << " JDWP invocation returning with exception=" << exception.Get()
- << " " << exception->Dump();
- result_value = 0;
- } else if (is_object_result) {
- /* if no exception was thrown, examine object result more closely */
- JDWP::JdwpTag new_tag = TagFromObject(soa, object_result.Get());
- if (new_tag != result_tag) {
- VLOG(jdwp) << " JDWP promoted result from " << result_tag << " to " << new_tag;
- result_tag = new_tag;
- }
-
- // Register the object in the registry and reference its ObjectId. This ensures
- // GC safety and prevents from accessing stale reference if the object is moved.
- result_value = gRegistry->Add(object_result.Get());
- } else {
- // Primitive result.
- DCHECK(IsPrimitiveTag(result_tag));
- result_value = result.GetJ();
- }
- const bool is_constructor = m->IsConstructor() && !m->IsStatic();
- if (is_constructor) {
- // If we invoked a constructor (which actually returns void), return the receiver,
- // unless we threw, in which case we return null.
- DCHECK_EQ(JDWP::JT_VOID, result_tag);
- if (exceptionObjectId == 0) {
- if (m->GetDeclaringClass()->IsStringClass()) {
- // For string constructors, the new string is remapped to the receiver (stored in ref).
- Handle<mirror::Object> decoded_ref = hs.NewHandle(soa.Self()->DecodeJObject(ref.get()));
- result_value = gRegistry->Add(decoded_ref);
- result_tag = TagFromObject(soa, decoded_ref.Get());
- } else {
- // TODO we could keep the receiver ObjectId in the DebugInvokeReq to avoid looking into the
- // object registry.
- result_value = GetObjectRegistry()->Add(pReq->receiver.Read());
- result_tag = TagFromObject(soa, pReq->receiver.Read());
- }
- } else {
- result_value = 0;
- result_tag = JDWP::JT_OBJECT;
- }
- }
-
- // Suspend other threads if the invoke is not single-threaded.
- if ((pReq->options & JDWP::INVOKE_SINGLE_THREADED) == 0) {
- ScopedThreadSuspension sts(soa.Self(), kWaitingForDebuggerSuspension);
- // Avoid a deadlock between GC and debugger where GC gets suspended during GC. b/25800335.
- gc::ScopedGCCriticalSection gcs(soa.Self(), gc::kGcCauseDebugger, gc::kCollectorTypeDebugger);
- VLOG(jdwp) << " Suspending all threads";
- Runtime::Current()->GetThreadList()->SuspendAllForDebugger();
- }
-
- VLOG(jdwp) << " --> returned " << result_tag
- << StringPrintf(" %#" PRIx64 " (except=%#" PRIx64 ")", result_value,
- exceptionObjectId);
-
- // Show detailed debug output.
- if (result_tag == JDWP::JT_STRING && exceptionObjectId == 0) {
- if (result_value != 0) {
- if (VLOG_IS_ON(jdwp)) {
- std::string result_string;
- JDWP::JdwpError error = Dbg::StringToUtf8(result_value, &result_string);
- CHECK_EQ(error, JDWP::ERR_NONE);
- VLOG(jdwp) << " string '" << result_string << "'";
- }
- } else {
- VLOG(jdwp) << " string (null)";
- }
- }
-
- // Attach the reply to DebugInvokeReq so it can be sent to the debugger when the event thread
- // is ready to suspend.
- BuildInvokeReply(pReq->reply, pReq->request_id, result_tag, result_value, exceptionObjectId);
-}
-
-void Dbg::BuildInvokeReply(JDWP::ExpandBuf* pReply, uint32_t request_id, JDWP::JdwpTag result_tag,
- uint64_t result_value, JDWP::ObjectId exception) {
- // Make room for the JDWP header since we do not know the size of the reply yet.
- JDWP::expandBufAddSpace(pReply, kJDWPHeaderLen);
-
- size_t width = GetTagWidth(result_tag);
- JDWP::expandBufAdd1(pReply, result_tag);
- if (width != 0) {
- WriteValue(pReply, width, result_value);
- }
- JDWP::expandBufAdd1(pReply, JDWP::JT_OBJECT);
- JDWP::expandBufAddObjectId(pReply, exception);
-
- // Now we know the size, we can complete the JDWP header.
- uint8_t* buf = expandBufGetBuffer(pReply);
- JDWP::Set4BE(buf + kJDWPHeaderSizeOffset, expandBufGetLength(pReply));
- JDWP::Set4BE(buf + kJDWPHeaderIdOffset, request_id);
- JDWP::Set1(buf + kJDWPHeaderFlagsOffset, kJDWPFlagReply); // flags
- JDWP::Set2BE(buf + kJDWPHeaderErrorCodeOffset, JDWP::ERR_NONE);
-}
-
-void Dbg::FinishInvokeMethod(DebugInvokeReq* pReq) {
- CHECK_NE(Thread::Current(), GetDebugThread()) << "This must be called by the event thread";
-
- JDWP::ExpandBuf* const pReply = pReq->reply;
- CHECK(pReply != nullptr) << "No reply attached to DebugInvokeReq";
-
- // We need to prevent other threads (including JDWP thread) from interacting with the debugger
- // while we send the reply but are not yet suspended. The JDWP token will be released just before
- // we suspend ourself again (see ThreadList::SuspendSelfForDebugger).
- gJdwpState->AcquireJdwpTokenForEvent(pReq->thread_id);
-
- // Send the reply unless the debugger detached before the completion of the method.
- if (IsDebuggerActive()) {
- const size_t replyDataLength = expandBufGetLength(pReply) - kJDWPHeaderLen;
- VLOG(jdwp) << StringPrintf("REPLY INVOKE id=0x%06x (length=%zu)",
- pReq->request_id, replyDataLength);
-
- gJdwpState->SendRequest(pReply);
- } else {
- VLOG(jdwp) << "Not sending invoke reply because debugger detached";
- }
-}
bool Dbg::DdmHandleChunk(JNIEnv* env,
uint32_t type,
@@ -4293,52 +264,6 @@ bool Dbg::DdmHandleChunk(JNIEnv* env,
return true;
}
-/*
- * "request" contains a full JDWP packet, possibly with multiple chunks. We
- * need to process each, accumulate the replies, and ship the whole thing
- * back.
- *
- * Returns "true" if we have a reply. The reply buffer is newly allocated,
- * and includes the chunk type/length, followed by the data.
- *
- * OLD-TODO: we currently assume that the request and reply include a single
- * chunk. If this becomes inconvenient we will need to adapt.
- */
-bool Dbg::DdmHandlePacket(JDWP::Request* request, uint8_t** pReplyBuf, int* pReplyLen) {
- Thread* self = Thread::Current();
- JNIEnv* env = self->GetJniEnv();
-
- uint32_t type = request->ReadUnsigned32("type");
- uint32_t length = request->ReadUnsigned32("length");
-
- // Create a byte[] corresponding to 'request'.
- size_t request_length = request->size();
- // Run through and find all chunks. [Currently just find the first.]
- if (length != request_length) {
- LOG(WARNING) << StringPrintf("bad chunk found (len=%u pktLen=%zd)", length, request_length);
- return false;
- }
-
- ArrayRef<const jbyte> data(reinterpret_cast<const jbyte*>(request->data()), request_length);
- std::vector<uint8_t> out_data;
- uint32_t out_type = 0;
- request->Skip(request_length);
- if (!DdmHandleChunk(env, type, data, &out_type, &out_data) || out_data.empty()) {
- return false;
- }
- const uint32_t kDdmHeaderSize = 8;
- *pReplyLen = out_data.size() + kDdmHeaderSize;
- *pReplyBuf = new uint8_t[out_data.size() + kDdmHeaderSize];
- memcpy((*pReplyBuf) + kDdmHeaderSize, out_data.data(), out_data.size());
- JDWP::Set4BE(*pReplyBuf, out_type);
- JDWP::Set4BE((*pReplyBuf) + 4, static_cast<uint32_t>(out_data.size()));
- VLOG(jdwp)
- << StringPrintf("dvmHandleDdm returning type=%.4s", reinterpret_cast<char*>(*pReplyBuf))
- << "0x" << std::hex << reinterpret_cast<uintptr_t>(*pReplyBuf) << std::dec
- << " len= " << out_data.size();
- return true;
-}
-
void Dbg::DdmBroadcast(bool connect) {
VLOG(jdwp) << "Broadcasting DDM " << (connect ? "connect" : "disconnect") << "...";
@@ -4369,6 +294,7 @@ void Dbg::DdmDisconnected() {
gDdmThreadNotification = false;
}
+
/*
* Send a notification when a thread starts, stops, or changes its name.
*
@@ -4376,6 +302,7 @@ void Dbg::DdmDisconnected() {
* first enabled, it's possible for "thread" to be actively executing.
*/
void Dbg::DdmSendThreadNotification(Thread* t, uint32_t type) {
+ Locks::mutator_lock_->AssertNotExclusiveHeld(Thread::Current());
if (!gDdmThreadNotification) {
return;
}
@@ -4383,24 +310,23 @@ void Dbg::DdmSendThreadNotification(Thread* t, uint32_t type) {
RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks();
if (type == CHUNK_TYPE("THDE")) {
uint8_t buf[4];
- JDWP::Set4BE(&buf[0], t->GetThreadId());
+ Set4BE(&buf[0], t->GetThreadId());
cb->DdmPublishChunk(CHUNK_TYPE("THDE"), ArrayRef<const uint8_t>(buf));
} else {
CHECK(type == CHUNK_TYPE("THCR") || type == CHUNK_TYPE("THNM")) << type;
- ScopedObjectAccessUnchecked soa(Thread::Current());
- StackHandleScope<1> hs(soa.Self());
+ StackHandleScope<1> hs(Thread::Current());
Handle<mirror::String> name(hs.NewHandle(t->GetThreadName()));
size_t char_count = (name != nullptr) ? name->GetLength() : 0;
const jchar* chars = (name != nullptr) ? name->GetValue() : nullptr;
bool is_compressed = (name != nullptr) ? name->IsCompressed() : false;
std::vector<uint8_t> bytes;
- JDWP::Append4BE(bytes, t->GetThreadId());
+ Append4BE(bytes, t->GetThreadId());
if (is_compressed) {
const uint8_t* chars_compressed = name->GetValueCompressed();
- JDWP::AppendUtf16CompressedBE(bytes, chars_compressed, char_count);
+ AppendUtf16CompressedBE(bytes, chars_compressed, char_count);
} else {
- JDWP::AppendUtf16BE(bytes, chars, char_count);
+ AppendUtf16BE(bytes, chars, char_count);
}
CHECK_EQ(bytes.size(), char_count*2 + sizeof(uint32_t)*2);
cb->DdmPublishChunk(type, ArrayRef<const uint8_t>(bytes));
@@ -4411,30 +337,25 @@ void Dbg::DdmSetThreadNotification(bool enable) {
// Enable/disable thread notifications.
gDdmThreadNotification = enable;
if (enable) {
- // Suspend the VM then post thread start notifications for all threads. Threads attaching will
- // see a suspension in progress and block until that ends. They then post their own start
- // notification.
- SuspendVM();
- std::list<Thread*> threads;
+ // Use a Checkpoint to cause every currently running thread to send their own notification when
+ // able. We then wait for every thread thread active at the time to post the creation
+ // notification. Threads created later will send this themselves.
Thread* self = Thread::Current();
- {
- MutexLock mu(self, *Locks::thread_list_lock_);
- threads = Runtime::Current()->GetThreadList()->GetList();
- }
- {
- ScopedObjectAccess soa(self);
- for (Thread* thread : threads) {
- Dbg::DdmSendThreadNotification(thread, CHUNK_TYPE("THCR"));
- }
- }
- ResumeVM();
+ ScopedObjectAccess soa(self);
+ Barrier finish_barrier(0);
+ FunctionClosure fc([&](Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_) {
+ Thread* cls_self = Thread::Current();
+ Locks::mutator_lock_->AssertSharedHeld(cls_self);
+ Dbg::DdmSendThreadNotification(thread, CHUNK_TYPE("THCR"));
+ finish_barrier.Pass(cls_self);
+ });
+ size_t checkpoints = Runtime::Current()->GetThreadList()->RunCheckpoint(&fc);
+ ScopedThreadSuspension sts(self, ThreadState::kWaitingForCheckPointsToRun);
+ finish_barrier.Increment(self, checkpoints);
}
}
void Dbg::PostThreadStartOrStop(Thread* t, uint32_t type) {
- if (IsDebuggerActive()) {
- gJdwpState->PostThreadChange(t, type == CHUNK_TYPE("THCR"));
- }
Dbg::DdmSendThreadNotification(t, type);
}
@@ -4446,10 +367,6 @@ void Dbg::PostThreadDeath(Thread* t) {
Dbg::PostThreadStartOrStop(t, CHUNK_TYPE("THDE"));
}
-JDWP::JdwpState* Dbg::GetJdwpState() {
- return gJdwpState;
-}
-
int Dbg::DdmHandleHpifChunk(HpifWhen when) {
if (when == HPIF_WHEN_NOW) {
DdmSendHeapInfo(when);
@@ -4514,14 +431,14 @@ void Dbg::DdmSendHeapInfo(HpifWhen reason) {
uint8_t heap_count = 1;
gc::Heap* heap = Runtime::Current()->GetHeap();
std::vector<uint8_t> bytes;
- JDWP::Append4BE(bytes, heap_count);
- JDWP::Append4BE(bytes, 1); // Heap id (bogus; we only have one heap).
- JDWP::Append8BE(bytes, MilliTime());
- JDWP::Append1BE(bytes, reason);
- JDWP::Append4BE(bytes, heap->GetMaxMemory()); // Max allowed heap size in bytes.
- JDWP::Append4BE(bytes, heap->GetTotalMemory()); // Current heap size in bytes.
- JDWP::Append4BE(bytes, heap->GetBytesAllocated());
- JDWP::Append4BE(bytes, heap->GetObjectsAllocated());
+ Append4BE(bytes, heap_count);
+ Append4BE(bytes, 1); // Heap id (bogus; we only have one heap).
+ Append8BE(bytes, MilliTime());
+ Append1BE(bytes, reason);
+ Append4BE(bytes, heap->GetMaxMemory()); // Max allowed heap size in bytes.
+ Append4BE(bytes, heap->GetTotalMemory()); // Current heap size in bytes.
+ Append4BE(bytes, heap->GetBytesAllocated());
+ Append4BE(bytes, heap->GetObjectsAllocated());
CHECK_EQ(bytes.size(), 4U + (heap_count * (4 + 8 + 1 + 4 + 4 + 4 + 4)));
Runtime::Current()->GetRuntimeCallbacks()->DdmPublishChunk(CHUNK_TYPE("HPIF"),
ArrayRef<const uint8_t>(bytes));
@@ -4587,15 +504,15 @@ class HeapChunkContext {
}
// Start a new HPSx chunk.
- JDWP::Write4BE(&p_, 1); // Heap id (bogus; we only have one heap).
- JDWP::Write1BE(&p_, 8); // Size of allocation unit, in bytes.
+ Write4BE(&p_, 1); // Heap id (bogus; we only have one heap).
+ Write1BE(&p_, 8); // Size of allocation unit, in bytes.
- JDWP::Write4BE(&p_, reinterpret_cast<uintptr_t>(chunk_ptr)); // virtual address of segment start.
- JDWP::Write4BE(&p_, 0); // offset of this piece (relative to the virtual address).
+ Write4BE(&p_, reinterpret_cast<uintptr_t>(chunk_ptr)); // virtual address of segment start.
+ Write4BE(&p_, 0); // offset of this piece (relative to the virtual address).
// [u4]: length of piece, in allocation units
// We won't know this until we're done, so save the offset and stuff in a dummy value.
pieceLenField_ = p_;
- JDWP::Write4BE(&p_, 0x55555555);
+ Write4BE(&p_, 0x55555555);
needHeader_ = false;
}
@@ -4608,7 +525,7 @@ class HeapChunkContext {
// Patch the "length of piece" field.
CHECK_LE(&buf_[0], pieceLenField_);
CHECK_LE(pieceLenField_, p_);
- JDWP::Set4BE(pieceLenField_, totalAllocationUnits_);
+ Set4BE(pieceLenField_, totalAllocationUnits_);
ArrayRef<const uint8_t> out(&buf_[0], p_ - &buf_[0]);
Runtime::Current()->GetRuntimeCallbacks()->DdmPublishChunk(type_, out);
@@ -4787,6 +704,7 @@ class HeapChunkContext {
DISALLOW_COPY_AND_ASSIGN(HeapChunkContext);
};
+
void Dbg::DdmSendHeapSegments(bool native) {
Dbg::HpsgWhen when = native ? gDdmNhsgWhen : gDdmHpsgWhen;
Dbg::HpsgWhat what = native ? gDdmNhsgWhat : gDdmHpsgWhat;
@@ -4800,7 +718,7 @@ void Dbg::DdmSendHeapSegments(bool native) {
// First, send a heap start chunk.
uint8_t heap_id[4];
- JDWP::Set4BE(&heap_id[0], 1); // Heap id (bogus; we only have one heap).
+ Set4BE(&heap_id[0], 1); // Heap id (bogus; we only have one heap).
cb->DdmPublishChunk(native ? CHUNK_TYPE("NHST") : CHUNK_TYPE("HPST"),
ArrayRef<const uint8_t>(heap_id));
Thread* self = Thread::Current();
@@ -4869,41 +787,6 @@ void Dbg::SetAllocTrackingEnabled(bool enable) {
gc::AllocRecordObjectMap::SetAllocTrackingEnabled(enable);
}
-void Dbg::DumpRecentAllocations() {
- ScopedObjectAccess soa(Thread::Current());
- MutexLock mu(soa.Self(), *Locks::alloc_tracker_lock_);
- if (!Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()) {
- LOG(INFO) << "Not recording tracked allocations";
- return;
- }
- gc::AllocRecordObjectMap* records = Runtime::Current()->GetHeap()->GetAllocationRecords();
- CHECK(records != nullptr);
-
- const uint16_t capped_count = CappedAllocRecordCount(records->GetRecentAllocationSize());
- uint16_t count = capped_count;
-
- LOG(INFO) << "Tracked allocations, (count=" << count << ")";
- for (auto it = records->RBegin(), end = records->REnd();
- count > 0 && it != end; count--, it++) {
- const gc::AllocRecord* record = &it->second;
-
- LOG(INFO) << StringPrintf(" Thread %-2d %6zd bytes ", record->GetTid(), record->ByteCount())
- << mirror::Class::PrettyClass(record->GetClass());
-
- for (size_t stack_frame = 0, depth = record->GetDepth(); stack_frame < depth; ++stack_frame) {
- const gc::AllocRecordStackTraceElement& stack_element = record->StackElement(stack_frame);
- ArtMethod* m = stack_element.GetMethod();
- LOG(INFO) << " " << ArtMethod::PrettyMethod(m) << " line "
- << stack_element.ComputeLineNumber();
- }
-
- // pause periodically to help logcat catch up
- if ((count % 5) == 0) {
- usleep(40000);
- }
- }
-}
-
class StringTable {
private:
struct Entry {
@@ -4995,7 +878,7 @@ class StringTable {
size_t s_len = CountModifiedUtf8Chars(entry.data);
std::unique_ptr<uint16_t[]> s_utf16(new uint16_t[s_len]);
ConvertModifiedUtf8ToUtf16(s_utf16.get(), entry.data);
- JDWP::AppendUtf16BE(bytes, s_utf16.get(), s_len);
+ AppendUtf16BE(bytes, s_utf16.get(), s_len);
}
}
@@ -5008,6 +891,7 @@ class StringTable {
DISALLOW_COPY_AND_ASSIGN(StringTable);
};
+
static const char* GetMethodSourceFile(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(method != nullptr);
@@ -5132,21 +1016,21 @@ jbyteArray Dbg::GetRecentAllocations() {
const int kMessageHeaderLen = 15;
const int kEntryHeaderLen = 9;
const int kStackFrameLen = 8;
- JDWP::Append1BE(bytes, kMessageHeaderLen);
- JDWP::Append1BE(bytes, kEntryHeaderLen);
- JDWP::Append1BE(bytes, kStackFrameLen);
+ Append1BE(bytes, kMessageHeaderLen);
+ Append1BE(bytes, kEntryHeaderLen);
+ Append1BE(bytes, kStackFrameLen);
// (2b) number of entries
// (4b) offset to string table from start of message
// (2b) number of class name strings
// (2b) number of method name strings
// (2b) number of source file name strings
- JDWP::Append2BE(bytes, capped_count);
+ Append2BE(bytes, capped_count);
size_t string_table_offset = bytes.size();
- JDWP::Append4BE(bytes, 0); // We'll patch this later...
- JDWP::Append2BE(bytes, class_names.Size());
- JDWP::Append2BE(bytes, method_names.Size());
- JDWP::Append2BE(bytes, filenames.Size());
+ Append4BE(bytes, 0); // We'll patch this later...
+ Append2BE(bytes, class_names.Size());
+ Append2BE(bytes, method_names.Size());
+ Append2BE(bytes, filenames.Size());
VLOG(jdwp) << "Dumping allocations with stacks";
@@ -5169,10 +1053,10 @@ jbyteArray Dbg::GetRecentAllocations() {
size_t stack_depth = record->GetDepth();
size_t allocated_object_class_name_index =
class_names.IndexOf(record->GetClassDescriptor(&temp));
- JDWP::Append4BE(bytes, record->ByteCount());
- JDWP::Append2BE(bytes, static_cast<uint16_t>(record->GetTid()));
- JDWP::Append2BE(bytes, allocated_object_class_name_index);
- JDWP::Append1BE(bytes, stack_depth);
+ Append4BE(bytes, record->ByteCount());
+ Append2BE(bytes, static_cast<uint16_t>(record->GetTid()));
+ Append2BE(bytes, allocated_object_class_name_index);
+ Append1BE(bytes, stack_depth);
for (size_t stack_frame = 0; stack_frame < stack_depth; ++stack_frame) {
// For each stack frame:
@@ -5184,10 +1068,10 @@ jbyteArray Dbg::GetRecentAllocations() {
size_t class_name_index = class_names.IndexOf(m->GetDeclaringClassDescriptor());
size_t method_name_index = method_names.IndexOf(m->GetName());
size_t file_name_index = filenames.IndexOf(GetMethodSourceFile(m));
- JDWP::Append2BE(bytes, class_name_index);
- JDWP::Append2BE(bytes, method_name_index);
- JDWP::Append2BE(bytes, file_name_index);
- JDWP::Append2BE(bytes, record->StackElement(stack_frame).ComputeLineNumber());
+ Append2BE(bytes, class_name_index);
+ Append2BE(bytes, method_name_index);
+ Append2BE(bytes, file_name_index);
+ Append2BE(bytes, record->StackElement(stack_frame).ComputeLineNumber());
}
}
@@ -5197,7 +1081,7 @@ jbyteArray Dbg::GetRecentAllocations() {
// (xb) class name strings
// (xb) method name strings
// (xb) source file strings
- JDWP::Set4BE(&bytes[string_table_offset], bytes.size());
+ Set4BE(&bytes[string_table_offset], bytes.size());
class_names.WriteTo(bytes);
method_names.WriteTo(bytes);
filenames.WriteTo(bytes);
@@ -5212,23 +1096,6 @@ jbyteArray Dbg::GetRecentAllocations() {
return result;
}
-ArtMethod* DeoptimizationRequest::Method() const {
- return jni::DecodeArtMethod(method_);
-}
-
-void DeoptimizationRequest::SetMethod(ArtMethod* m) {
- method_ = jni::EncodeArtMethod(m);
-}
-
-void Dbg::VisitRoots(RootVisitor* visitor) {
- // Visit breakpoint roots, used to prevent unloading of methods with breakpoints.
- ReaderMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_);
- BufferedRootVisitor<128> root_visitor(visitor, RootInfo(kRootVMInternal));
- for (Breakpoint& breakpoint : gBreakpoints) {
- breakpoint.Method()->VisitRoots(root_visitor, kRuntimePointerSize);
- }
-}
-
void Dbg::DbgThreadLifecycleCallback::ThreadStart(Thread* self) {
Dbg::PostThreadStart(self);
}
@@ -5237,12 +1104,4 @@ void Dbg::DbgThreadLifecycleCallback::ThreadDeath(Thread* self) {
Dbg::PostThreadDeath(self);
}
-void Dbg::DbgClassLoadCallback::ClassLoad(Handle<mirror::Class> klass ATTRIBUTE_UNUSED) {
- // Ignore ClassLoad;
-}
-void Dbg::DbgClassLoadCallback::ClassPrepare(Handle<mirror::Class> temp_klass ATTRIBUTE_UNUSED,
- Handle<mirror::Class> klass) {
- Dbg::PostClassPrepare(klass.Get());
-}
-
} // namespace art
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 95cfca04b18..65dc13d4390 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-/*
- * Dalvik-specific side of debugger support. (The JDWP code is intended to
- * be relatively generic.)
- */
#ifndef ART_RUNTIME_DEBUGGER_H_
#define ART_RUNTIME_DEBUGGER_H_
@@ -27,599 +23,38 @@
#include <string>
#include <vector>
+#include "art_method.h"
#include "base/array_ref.h"
-#include "class_linker.h"
-#include "gc_root.h"
-#include "handle.h"
-#include "jdwp/jdwp.h"
+#include "base/locks.h"
+#include "base/logging.h"
#include "jni.h"
-#include "jvalue.h"
-#include "obj_ptr.h"
#include "runtime_callbacks.h"
#include "thread.h"
#include "thread_state.h"
namespace art {
-namespace mirror {
-class Class;
-class Object;
-class Throwable;
-} // namespace mirror
-class ArtField;
-class ArtMethod;
-class ObjectRegistry;
-class ScopedObjectAccess;
-class ScopedObjectAccessUnchecked;
-class StackVisitor;
-class Thread;
-
-struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback {
- bool IsMethodBeingInspected(ArtMethod* method) override REQUIRES_SHARED(Locks::mutator_lock_);
- bool IsMethodSafeToJit(ArtMethod* method) override REQUIRES_SHARED(Locks::mutator_lock_);
- bool MethodNeedsDebugVersion(ArtMethod* method) override REQUIRES_SHARED(Locks::mutator_lock_);
-};
-
-struct DebuggerDdmCallback : public DdmCallback {
- void DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data)
- override REQUIRES_SHARED(Locks::mutator_lock_);
-};
-
-struct InternalDebuggerControlCallback : public DebuggerControlCallback {
- void StartDebugger() override;
- void StopDebugger() override;
- bool IsDebuggerConfigured() override;
-};
-
-/*
- * Invoke-during-breakpoint support.
- */
-struct DebugInvokeReq {
- DebugInvokeReq(uint32_t invoke_request_id,
- JDWP::ObjectId invoke_thread_id,
- ObjPtr<mirror::Object> invoke_receiver,
- ObjPtr<mirror::Class> invoke_class,
- ArtMethod* invoke_method,
- uint32_t invoke_options,
- uint64_t args[],
- uint32_t args_count)
- : request_id(invoke_request_id),
- thread_id(invoke_thread_id),
- receiver(invoke_receiver),
- klass(invoke_class),
- method(invoke_method),
- arg_count(args_count),
- arg_values(args),
- options(invoke_options),
- reply(JDWP::expandBufAlloc()) {
- }
-
- ~DebugInvokeReq() {
- JDWP::expandBufFree(reply);
- }
-
- // Request
- const uint32_t request_id;
- const JDWP::ObjectId thread_id;
- GcRoot<mirror::Object> receiver; // not used for ClassType.InvokeMethod.
- GcRoot<mirror::Class> klass;
- ArtMethod* const method;
- const uint32_t arg_count;
- std::unique_ptr<uint64_t[]> arg_values; // will be null if arg_count_ == 0. We take ownership
- // of this array so we must delete it upon destruction.
- const uint32_t options;
-
- // Reply
- JDWP::ExpandBuf* const reply;
-
- void VisitRoots(RootVisitor* visitor, const RootInfo& root_info)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- private:
- DISALLOW_COPY_AND_ASSIGN(DebugInvokeReq);
-};
-
-// Thread local data-structure that holds fields for controlling single-stepping.
-class SingleStepControl {
- public:
- SingleStepControl(JDWP::JdwpStepSize step_size, JDWP::JdwpStepDepth step_depth,
- int stack_depth, ArtMethod* method)
- : step_size_(step_size), step_depth_(step_depth),
- stack_depth_(stack_depth), method_(method) {
- }
-
- JDWP::JdwpStepSize GetStepSize() const {
- return step_size_;
- }
-
- JDWP::JdwpStepDepth GetStepDepth() const {
- return step_depth_;
- }
-
- int GetStackDepth() const {
- return stack_depth_;
- }
-
- ArtMethod* GetMethod() const {
- return method_;
- }
-
- const std::set<uint32_t>& GetDexPcs() const {
- return dex_pcs_;
- }
-
- void AddDexPc(uint32_t dex_pc);
-
- bool ContainsDexPc(uint32_t dex_pc) const;
-
- private:
- // See JdwpStepSize and JdwpStepDepth for details.
- const JDWP::JdwpStepSize step_size_;
- const JDWP::JdwpStepDepth step_depth_;
-
- // The stack depth when this single-step was initiated. This is used to support SD_OVER and SD_OUT
- // single-step depth.
- const int stack_depth_;
-
- // The location this single-step was initiated from.
- // A single-step is initiated in a suspended thread. We save here the current method and the
- // set of DEX pcs associated to the source line number where the suspension occurred.
- // This is used to support SD_INTO and SD_OVER single-step depths so we detect when a single-step
- // causes the execution of an instruction in a different method or at a different line number.
- ArtMethod* method_;
-
- std::set<uint32_t> dex_pcs_;
-
- DISALLOW_COPY_AND_ASSIGN(SingleStepControl);
-};
-
-// TODO rename to InstrumentationRequest.
-class DeoptimizationRequest {
- public:
- enum Kind {
- kNothing, // no action.
- kRegisterForEvent, // start listening for instrumentation event.
- kUnregisterForEvent, // stop listening for instrumentation event.
- kFullDeoptimization, // deoptimize everything.
- kFullUndeoptimization, // undeoptimize everything.
- kSelectiveDeoptimization, // deoptimize one method.
- kSelectiveUndeoptimization // undeoptimize one method.
- };
-
- DeoptimizationRequest() : kind_(kNothing), instrumentation_event_(0), method_(nullptr) {}
-
- DeoptimizationRequest(const DeoptimizationRequest& other)
- REQUIRES_SHARED(Locks::mutator_lock_)
- : kind_(other.kind_), instrumentation_event_(other.instrumentation_event_) {
- // Create a new JNI global reference for the method.
- SetMethod(other.Method());
- }
-
- ArtMethod* Method() const REQUIRES_SHARED(Locks::mutator_lock_);
-
- void SetMethod(ArtMethod* m) REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Name 'Kind()' would collide with the above enum name.
- Kind GetKind() const {
- return kind_;
- }
-
- void SetKind(Kind kind) {
- kind_ = kind;
- }
-
- uint32_t InstrumentationEvent() const {
- return instrumentation_event_;
- }
-
- void SetInstrumentationEvent(uint32_t instrumentation_event) {
- instrumentation_event_ = instrumentation_event;
- }
-
- private:
- Kind kind_;
-
- // TODO we could use a union to hold the instrumentation_event and the method since they
- // respectively have sense only for kRegisterForEvent/kUnregisterForEvent and
- // kSelectiveDeoptimization/kSelectiveUndeoptimization.
-
- // Event to start or stop listening to. Only for kRegisterForEvent and kUnregisterForEvent.
- uint32_t instrumentation_event_;
-
- // Method for selective deoptimization.
- jmethodID method_;
-};
-std::ostream& operator<<(std::ostream& os, const DeoptimizationRequest::Kind& rhs);
class Dbg {
public:
static void SetJdwpAllowed(bool allowed);
static bool IsJdwpAllowed();
- static void StartJdwp();
- static void StopJdwp();
-
// Invoked by the GC in case we need to keep DDMS informed.
static void GcDidFinish() REQUIRES(!Locks::mutator_lock_);
- // Return the DebugInvokeReq for the current thread.
- static DebugInvokeReq* GetInvokeReq();
-
- static Thread* GetDebugThread();
- static void ClearWaitForEventThread();
-
- /*
- * Enable/disable breakpoints and step modes. Used to provide a heads-up
- * when the debugger attaches.
- */
- static void Connected();
- static void GoActive()
- REQUIRES(!Locks::breakpoint_lock_, !Locks::deoptimization_lock_, !Locks::mutator_lock_);
- static void Disconnected() REQUIRES(!Locks::deoptimization_lock_, !Locks::mutator_lock_);
- static void Dispose() {
- gDisposed = true;
- }
-
- // Returns true if we're actually debugging with a real debugger, false if it's
- // just DDMS (or nothing at all).
- static bool IsDebuggerActive() {
- return gDebuggerActive;
- }
-
- // Configures JDWP with parsed command-line options.
- static void ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Returns true if we had -Xrunjdwp or -agentlib:jdwp= on the command line.
- static bool IsJdwpConfigured();
-
- // Returns true if a method has any breakpoints.
- static bool MethodHasAnyBreakpoints(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::breakpoint_lock_);
-
- static bool IsDisposed() {
- return gDisposed;
- }
-
- /*
- * Time, in milliseconds, since the last debugger activity. Does not
- * include DDMS activity. Returns -1 if there has been no activity.
- * Returns 0 if we're in the middle of handling a debugger request.
- */
- static int64_t LastDebuggerActivity();
-
- static void UndoDebuggerSuspensions()
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
-
- /*
- * Class, Object, Array
- */
- static std::string GetClassName(JDWP::RefTypeId id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static std::string GetClassName(ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetClassObject(JDWP::RefTypeId id, JDWP::ObjectId* class_object_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetSuperclass(JDWP::RefTypeId id, JDWP::RefTypeId* superclass_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetClassLoader(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetModifiers(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetReflectedType(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void GetClassList(std::vector<JDWP::RefTypeId>* classes)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetClassInfo(JDWP::RefTypeId class_id, JDWP::JdwpTypeTag* pTypeTag,
- uint32_t* pStatus, std::string* pDescriptor)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void FindLoadedClassBySignature(const char* descriptor, std::vector<JDWP::RefTypeId>* ids)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetSignature(JDWP::RefTypeId ref_type_id, std::string* signature)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetSourceDebugExtension(JDWP::RefTypeId ref_type_id,
- std::string* extension_data)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetSourceFile(JDWP::RefTypeId ref_type_id, std::string* source_file)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetObjectTag(JDWP::ObjectId object_id, uint8_t* tag)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static size_t GetTagWidth(JDWP::JdwpTag tag);
-
- static JDWP::JdwpError GetArrayLength(JDWP::ObjectId array_id, int32_t* length)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError OutputArray(JDWP::ObjectId array_id,
- int offset,
- int count,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError SetArrayElements(JDWP::ObjectId array_id, int offset, int count,
- JDWP::Request* request)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpError CreateString(const std::string& str, JDWP::ObjectId* new_string_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError CreateObject(JDWP::RefTypeId class_id, JDWP::ObjectId* new_object_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError CreateArrayObject(JDWP::RefTypeId array_class_id, uint32_t length,
- JDWP::ObjectId* new_array_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- //
- // Event filtering.
- //
- static bool MatchThread(JDWP::ObjectId expected_thread_id, Thread* event_thread)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool MatchLocation(const JDWP::JdwpLocation& expected_location,
- const JDWP::EventLocation& event_location)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool MatchType(ObjPtr<mirror::Class> event_class, JDWP::RefTypeId class_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool MatchField(JDWP::RefTypeId expected_type_id,
- JDWP::FieldId expected_field_id,
- ArtField* event_field)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool MatchInstance(JDWP::ObjectId expected_instance_id,
- ObjPtr<mirror::Object> event_instance)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- //
- // Monitors.
- //
- static JDWP::JdwpError GetMonitorInfo(JDWP::ObjectId object_id, JDWP::ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetOwnedMonitors(JDWP::ObjectId thread_id,
- std::vector<JDWP::ObjectId>* monitors,
- std::vector<uint32_t>* stack_depths)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetContendedMonitor(JDWP::ObjectId thread_id,
- JDWP::ObjectId* contended_monitor)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- //
- // Heap.
- //
- static JDWP::JdwpError GetInstanceCounts(const std::vector<JDWP::RefTypeId>& class_ids,
- std::vector<uint64_t>* counts)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetInstances(JDWP::RefTypeId class_id, int32_t max_count,
- std::vector<JDWP::ObjectId>* instances)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetReferringObjects(JDWP::ObjectId object_id, int32_t max_count,
- std::vector<JDWP::ObjectId>* referring_objects)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError DisableCollection(JDWP::ObjectId object_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError EnableCollection(JDWP::ObjectId object_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError IsCollected(JDWP::ObjectId object_id, bool* is_collected)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void DisposeObject(JDWP::ObjectId object_id, uint32_t reference_count)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- //
- // Methods and fields.
- //
- static std::string GetMethodName(JDWP::MethodId method_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static bool IsMethodObsolete(JDWP::MethodId method_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError OutputDeclaredFields(JDWP::RefTypeId ref_type_id, bool with_generic,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError OutputDeclaredMethods(JDWP::RefTypeId ref_type_id, bool with_generic,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError OutputDeclaredInterfaces(JDWP::RefTypeId ref_type_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void OutputLineTable(JDWP::RefTypeId ref_type_id, JDWP::MethodId method_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void OutputVariableTable(JDWP::RefTypeId ref_type_id, JDWP::MethodId id, bool with_generic,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void OutputMethodReturnValue(JDWP::MethodId method_id, const JValue* return_value,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void OutputFieldValue(JDWP::FieldId field_id, const JValue* field_value,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetBytecodes(JDWP::RefTypeId class_id, JDWP::MethodId method_id,
- std::vector<uint8_t>* bytecodes)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static std::string GetFieldName(JDWP::FieldId field_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpTag GetFieldBasicTag(JDWP::FieldId field_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpTag GetStaticFieldBasicTag(JDWP::FieldId field_id)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError SetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id,
- uint64_t value, int width)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetStaticFieldValue(JDWP::RefTypeId ref_type_id, JDWP::FieldId field_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError SetStaticFieldValue(JDWP::FieldId field_id, uint64_t value, int width)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpError StringToUtf8(JDWP::ObjectId string_id, std::string* str)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void OutputJValue(JDWP::JdwpTag tag, const JValue* return_value, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Thread, ThreadGroup, Frame
- */
- static JDWP::JdwpError GetThreadName(JDWP::ObjectId thread_id, std::string* name)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::thread_list_lock_);
- static JDWP::JdwpError GetThreadGroup(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::thread_list_lock_);
- static JDWP::JdwpError GetThreadGroupName(JDWP::ObjectId thread_group_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetThreadGroupParent(JDWP::ObjectId thread_group_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetThreadGroupChildren(JDWP::ObjectId thread_group_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::ObjectId GetSystemThreadGroupId()
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpThreadStatus ToJdwpThreadStatus(ThreadState state);
- static JDWP::JdwpError GetThreadStatus(JDWP::ObjectId thread_id,
- JDWP::JdwpThreadStatus* pThreadStatus,
- JDWP::JdwpSuspendStatus* pSuspendStatus)
- REQUIRES(!Locks::thread_list_lock_);
- static JDWP::JdwpError GetThreadDebugSuspendCount(JDWP::ObjectId thread_id,
- JDWP::ExpandBuf* pReply)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
- // static void WaitForSuspend(JDWP::ObjectId thread_id);
-
- // Fills 'thread_ids' with the threads in the given thread group. If thread_group_id == 0,
- // returns all threads.
- static void GetThreads(ObjPtr<mirror::Object> thread_group,
- std::vector<JDWP::ObjectId>* thread_ids)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpError GetThreadFrameCount(JDWP::ObjectId thread_id, size_t* result)
- REQUIRES(!Locks::thread_list_lock_);
- static JDWP::JdwpError GetThreadFrames(JDWP::ObjectId thread_id, size_t start_frame,
- size_t frame_count, JDWP::ExpandBuf* buf)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::ObjectId GetThreadSelfId() REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::ObjectId GetThreadId(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void SuspendVM()
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
- static void ResumeVM()
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
- static JDWP::JdwpError SuspendThread(JDWP::ObjectId thread_id, bool request_suspension = true)
- REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_,
- !Locks::thread_suspend_count_lock_);
-
- static void ResumeThread(JDWP::ObjectId thread_id)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void SuspendSelf();
-
- static JDWP::JdwpError GetThisObject(JDWP::ObjectId thread_id, JDWP::FrameId frame_id,
- JDWP::ObjectId* result)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError GetLocalValues(JDWP::Request* request, JDWP::ExpandBuf* pReply)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError SetLocalValues(JDWP::Request* request)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpError Interrupt(JDWP::ObjectId thread_id)
- REQUIRES(!Locks::thread_list_lock_);
-
- /*
- * Debugger notification
- */
- enum EventFlag {
- kBreakpoint = 0x01,
- kSingleStep = 0x02,
- kMethodEntry = 0x04,
- kMethodExit = 0x08,
- };
- static void PostFieldAccessEvent(ArtMethod* m,
- int dex_pc,
- ObjPtr<mirror::Object> this_object,
- ArtField* f)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void PostFieldModificationEvent(ArtMethod* m,
- int dex_pc,
- ObjPtr<mirror::Object> this_object,
- ArtField* f,
- const JValue* field_value)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void PostException(ObjPtr<mirror::Throwable> exception)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void UpdateDebugger(Thread* thread,
- ObjPtr<mirror::Object> this_object,
- ArtMethod* method,
- uint32_t new_dex_pc,
- int event_flags,
- const JValue* return_value)
- REQUIRES(!Locks::breakpoint_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Indicates whether we need deoptimization for debugging.
- static bool RequiresDeoptimization();
-
- // Records deoptimization request in the queue.
- static void RequestDeoptimization(const DeoptimizationRequest& req)
- REQUIRES(!Locks::deoptimization_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Manage deoptimization after updating JDWP events list. Suspends all threads, processes each
- // request and finally resumes all threads.
- static void ManageDeoptimization()
- REQUIRES(!Locks::deoptimization_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Breakpoints.
- static void WatchLocation(const JDWP::JdwpLocation* pLoc, DeoptimizationRequest* req)
- REQUIRES(!Locks::breakpoint_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- static void UnwatchLocation(const JDWP::JdwpLocation* pLoc, DeoptimizationRequest* req)
- REQUIRES(!Locks::breakpoint_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Forced interpreter checkers for single-step and continue support.
- */
-
- // Indicates whether we need to force the use of interpreter to invoke a method.
- // This allows to single-step or continue into the called method.
- static bool IsForcedInterpreterNeededForCalling(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsDebuggerActive()) {
- return false;
- }
- return IsForcedInterpreterNeededForCallingImpl(thread, m);
- }
-
- // Indicates whether we need to force the use of interpreter entrypoint when calling a
- // method through the resolution trampoline. This allows to single-step or continue into
- // the called method.
- static bool IsForcedInterpreterNeededForResolution(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsDebuggerActive()) {
- return false;
- }
- return IsForcedInterpreterNeededForResolutionImpl(thread, m);
- }
-
- // Indicates whether we need to force the use of instrumentation entrypoint when calling
- // a method through the resolution trampoline. This allows to deoptimize the stack for
- // debugging when we returned from the called method.
- static bool IsForcedInstrumentationNeededForResolution(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsDebuggerActive()) {
- return false;
- }
- return IsForcedInstrumentationNeededForResolutionImpl(thread, m);
- }
+ static uint8_t ToJdwpThreadStatus(ThreadState state);
// Indicates whether we need to force the use of interpreter when returning from the
// interpreter into the runtime. This allows to deoptimize the stack and continue
// execution with interpreter for debugging.
static bool IsForcedInterpreterNeededForUpcall(Thread* thread, ArtMethod* m)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
+ if (LIKELY(!thread->HasDebuggerShadowFrames())) {
return false;
}
- return IsForcedInterpreterNeededForUpcallImpl(thread, m);
+ // If we have debugger stack frames we always need to go back to interpreter unless we are
+ // native or a proxy.
+ return m != nullptr && !m->IsProxyMethod() && !m->IsNative();
}
// Indicates whether we need to force the use of interpreter when handling an
@@ -629,50 +64,12 @@ class Dbg {
// the deoptimized frames.
static bool IsForcedInterpreterNeededForException(Thread* thread)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (!IsDebuggerActive() && !thread->HasDebuggerShadowFrames()) {
+ if (LIKELY(!thread->HasDebuggerShadowFrames())) {
return false;
}
return IsForcedInterpreterNeededForExceptionImpl(thread);
}
- // Single-stepping.
- static JDWP::JdwpError ConfigureStep(JDWP::ObjectId thread_id, JDWP::JdwpStepSize size,
- JDWP::JdwpStepDepth depth)
- REQUIRES_SHARED(Locks::mutator_lock_);
- static void UnconfigureStep(JDWP::ObjectId thread_id)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Invoke support
- */
-
- // Called by the JDWP thread to prepare invocation in the event thread (suspended on an event).
- // If the information sent by the debugger is incorrect, it will send a reply with the
- // appropriate error code. Otherwise, it will attach a DebugInvokeReq object to the event thread
- // and resume it (and possibly other threads depending on the invoke options).
- // Unlike other commands, the JDWP thread will not send the reply to the debugger (see
- // JdwpState::ProcessRequest). The reply will be sent by the event thread itself after method
- // invocation completes (see FinishInvokeMethod). This is required to allow the JDWP thread to
- // process incoming commands from the debugger while the invocation is still in progress in the
- // event thread, especially if it gets suspended by a debug event occurring in another thread.
- static JDWP::JdwpError PrepareInvokeMethod(uint32_t request_id, JDWP::ObjectId thread_id,
- JDWP::ObjectId object_id, JDWP::RefTypeId class_id,
- JDWP::MethodId method_id, uint32_t arg_count,
- uint64_t arg_values[], JDWP::JdwpTag* arg_types,
- uint32_t options)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- // Called by the event thread to execute a method prepared by the JDWP thread in the given
- // DebugInvokeReq object. Once the invocation completes, the event thread attaches a reply
- // to that DebugInvokeReq object so it can be sent to the debugger only when the event thread
- // is ready to suspend (see FinishInvokeMethod).
- static void ExecuteMethod(DebugInvokeReq* pReq);
-
- // Called by the event thread to send the reply of the invoke (created in ExecuteMethod)
- // before suspending itself. This is to ensure the thread is ready to suspend before the
- // debugger receives the reply.
- static void FinishInvokeMethod(DebugInvokeReq* pReq);
/*
* DDM support.
@@ -687,14 +84,10 @@ class Dbg {
const ArrayRef<const jbyte>& data,
/*out*/uint32_t* out_type,
/*out*/std::vector<uint8_t>* out_data);
- static bool DdmHandlePacket(JDWP::Request* request, uint8_t** pReplyBuf, int* pReplyLen);
+
static void DdmConnected() REQUIRES_SHARED(Locks::mutator_lock_);
static void DdmDisconnected() REQUIRES_SHARED(Locks::mutator_lock_);
- // Visit breakpoint roots, used to prevent unloading of methods with breakpoints.
- static void VisitRoots(RootVisitor* visitor)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
/*
* Allocation tracking support.
*/
@@ -727,54 +120,11 @@ class Dbg {
static void DdmSendHeapSegments(bool native)
REQUIRES_SHARED(Locks::mutator_lock_);
- static ObjectRegistry* GetObjectRegistry() {
- return gRegistry;
- }
-
- static JDWP::JdwpTag TagFromObject(const ScopedObjectAccessUnchecked& soa,
- ObjPtr<mirror::Object> o)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpTypeTag GetTypeTag(ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::FieldId ToFieldId(const ArtField* f)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void SetJdwpLocation(JDWP::JdwpLocation* location, ArtMethod* m, uint32_t dex_pc)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
-
- static JDWP::JdwpState* GetJdwpState();
-
- static uint32_t GetInstrumentationEvents() REQUIRES_SHARED(Locks::mutator_lock_) {
- return instrumentation_events_;
- }
-
static ThreadLifecycleCallback* GetThreadLifecycleCallback() {
return &thread_lifecycle_callback_;
}
- static ClassLoadCallback* GetClassLoadCallback() {
- return &class_load_callback_;
- }
private:
- static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void BuildInvokeReply(JDWP::ExpandBuf* pReply, uint32_t request_id,
- JDWP::JdwpTag result_tag, uint64_t result_value,
- JDWP::ObjectId exception)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static JDWP::JdwpError GetLocalValue(const StackVisitor& visitor,
- ScopedObjectAccessUnchecked& soa, int slot,
- JDWP::JdwpTag tag, uint8_t* buf, size_t width)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- static JDWP::JdwpError SetLocalValue(Thread* thread, StackVisitor& visitor, int slot,
- JDWP::JdwpTag tag, uint64_t value, size_t width)
- REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
static void DdmBroadcast(bool connect) REQUIRES_SHARED(Locks::mutator_lock_);
static void PostThreadStart(Thread* t)
@@ -784,91 +134,16 @@ class Dbg {
static void PostThreadStartOrStop(Thread*, uint32_t)
REQUIRES_SHARED(Locks::mutator_lock_);
- static void PostClassPrepare(ObjPtr<mirror::Class> c)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void PostLocationEvent(ArtMethod* method,
- int pcOffset,
- ObjPtr<mirror::Object> thisPtr,
- int eventFlags,
- const JValue* return_value)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static void ProcessDeoptimizationRequest(const DeoptimizationRequest& request)
- REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
-
- static void RequestDeoptimizationLocked(const DeoptimizationRequest& req)
- REQUIRES(Locks::deoptimization_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool IsForcedInterpreterNeededForCallingImpl(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool IsForcedInterpreterNeededForResolutionImpl(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool IsForcedInstrumentationNeededForResolutionImpl(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- static bool IsForcedInterpreterNeededForUpcallImpl(Thread* thread, ArtMethod* m)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
static bool IsForcedInterpreterNeededForExceptionImpl(Thread* thread)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Indicates whether the debugger is making requests.
- static bool gDebuggerActive;
-
- static DebuggerActiveMethodInspectionCallback gDebugActiveCallback;
- static DebuggerDdmCallback gDebugDdmCallback;
- static InternalDebuggerControlCallback gDebuggerControlCallback;
-
- // Indicates whether we should drop the JDWP connection because the runtime stops or the
- // debugger called VirtualMachine.Dispose.
- static bool gDisposed;
-
- // The registry mapping objects to JDWP ids.
- static ObjectRegistry* gRegistry;
-
- // Deoptimization requests to be processed each time the event list is updated. This is used when
- // registering and unregistering events so we do not deoptimize while holding the event list
- // lock.
- // TODO rename to instrumentation_requests.
- static std::vector<DeoptimizationRequest> deoptimization_requests_ GUARDED_BY(Locks::deoptimization_lock_);
-
- // Count the number of events requiring full deoptimization. When the counter is > 0, everything
- // is deoptimized, otherwise everything is undeoptimized.
- // Note: we fully deoptimize on the first event only (when the counter is set to 1). We fully
- // undeoptimize when the last event is unregistered (when the counter is set to 0).
- static size_t full_deoptimization_event_count_ GUARDED_BY(Locks::deoptimization_lock_);
-
- static size_t* GetReferenceCounterForEvent(uint32_t instrumentation_event);
-
- // Instrumentation event reference counters.
- // TODO we could use an array instead of having all these dedicated counters. Instrumentation
- // events are bits of a mask so we could convert them to array index.
- static size_t dex_pc_change_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static size_t method_enter_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static size_t method_exit_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static size_t field_read_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static size_t field_write_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
- static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_);
-
class DbgThreadLifecycleCallback : public ThreadLifecycleCallback {
public:
void ThreadStart(Thread* self) override REQUIRES_SHARED(Locks::mutator_lock_);
void ThreadDeath(Thread* self) override REQUIRES_SHARED(Locks::mutator_lock_);
};
- class DbgClassLoadCallback : public ClassLoadCallback {
- public:
- void ClassLoad(Handle<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_);
- void ClassPrepare(Handle<mirror::Class> temp_klass,
- Handle<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_);
- };
-
static DbgThreadLifecycleCallback thread_lifecycle_callback_;
- static DbgClassLoadCallback class_load_callback_;
DISALLOW_COPY_AND_ASSIGN(Dbg);
};
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index e7eff0d25e0..1049c5d89c1 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -1466,22 +1466,11 @@ extern "C" const void* artQuickResolutionTrampoline(
}
bool force_interpreter = self->IsForceInterpreter() && !called->IsNative();
if (called_class->IsInitialized() || called_class->IsInitializing()) {
- if (UNLIKELY(force_interpreter ||
- Dbg::IsForcedInterpreterNeededForResolution(self, called))) {
+ if (UNLIKELY(force_interpreter)) {
// If we are single-stepping or the called method is deoptimized (by a
// breakpoint, for example), then we have to execute the called method
// with the interpreter.
code = GetQuickToInterpreterBridge();
- } else if (UNLIKELY(Dbg::IsForcedInstrumentationNeededForResolution(self, caller))) {
- // If the caller is deoptimized (by a breakpoint, for example), we have to
- // continue its execution with interpreter when returning from the called
- // method. Because we do not want to execute the called method with the
- // interpreter, we wrap its execution into the instrumentation stubs.
- // When the called method returns, it will execute the instrumentation
- // exit hook that will determine the need of the interpreter with a call
- // to Dbg::IsForcedInterpreterNeededForUpcall and deoptimize the stack if
- // it is needed.
- code = GetQuickInstrumentationEntryPoint();
} else {
code = called->GetEntryPointFromQuickCompiledCode();
if (linker->IsQuickResolutionStub(code)) {
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 36f5b398e8c..d88584d727b 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -100,9 +100,7 @@ class EntrypointsOrderTest : public CommonRuntimeTest {
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, top_handle_scope, class_loader_override, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, class_loader_override, long_jump_context, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, long_jump_context, instrumentation_stack, sizeof(void*));
- EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, instrumentation_stack, debug_invoke_req, sizeof(void*));
- EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, debug_invoke_req, single_step_control, sizeof(void*));
- EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, single_step_control, stacked_shadow_frame_record,
+ EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, instrumentation_stack, stacked_shadow_frame_record,
sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, stacked_shadow_frame_record,
deoptimization_context_stack, sizeof(void*));
diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc
index 4900a9acba4..60fb71d9524 100644
--- a/runtime/gc/allocation_record.cc
+++ b/runtime/gc/allocation_record.cc
@@ -153,11 +153,6 @@ void AllocRecordObjectMap::SetAllocTrackingEnabled(bool enable) {
}
CHECK(records != nullptr);
records->SetMaxStackDepth(heap->GetAllocTrackerStackDepth());
- std::string self_name;
- self->GetThreadName(self_name);
- if (self_name == "JDWP") {
- records->alloc_ddm_thread_id_ = self->GetTid();
- }
size_t sz = sizeof(AllocRecordStackTraceElement) * records->max_stack_depth_ +
sizeof(AllocRecord) + sizeof(AllocRecordStackTrace);
LOG(INFO) << "Enabling alloc tracker (" << records->alloc_record_max_ << " entries of "
@@ -222,10 +217,9 @@ void AllocRecordObjectMap::RecordAllocation(Thread* self,
return;
}
- // Do not record for DDM thread.
- if (alloc_ddm_thread_id_ == self->GetTid()) {
- return;
- }
+ // TODO Skip recording allocations associated with DDMS. This was a feature of the old debugger
+ // but when we switched to the JVMTI based debugger the feature was (unintentionally) broken.
+ // Since nobody seemed to really notice or care it might not be worth the trouble.
// Wait for GC's sweeping to complete and allow new records.
while (UNLIKELY((!kUseReadBarrier && !allow_new_record_) ||
diff --git a/runtime/gc/allocation_record.h b/runtime/gc/allocation_record.h
index 7c4181ce9fd..405d060821f 100644
--- a/runtime/gc/allocation_record.h
+++ b/runtime/gc/allocation_record.h
@@ -299,7 +299,6 @@ class AllocRecordObjectMap {
size_t alloc_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_) = kDefaultNumAllocRecords;
size_t recent_record_max_ GUARDED_BY(Locks::alloc_tracker_lock_) = kDefaultNumRecentRecords;
size_t max_stack_depth_ = kDefaultAllocStackDepth;
- pid_t alloc_ddm_thread_id_ GUARDED_BY(Locks::alloc_tracker_lock_) = 0;
bool allow_new_record_ GUARDED_BY(Locks::alloc_tracker_lock_) = true;
ConditionVariable new_record_condition_ GUARDED_BY(Locks::alloc_tracker_lock_);
// see the comment in typedef of EntryList
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index 8440c4175a1..516c435d32f 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -42,6 +42,7 @@
#include "art_method-inl.h"
#include "base/array_ref.h"
#include "base/file_utils.h"
+#include "base/logging.h"
#include "base/macros.h"
#include "base/mutex.h"
#include "base/os.h"
@@ -60,8 +61,6 @@
#include "gc/scoped_gc_critical_section.h"
#include "gc/space/space.h"
#include "gc_root.h"
-#include "jdwp/jdwp.h"
-#include "jdwp/jdwp_priv.h"
#include "mirror/class-inl.h"
#include "mirror/class.h"
#include "mirror/object-refvisitor-inl.h"
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index f38b69f15d6..c6d32587669 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -149,7 +149,6 @@ bool CanUseMterp()
return
runtime->IsStarted() &&
!runtime->IsAotCompiler() &&
- !Dbg::IsDebuggerActive() &&
!runtime->GetInstrumentation()->IsActive() &&
// mterp only knows how to deal with the normal exits. It cannot handle any of the
// non-standard force-returns.
diff --git a/runtime/jdwp/README.txt b/runtime/jdwp/README.txt
deleted file mode 100644
index da25fb17dd8..00000000000
--- a/runtime/jdwp/README.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Java Debug Wire Protocol support
-
-This is a reasonably complete implementation, but only messages that are
-actually generated by debuggers have been implemented. The reasoning
-behind this is that it's better to leave a call unimplemented than have
-something that appears implemented but has never been tested.
-
-An attempt has been made to keep the JDWP implementation distinct from the
-runtime, so that the code might be useful in other projects. Once you get
-multiple simultaneous events and debugger requests with thread suspension
-bouncing around, though, it's difficult to keep things "generic".
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
deleted file mode 100644
index 5796a61920d..00000000000
--- a/runtime/jdwp/jdwp.h
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ART_RUNTIME_JDWP_JDWP_H_
-#define ART_RUNTIME_JDWP_JDWP_H_
-
-#include "base/atomic.h"
-#include "base/logging.h" // For VLOG.
-#include "base/mutex.h"
-#include "jdwp/jdwp_bits.h"
-#include "jdwp/jdwp_constants.h"
-#include "jdwp/jdwp_expand_buf.h"
-#include "obj_ptr.h"
-
-#include <pthread.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <string.h>
-#include <vector>
-
-struct iovec;
-
-namespace art {
-
-class ArtField;
-class ArtMethod;
-union JValue;
-class Thread;
-
-namespace mirror {
-class Class;
-class Object;
-class Throwable;
-} // namespace mirror
-class Thread;
-
-namespace JDWP {
-
-/*
- * Fundamental types.
- *
- * ObjectId and RefTypeId must be the same size.
- * Its OK to change MethodId and FieldId sizes as long as the size is <= 8 bytes.
- * Note that ArtFields are 64 bit pointers on 64 bit targets. So this one must remain 8 bytes.
- */
-typedef uint64_t FieldId; /* static or instance field */
-typedef uint64_t MethodId; /* any kind of method, including constructors */
-typedef uint64_t ObjectId; /* any object (threadID, stringID, arrayID, etc) */
-typedef uint64_t RefTypeId; /* like ObjectID, but unique for Class objects */
-typedef uint64_t FrameId; /* short-lived stack frame ID */
-
-ObjectId ReadObjectId(const uint8_t** pBuf);
-
-static inline void SetFieldId(uint8_t* buf, FieldId val) { return Set8BE(buf, val); }
-static inline void SetMethodId(uint8_t* buf, MethodId val) { return Set8BE(buf, val); }
-static inline void SetObjectId(uint8_t* buf, ObjectId val) { return Set8BE(buf, val); }
-static inline void SetRefTypeId(uint8_t* buf, RefTypeId val) { return Set8BE(buf, val); }
-static inline void SetFrameId(uint8_t* buf, FrameId val) { return Set8BE(buf, val); }
-static inline void expandBufAddFieldId(ExpandBuf* pReply, FieldId id) { expandBufAdd8BE(pReply, id); }
-static inline void expandBufAddMethodId(ExpandBuf* pReply, MethodId id) { expandBufAdd8BE(pReply, id); }
-static inline void expandBufAddObjectId(ExpandBuf* pReply, ObjectId id) { expandBufAdd8BE(pReply, id); }
-static inline void expandBufAddRefTypeId(ExpandBuf* pReply, RefTypeId id) { expandBufAdd8BE(pReply, id); }
-static inline void expandBufAddFrameId(ExpandBuf* pReply, FrameId id) { expandBufAdd8BE(pReply, id); }
-
-struct EventLocation {
- ArtMethod* method;
- uint32_t dex_pc;
-};
-
-/*
- * Holds a JDWP "location".
- */
-struct JdwpLocation {
- JdwpTypeTag type_tag;
- RefTypeId class_id;
- MethodId method_id;
- uint64_t dex_pc;
-};
-std::ostream& operator<<(std::ostream& os, const JdwpLocation& rhs)
- REQUIRES_SHARED(Locks::mutator_lock_);
-bool operator==(const JdwpLocation& lhs, const JdwpLocation& rhs);
-bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs);
-
-/*
- * How we talk to the debugger.
- */
-enum JdwpTransportType {
- kJdwpTransportNone = 0,
- kJdwpTransportUnknown, // Unknown tranpsort
- kJdwpTransportSocket, // transport=dt_socket
- kJdwpTransportAndroidAdb, // transport=dt_android_adb
-};
-std::ostream& operator<<(std::ostream& os, const JdwpTransportType& rhs);
-
-struct JdwpOptions {
- JdwpTransportType transport = kJdwpTransportNone;
- bool server = false;
- bool suspend = false;
- std::string host = "";
- uint16_t port = static_cast<uint16_t>(-1);
-};
-
-bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs);
-
-bool ParseJdwpOptions(const std::string& options, JdwpOptions* jdwp_options);
-
-struct JdwpEvent;
-class JdwpNetStateBase;
-struct ModBasket;
-class Request;
-
-/*
- * State for JDWP functions.
- */
-struct JdwpState {
- /*
- * Perform one-time initialization.
- *
- * Among other things, this binds to a port to listen for a connection from
- * the debugger.
- *
- * Returns a newly-allocated JdwpState struct on success, or nullptr on failure.
- *
- * NO_THREAD_SAFETY_ANALYSIS since we can't annotate that we do not have
- * state->thread_start_lock_ held.
- */
- static JdwpState* Create(const JdwpOptions* options)
- REQUIRES(!Locks::mutator_lock_) NO_THREAD_SAFETY_ANALYSIS;
-
- ~JdwpState();
-
- /*
- * Returns "true" if a debugger or DDM is connected.
- */
- bool IsActive();
-
- /**
- * Returns the Thread* for the JDWP daemon thread.
- */
- Thread* GetDebugThread();
-
- /*
- * Get time, in milliseconds, since the last debugger activity.
- */
- int64_t LastDebuggerActivity();
-
- void ExitAfterReplying(int exit_status);
-
- // Acquires/releases the JDWP synchronization token for the debugger
- // thread (command handler) so no event thread posts an event while
- // it processes a command. This must be called only from the debugger
- // thread.
- void AcquireJdwpTokenForCommand() REQUIRES(!jdwp_token_lock_);
- void ReleaseJdwpTokenForCommand() REQUIRES(!jdwp_token_lock_);
-
- // Acquires/releases the JDWP synchronization token for the event thread
- // so no other thread (debugger thread or event thread) interleaves with
- // it when posting an event. This must NOT be called from the debugger
- // thread, only event thread.
- void AcquireJdwpTokenForEvent(ObjectId threadId) REQUIRES(!jdwp_token_lock_);
- void ReleaseJdwpTokenForEvent() REQUIRES(!jdwp_token_lock_);
-
- /*
- * These notify the debug code that something interesting has happened. This
- * could be a thread starting or ending, an exception, or an opportunity
- * for a breakpoint. These calls do not mean that an event the debugger
- * is interested has happened, just that something has happened that the
- * debugger *might* be interested in.
- *
- * The item of interest may trigger multiple events, some or all of which
- * are grouped together in a single response.
- *
- * The event may cause the current thread or all threads (except the
- * JDWP support thread) to be suspended.
- */
-
- /*
- * The VM has finished initializing. Only called when the debugger is
- * connected at the time initialization completes.
- */
- void PostVMStart() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!jdwp_token_lock_);
-
- /*
- * A location of interest has been reached. This is used for breakpoints,
- * single-stepping, and method entry/exit. (JDWP requires that these four
- * events are grouped together in a single response.)
- *
- * In some cases "*pLoc" will just have a method and class name, e.g. when
- * issuing a MethodEntry on a native method.
- *
- * "eventFlags" indicates the types of events that have occurred.
- *
- * "returnValue" is non-null for MethodExit events only.
- */
- void PostLocationEvent(const EventLocation* pLoc,
- ObjPtr<mirror::Object> thisPtr,
- int eventFlags,
- const JValue* returnValue)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * A field of interest has been accessed or modified. This is used for field access and field
- * modification events.
- *
- * "fieldValue" is non-null for field modification events only.
- * "is_modification" is true for field modification, false for field access.
- */
- void PostFieldEvent(const EventLocation* pLoc,
- ArtField* field,
- ObjPtr<mirror::Object> thisPtr,
- const JValue* fieldValue,
- bool is_modification)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * An exception has been thrown.
- *
- * Pass in a zeroed-out "*pCatchLoc" if the exception wasn't caught.
- */
- void PostException(const EventLocation* pThrowLoc,
- ObjPtr<mirror::Throwable> exception_object,
- const EventLocation* pCatchLoc,
- ObjPtr<mirror::Object> thisPtr)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * A thread has started or stopped.
- */
- void PostThreadChange(Thread* thread, bool start)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Class has been prepared.
- */
- void PostClassPrepare(ObjPtr<mirror::Class> klass)
- REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * The VM is about to stop.
- */
- bool PostVMDeath();
-
- // Called if/when we realize we're talking to DDMS.
- void NotifyDdmsActive() REQUIRES_SHARED(Locks::mutator_lock_);
-
-
- void SetupChunkHeader(uint32_t type, size_t data_len, size_t header_size, uint8_t* out_header);
-
- /*
- * Send up a chunk of DDM data.
- */
- void DdmSendChunkV(uint32_t type, const iovec* iov, int iov_count)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- bool HandlePacket() REQUIRES(!shutdown_lock_, !jdwp_token_lock_);
-
- void SendRequest(ExpandBuf* pReq);
-
- void ResetState()
- REQUIRES(!event_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- /* atomic ops to get next serial number */
- uint32_t NextRequestSerial();
- uint32_t NextEventSerial();
-
- void Run()
- REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_, !thread_start_lock_,
- !attach_lock_, !event_list_lock_);
-
- /*
- * Register an event by adding it to the event list.
- *
- * "*pEvent" must be storage allocated with jdwpEventAlloc(). The caller
- * may discard its pointer after calling this.
- */
- JdwpError RegisterEvent(JdwpEvent* pEvent)
- REQUIRES(!event_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Unregister an event, given the requestId.
- */
- void UnregisterEventById(uint32_t requestId)
- REQUIRES(!event_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- void UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass)
- REQUIRES(!event_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
- * Unregister all events.
- */
- void UnregisterAll()
- REQUIRES(!event_list_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- private:
- explicit JdwpState(const JdwpOptions* options);
- size_t ProcessRequest(Request* request, ExpandBuf* pReply, bool* skip_reply)
- REQUIRES(!jdwp_token_lock_);
- bool InvokeInProgress();
- bool IsConnected();
- void SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id)
- REQUIRES(!Locks::mutator_lock_);
- void SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy suspend_policy,
- ObjectId threadId)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!jdwp_token_lock_);
- void CleanupMatchList(const std::vector<JdwpEvent*>& match_list)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- void EventFinish(ExpandBuf* pReq);
- bool FindMatchingEvents(JdwpEventKind eventKind, const ModBasket& basket,
- std::vector<JdwpEvent*>* match_list)
- REQUIRES(!event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- void FindMatchingEventsLocked(JdwpEventKind eventKind, const ModBasket& basket,
- std::vector<JdwpEvent*>* match_list)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- void UnregisterEvent(JdwpEvent* pEvent)
- REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- void SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov);
-
- /*
- * When we hit a debugger event that requires suspension, it's important
- * that we wait for the thread to suspend itself before processing any
- * additional requests. Otherwise, if the debugger immediately sends a
- * "resume thread" command, the resume might arrive before the thread has
- * suspended itself.
- *
- * It's also important no event thread suspends while we process a command
- * from the debugger. Otherwise we could post an event ("thread death")
- * before sending the reply of the command being processed ("resume") and
- * cause bad synchronization with the debugger.
- *
- * The thread wanting "exclusive" access to the JDWP world must call the
- * SetWaitForJdwpToken method before processing a command from the
- * debugger or sending an event to the debugger.
- * Once the command is processed or the event thread has posted its event,
- * it must call the ClearWaitForJdwpToken method to allow another thread
- * to do JDWP stuff.
- *
- * Therefore the main JDWP handler loop will wait for the event thread
- * suspension before processing the next command. Once the event thread
- * has suspended itself and cleared the token, the JDWP handler continues
- * processing commands. This works in the suspend-all case because the
- * event thread doesn't suspend itself until everything else has suspended.
- *
- * It's possible that multiple threads could encounter thread-suspending
- * events at the same time, so we grab a mutex in the SetWaitForJdwpToken
- * call, and release it in the ClearWaitForJdwpToken call.
- */
- void SetWaitForJdwpToken(ObjectId threadId) REQUIRES(!jdwp_token_lock_);
- void ClearWaitForJdwpToken() REQUIRES(!jdwp_token_lock_);
-
- public: // TODO: fix privacy
- const JdwpOptions* options_;
-
- private:
- /* wait for creation of the JDWP thread */
- Mutex thread_start_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
- ConditionVariable thread_start_cond_ GUARDED_BY(thread_start_lock_);
-
- pthread_t pthread_;
- Thread* thread_;
-
- volatile bool debug_thread_started_ GUARDED_BY(thread_start_lock_);
- ObjectId debug_thread_id_;
-
- private:
- bool run;
-
- public: // TODO: fix privacy
- JdwpNetStateBase* netState;
-
- private:
- // For wait-for-debugger.
- Mutex attach_lock_ ACQUIRED_AFTER(thread_start_lock_);
- ConditionVariable attach_cond_ GUARDED_BY(attach_lock_);
-
- // Time of last debugger activity, in milliseconds.
- Atomic<int64_t> last_activity_time_ms_;
-
- // Global counters and a mutex to protect them.
- AtomicInteger request_serial_;
- AtomicInteger event_serial_;
-
- // Linked list of events requested by the debugger (breakpoints, class prep, etc).
- Mutex event_list_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_BEFORE(Locks::breakpoint_lock_);
- JdwpEvent* event_list_ GUARDED_BY(event_list_lock_);
- size_t event_list_size_ GUARDED_BY(event_list_lock_); // Number of elements in event_list_.
-
- // Used to synchronize JDWP command handler thread and event threads so only one
- // thread does JDWP stuff at a time. This prevent from interleaving command handling
- // and event notification. Otherwise we could receive a "resume" command for an
- // event thread that is not suspended yet, or post a "thread death" or event "VM death"
- // event before sending the reply of the "resume" command that caused it.
- Mutex jdwp_token_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
- ConditionVariable jdwp_token_cond_ GUARDED_BY(jdwp_token_lock_);
- ObjectId jdwp_token_owner_thread_id_;
-
- bool ddm_is_active_;
-
- // Used for VirtualMachine.Exit command handling.
- bool should_exit_;
- int exit_status_;
-
- // Used to synchronize runtime shutdown with JDWP command handler thread.
- // When the runtime shuts down, it needs to stop JDWP command handler thread by closing the
- // JDWP connection. However, if the JDWP thread is processing a command, it needs to wait
- // for the command to finish so we can send its reply before closing the connection.
- Mutex shutdown_lock_ ACQUIRED_AFTER(event_list_lock_);
- ConditionVariable shutdown_cond_ GUARDED_BY(shutdown_lock_);
- bool processing_request_ GUARDED_BY(shutdown_lock_);
-};
-
-std::string DescribeField(const FieldId& field_id) REQUIRES_SHARED(Locks::mutator_lock_);
-std::string DescribeMethod(const MethodId& method_id) REQUIRES_SHARED(Locks::mutator_lock_);
-std::string DescribeRefTypeId(const RefTypeId& ref_type_id) REQUIRES_SHARED(Locks::mutator_lock_);
-
-class Request {
- public:
- Request(const uint8_t* bytes, uint32_t available);
- ~Request();
-
- std::string ReadUtf8String();
-
- // Helper function: read a variable-width value from the input buffer.
- uint64_t ReadValue(size_t width);
-
- int32_t ReadSigned32(const char* what);
-
- uint32_t ReadUnsigned32(const char* what);
-
- FieldId ReadFieldId() REQUIRES_SHARED(Locks::mutator_lock_);
-
- MethodId ReadMethodId() REQUIRES_SHARED(Locks::mutator_lock_);
-
- ObjectId ReadObjectId(const char* specific_kind);
-
- ObjectId ReadArrayId();
-
- ObjectId ReadObjectId();
-
- ObjectId ReadThreadId();
-
- ObjectId ReadThreadGroupId();
-
- RefTypeId ReadRefTypeId() REQUIRES_SHARED(Locks::mutator_lock_);
-
- FrameId ReadFrameId();
-
- template <typename T> T ReadEnum1(const char* specific_kind) {
- T value = static_cast<T>(Read1());
- VLOG(jdwp) << " " << specific_kind << " " << value;
- return value;
- }
-
- JdwpTag ReadTag();
-
- JdwpTypeTag ReadTypeTag();
-
- JdwpLocation ReadLocation() REQUIRES_SHARED(Locks::mutator_lock_);
-
- JdwpModKind ReadModKind();
-
- //
- // Return values from this JDWP packet's header.
- //
- size_t GetLength() { return byte_count_; }
- uint32_t GetId() { return id_; }
- uint8_t GetCommandSet() { return command_set_; }
- uint8_t GetCommand() { return command_; }
-
- // Returns the number of bytes remaining.
- size_t size() { return end_ - p_; }
-
- // Returns a pointer to the next byte.
- const uint8_t* data() { return p_; }
-
- void Skip(size_t count) { p_ += count; }
-
- void CheckConsumed();
-
- private:
- uint8_t Read1();
- uint16_t Read2BE();
- uint32_t Read4BE();
- uint64_t Read8BE();
-
- uint32_t byte_count_;
- uint32_t id_;
- uint8_t command_set_;
- uint8_t command_;
-
- const uint8_t* p_;
- const uint8_t* end_;
-
- DISALLOW_COPY_AND_ASSIGN(Request);
-};
-
-} // namespace JDWP
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_JDWP_H_
diff --git a/runtime/jdwp/jdwp_adb.cc b/runtime/jdwp/jdwp_adb.cc
deleted file mode 100644
index 4fa9984f344..00000000000
--- a/runtime/jdwp/jdwp_adb.cc
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <errno.h>
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include "android-base/cmsg.h"
-#include "android-base/stringprintf.h"
-#include "android-base/unique_fd.h"
-
-#include "base/logging.h" // For VLOG.
-#include "base/socket_peer_is_trusted.h"
-#include "jdwp/jdwp_priv.h"
-#include "thread-current-inl.h"
-
-/*
- * The JDWP <-> ADB transport protocol is explained in detail
- * in system/core/adb/jdwp_service.c. Here's a summary.
- *
- * 1/ when the JDWP thread starts, it tries to connect to a Unix
- * domain stream socket (@jdwp-control) that is opened by the
- * ADB daemon.
- *
- * 2/ it then sends the current process PID as an int32_t.
- *
- * 3/ then, it uses recvmsg to receive file descriptors from the
- * daemon. each incoming file descriptor is a pass-through to
- * a given JDWP debugger, that can be used to read the usual
- * JDWP-handshake, etc...
- */
-
-static constexpr char kJdwpControlName[] = "\0jdwp-control";
-static constexpr size_t kJdwpControlNameLen = sizeof(kJdwpControlName) - 1;
-/* This timeout is for connect/send with control socket. In practice, the
- * connect should never timeout since it's just connect to a local unix domain
- * socket. But in case adb is buggy and doesn't respond to any connection, the
- * connect will block. For send, actually it would never block since we only send
- * several bytes and the kernel buffer is big enough to accept it. 10 seconds
- * should be far enough.
- */
-static constexpr int kControlSockSendTimeout = 10;
-
-namespace art {
-
-namespace JDWP {
-
-using android::base::StringPrintf;
-
-struct JdwpAdbState : public JdwpNetStateBase {
- public:
- explicit JdwpAdbState(JdwpState* state)
- : JdwpNetStateBase(state),
- state_lock_("JdwpAdbState lock", kJdwpAdbStateLock) {
- control_sock_ = -1;
- shutting_down_ = false;
-
- control_addr_.controlAddrUn.sun_family = AF_UNIX;
- control_addr_len_ = sizeof(control_addr_.controlAddrUn.sun_family) + kJdwpControlNameLen;
- memcpy(control_addr_.controlAddrUn.sun_path, kJdwpControlName, kJdwpControlNameLen);
- }
-
- ~JdwpAdbState() {
- if (clientSock != -1) {
- shutdown(clientSock, SHUT_RDWR);
- close(clientSock);
- }
- if (control_sock_ != -1) {
- shutdown(control_sock_, SHUT_RDWR);
- close(control_sock_);
- }
- }
-
- bool Accept() override REQUIRES(!state_lock_);
-
- bool Establish(const JdwpOptions*) override {
- return false;
- }
-
- void Shutdown() override REQUIRES(!state_lock_) {
- int control_sock;
- int local_clientSock;
- {
- MutexLock mu(Thread::Current(), state_lock_);
- shutting_down_ = true;
- control_sock = this->control_sock_;
- local_clientSock = this->clientSock;
- /* clear these out so it doesn't wake up and try to reuse them */
- this->control_sock_ = this->clientSock = -1;
- }
-
- if (local_clientSock != -1) {
- shutdown(local_clientSock, SHUT_RDWR);
- }
-
- if (control_sock != -1) {
- shutdown(control_sock, SHUT_RDWR);
- }
-
- WakePipe();
- }
-
- bool ProcessIncoming() override REQUIRES(!state_lock_);
-
- private:
- int ReceiveClientFd() REQUIRES(!state_lock_);
-
- bool IsDown() REQUIRES(!state_lock_) {
- MutexLock mu(Thread::Current(), state_lock_);
- return shutting_down_;
- }
-
- int ControlSock() REQUIRES(!state_lock_) {
- MutexLock mu(Thread::Current(), state_lock_);
- if (shutting_down_) {
- CHECK_EQ(control_sock_, -1);
- }
- return control_sock_;
- }
-
- int control_sock_ GUARDED_BY(state_lock_);
- bool shutting_down_ GUARDED_BY(state_lock_);
- Mutex state_lock_;
-
- socklen_t control_addr_len_;
- union {
- sockaddr_un controlAddrUn;
- sockaddr controlAddrPlain;
- } control_addr_;
-};
-
-/*
- * Do initial prep work, e.g. binding to ports and opening files. This
- * runs in the main thread, before the JDWP thread starts, so it shouldn't
- * do anything that might block forever.
- */
-bool InitAdbTransport(JdwpState* state, const JdwpOptions*) {
- VLOG(jdwp) << "ADB transport startup";
- state->netState = new JdwpAdbState(state);
- return (state->netState != nullptr);
-}
-
-/*
- * Receive a file descriptor from ADB. The fd can be used to communicate
- * directly with a debugger or DDMS.
- *
- * Returns the file descriptor on success. On failure, returns -1 and
- * closes netState->control_sock_.
- */
-int JdwpAdbState::ReceiveClientFd() {
- char dummy = '!';
- android::base::unique_fd client_fd;
- ssize_t rc = android::base::ReceiveFileDescriptors(ControlSock(), &dummy, 1, &client_fd);
-
- if (rc <= 0) {
- if (rc == -1) {
- PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << ControlSock() << ")";
- }
- MutexLock mu(Thread::Current(), state_lock_);
- close(control_sock_);
- control_sock_ = -1;
- return -1;
- }
-
- return client_fd.release();
-}
-
-/*
- * Block forever, waiting for a debugger to connect to us. Called from the
- * JDWP thread.
- *
- * This needs to un-block and return "false" if the VM is shutting down. It
- * should return "true" when it successfully accepts a connection.
- */
-bool JdwpAdbState::Accept() {
- int retryCount = 0;
-
- /* first, ensure that we get a connection to the ADB daemon */
-
- retry:
- if (IsDown()) {
- return false;
- }
-
- if (ControlSock() == -1) {
- int sleep_ms = 500;
- const int sleep_max_ms = 2*1000;
-
- int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
- if (sock < 0) {
- PLOG(ERROR) << "Could not create ADB control socket";
- return false;
- }
- struct timeval timeout;
- timeout.tv_sec = kControlSockSendTimeout;
- timeout.tv_usec = 0;
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
- {
- MutexLock mu(Thread::Current(), state_lock_);
- control_sock_ = sock;
- if (shutting_down_) {
- return false;
- }
- if (!MakePipe()) {
- return false;
- }
- }
-
- int32_t pid = getpid();
-
- for (;;) {
- /*
- * If adbd isn't running, because USB debugging was disabled or
- * perhaps the system is restarting it for "adb root", the
- * connect() will fail. We loop here forever waiting for it
- * to come back.
- *
- * Waking up and polling every couple of seconds is generally a
- * bad thing to do, but we only do this if the application is
- * debuggable *and* adbd isn't running. Still, for the sake
- * of battery life, we should consider timing out and giving
- * up after a few minutes in case somebody ships an app with
- * the debuggable flag set.
- */
- int ret = connect(ControlSock(), &control_addr_.controlAddrPlain, control_addr_len_);
- if (!ret) {
- int control_sock = ControlSock();
-#ifdef ART_TARGET_ANDROID
- if (control_sock < 0 || !art::SocketPeerIsTrusted(control_sock)) {
- if (control_sock >= 0 && shutdown(control_sock, SHUT_RDWR)) {
- PLOG(ERROR) << "trouble shutting down socket";
- }
- return false;
- }
-#endif
-
- /* now try to send our pid to the ADB daemon */
- ret = TEMP_FAILURE_RETRY(send(control_sock, &pid, sizeof(pid), 0));
- if (ret == sizeof(pid)) {
- VLOG(jdwp) << "PID " << pid << " sent to ADB";
- break;
- }
-
- PLOG(ERROR) << "Weird, can't send JDWP process pid to ADB";
- return false;
- }
- if (VLOG_IS_ON(jdwp)) {
- PLOG(ERROR) << "Can't connect to ADB control socket";
- }
-
- usleep(sleep_ms * 1000);
-
- sleep_ms += (sleep_ms >> 1);
- if (sleep_ms > sleep_max_ms) {
- sleep_ms = sleep_max_ms;
- }
- if (IsDown()) {
- return false;
- }
- }
- }
-
- VLOG(jdwp) << "trying to receive file descriptor from ADB";
- /* now we can receive a client file descriptor */
- int sock = ReceiveClientFd();
- {
- MutexLock mu(Thread::Current(), state_lock_);
- clientSock = sock;
- if (shutting_down_) {
- return false; // suppress logs and additional activity
- }
- }
- if (clientSock == -1) {
- if (++retryCount > 5) {
- LOG(ERROR) << "adb connection max retries exceeded";
- return false;
- }
- goto retry;
- } else {
- VLOG(jdwp) << "received file descriptor " << clientSock << " from ADB";
- SetAwaitingHandshake(true);
- input_count_ = 0;
- return true;
- }
-}
-
-/*
- * Process incoming data. If no data is available, this will block until
- * some arrives.
- *
- * If we get a full packet, handle it.
- *
- * To take some of the mystery out of life, we want to reject incoming
- * connections if we already have a debugger attached. If we don't, the
- * debugger will just mysteriously hang until it times out. We could just
- * close the listen socket, but there's a good chance we won't be able to
- * bind to the same port again, which would confuse utilities.
- *
- * Returns "false" on error (indicating that the connection has been severed),
- * "true" if things are still okay.
- */
-bool JdwpAdbState::ProcessIncoming() {
- int readCount;
-
- CHECK_NE(clientSock, -1);
-
- if (!HaveFullPacket()) {
- /* read some more, looping until we have data */
- errno = 0;
- while (true) {
- int selCount;
- fd_set readfds;
- int maxfd = -1;
- int fd;
-
- FD_ZERO(&readfds);
-
- /* configure fds; note these may get zapped by another thread */
- fd = ControlSock();
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- }
- fd = clientSock;
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- }
- fd = wake_pipe_[0];
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- } else {
- LOG(INFO) << "NOTE: entering select w/o wakepipe";
- }
-
- if (maxfd < 0) {
- VLOG(jdwp) << "+++ all fds are closed";
- return false;
- }
-
- /*
- * Select blocks until it sees activity on the file descriptors.
- * Closing the local file descriptor does not count as activity,
- * so we can't rely on that to wake us up (it works for read()
- * and accept(), but not select()).
- *
- * We can do one of three things: (1) send a signal and catch
- * EINTR, (2) open an additional fd ("wake pipe") and write to
- * it when it's time to exit, or (3) time out periodically and
- * re-issue the select. We're currently using #2, as it's more
- * reliable than #1 and generally better than #3. Wastes two fds.
- */
- selCount = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr);
- if (selCount < 0) {
- if (errno == EINTR) {
- continue;
- }
- PLOG(ERROR) << "select failed";
- goto fail;
- }
-
- if (wake_pipe_[0] >= 0 && FD_ISSET(wake_pipe_[0], &readfds)) {
- VLOG(jdwp) << "Got wake-up signal, bailing out of select";
- goto fail;
- }
- int control_sock = ControlSock();
- if (control_sock >= 0 && FD_ISSET(control_sock, &readfds)) {
- int sock = ReceiveClientFd();
- if (sock >= 0) {
- LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
- close(sock);
- } else {
- CHECK_EQ(ControlSock(), -1);
- /*
- * Remote side most likely went away, so our next read
- * on clientSock will fail and throw us out of the loop.
- */
- }
- }
- if (clientSock >= 0 && FD_ISSET(clientSock, &readfds)) {
- readCount = read(clientSock, input_buffer_ + input_count_, sizeof(input_buffer_) - input_count_);
- if (readCount < 0) {
- /* read failed */
- if (errno != EINTR) {
- goto fail;
- }
- VLOG(jdwp) << "+++ EINTR hit";
- return true;
- } else if (readCount == 0) {
- /* EOF hit -- far end went away */
- VLOG(jdwp) << "+++ peer disconnected";
- goto fail;
- } else {
- break;
- }
- }
- }
-
- input_count_ += readCount;
- if (!HaveFullPacket()) {
- return true; /* still not there yet */
- }
- }
-
- /*
- * Special-case the initial handshake. For some bizarre reason we're
- * expected to emulate bad tty settings by echoing the request back
- * exactly as it was sent. Note the handshake is always initiated by
- * the debugger, no matter who connects to whom.
- *
- * Other than this one case, the protocol [claims to be] stateless.
- */
- if (IsAwaitingHandshake()) {
- if (memcmp(input_buffer_, kMagicHandshake, kMagicHandshakeLen) != 0) {
- LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", input_buffer_);
- goto fail;
- }
-
- errno = 0;
- int cc = TEMP_FAILURE_RETRY(write(clientSock, input_buffer_, kMagicHandshakeLen));
- if (cc != kMagicHandshakeLen) {
- PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
- goto fail;
- }
-
- ConsumeBytes(kMagicHandshakeLen);
- SetAwaitingHandshake(false);
- VLOG(jdwp) << "+++ handshake complete";
- return true;
- }
-
- /*
- * Handle this packet.
- */
- return state_->HandlePacket();
-
- fail:
- Close();
- return false;
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_constants.h b/runtime/jdwp/jdwp_constants.h
deleted file mode 100644
index 9fc896d8eb3..00000000000
--- a/runtime/jdwp/jdwp_constants.h
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * These come out of the JDWP documentation.
- */
-#ifndef ART_RUNTIME_JDWP_JDWP_CONSTANTS_H_
-#define ART_RUNTIME_JDWP_JDWP_CONSTANTS_H_
-
-#include <iosfwd>
-
-namespace art {
-
-namespace JDWP {
-
-/*
- * Error constants.
- */
-enum JdwpError {
- ERR_NONE = 0,
- ERR_INVALID_THREAD = 10,
- ERR_INVALID_THREAD_GROUP = 11,
- ERR_INVALID_PRIORITY = 12,
- ERR_THREAD_NOT_SUSPENDED = 13,
- ERR_THREAD_SUSPENDED = 14,
- ERR_THREAD_NOT_ALIVE = 15,
- ERR_INVALID_OBJECT = 20,
- ERR_INVALID_CLASS = 21,
- ERR_CLASS_NOT_PREPARED = 22,
- ERR_INVALID_METHODID = 23,
- ERR_INVALID_LOCATION = 24,
- ERR_INVALID_FIELDID = 25,
- ERR_INVALID_FRAMEID = 30,
- ERR_NO_MORE_FRAMES = 31,
- ERR_OPAQUE_FRAME = 32,
- ERR_NOT_CURRENT_FRAME = 33,
- ERR_TYPE_MISMATCH = 34,
- ERR_INVALID_SLOT = 35,
- ERR_DUPLICATE = 40,
- ERR_NOT_FOUND = 41,
- ERR_INVALID_MONITOR = 50,
- ERR_NOT_MONITOR_OWNER = 51,
- ERR_INTERRUPT = 52,
- ERR_INVALID_CLASS_FORMAT = 60,
- ERR_CIRCULAR_CLASS_DEFINITION = 61,
- ERR_FAILS_VERIFICATION = 62,
- ERR_ADD_METHOD_NOT_IMPLEMENTED = 63,
- ERR_SCHEMA_CHANGE_NOT_IMPLEMENTED = 64,
- ERR_INVALID_TYPESTATE = 65,
- ERR_HIERARCHY_CHANGE_NOT_IMPLEMENTED = 66,
- ERR_DELETE_METHOD_NOT_IMPLEMENTED = 67,
- ERR_UNSUPPORTED_VERSION = 68,
- ERR_NAMES_DONT_MATCH = 69,
- ERR_CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 70,
- ERR_METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 71,
- ERR_NOT_IMPLEMENTED = 99,
- ERR_NULL_POINTER = 100,
- ERR_ABSENT_INFORMATION = 101,
- ERR_INVALID_EVENT_TYPE = 102,
- ERR_ILLEGAL_ARGUMENT = 103,
- ERR_OUT_OF_MEMORY = 110,
- ERR_ACCESS_DENIED = 111,
- ERR_VM_DEAD = 112,
- ERR_INTERNAL = 113,
- ERR_UNATTACHED_THREAD = 115,
- ERR_INVALID_TAG = 500,
- ERR_ALREADY_INVOKING = 502,
- ERR_INVALID_INDEX = 503,
- ERR_INVALID_LENGTH = 504,
- ERR_INVALID_STRING = 506,
- ERR_INVALID_CLASS_LOADER = 507,
- ERR_INVALID_ARRAY = 508,
- ERR_TRANSPORT_LOAD = 509,
- ERR_TRANSPORT_INIT = 510,
- ERR_NATIVE_METHOD = 511,
- ERR_INVALID_COUNT = 512,
-};
-std::ostream& operator<<(std::ostream& os, const JdwpError& value);
-
-
-/*
- * ClassStatus constants. These are bit flags that can be ORed together.
- */
-enum JdwpClassStatus {
- CS_VERIFIED = 0x01,
- CS_PREPARED = 0x02,
- CS_INITIALIZED = 0x04,
- CS_ERROR = 0x08,
-};
-std::ostream& operator<<(std::ostream& os, const JdwpClassStatus& value);
-
-/*
- * EventKind constants.
- */
-enum JdwpEventKind {
- EK_SINGLE_STEP = 1,
- EK_BREAKPOINT = 2,
- EK_FRAME_POP = 3,
- EK_EXCEPTION = 4,
- EK_USER_DEFINED = 5,
- EK_THREAD_START = 6,
- EK_THREAD_DEATH = 7, // Formerly known as THREAD_END.
- EK_CLASS_PREPARE = 8,
- EK_CLASS_UNLOAD = 9,
- EK_CLASS_LOAD = 10,
- EK_FIELD_ACCESS = 20,
- EK_FIELD_MODIFICATION = 21,
- EK_EXCEPTION_CATCH = 30,
- EK_METHOD_ENTRY = 40,
- EK_METHOD_EXIT = 41,
- EK_METHOD_EXIT_WITH_RETURN_VALUE = 42,
- EK_MONITOR_CONTENDED_ENTER = 43,
- EK_MONITOR_CONTENDED_ENTERED = 44,
- EK_MONITOR_WAIT = 45,
- EK_MONITOR_WAITED = 46,
- EK_VM_START = 90, // Formerly known as VM_INIT.
- EK_VM_DEATH = 99,
- EK_VM_DISCONNECTED = 100, // "Never sent across JDWP".
-};
-std::ostream& operator<<(std::ostream& os, const JdwpEventKind& value);
-
-/*
- * Values for "modKind" in EventRequest.Set.
- */
-enum JdwpModKind {
- MK_COUNT = 1,
- MK_CONDITIONAL = 2,
- MK_THREAD_ONLY = 3,
- MK_CLASS_ONLY = 4,
- MK_CLASS_MATCH = 5,
- MK_CLASS_EXCLUDE = 6,
- MK_LOCATION_ONLY = 7,
- MK_EXCEPTION_ONLY = 8,
- MK_FIELD_ONLY = 9,
- MK_STEP = 10,
- MK_INSTANCE_ONLY = 11,
- MK_SOURCE_NAME_MATCH = 12, // Since Java 6.
-};
-std::ostream& operator<<(std::ostream& os, const JdwpModKind& value);
-
-/*
- * InvokeOptions constants (bit flags).
- */
-enum JdwpInvokeOptions {
- INVOKE_SINGLE_THREADED = 0x01,
- INVOKE_NONVIRTUAL = 0x02,
-};
-std::ostream& operator<<(std::ostream& os, const JdwpInvokeOptions& value);
-
-/*
- * StepDepth constants.
- */
-enum JdwpStepDepth {
- SD_INTO = 0, // Step into method calls.
- SD_OVER = 1, // Step over method calls.
- SD_OUT = 2, // Step out of current method.
-};
-std::ostream& operator<<(std::ostream& os, const JdwpStepDepth& value);
-
-/*
- * StepSize constants.
- */
-enum JdwpStepSize {
- SS_MIN = 0, // Step by minimum (for example, one bytecode).
- SS_LINE = 1, // If possible, step to next line.
-};
-std::ostream& operator<<(std::ostream& os, const JdwpStepSize& value);
-
-/*
- * SuspendPolicy constants.
- */
-enum JdwpSuspendPolicy {
- SP_NONE = 0, // Suspend no threads.
- SP_EVENT_THREAD = 1, // Suspend event thread.
- SP_ALL = 2, // Suspend all threads.
-};
-std::ostream& operator<<(std::ostream& os, const JdwpSuspendPolicy& value);
-
-/*
- * SuspendStatus constants.
- */
-enum JdwpSuspendStatus {
- SUSPEND_STATUS_NOT_SUSPENDED = 0,
- SUSPEND_STATUS_SUSPENDED = 1,
-};
-std::ostream& operator<<(std::ostream& os, const JdwpSuspendStatus& value);
-
-/*
- * ThreadStatus constants.
- */
-enum JdwpThreadStatus {
- TS_ZOMBIE = 0,
- TS_RUNNING = 1, // RUNNING
- TS_SLEEPING = 2, // (in Thread.sleep())
- TS_MONITOR = 3, // WAITING (monitor wait)
- TS_WAIT = 4, // (in Object.wait())
-};
-std::ostream& operator<<(std::ostream& os, const JdwpThreadStatus& value);
-
-/*
- * TypeTag constants.
- */
-enum JdwpTypeTag {
- TT_CLASS = 1,
- TT_INTERFACE = 2,
- TT_ARRAY = 3,
-};
-std::ostream& operator<<(std::ostream& os, const JdwpTypeTag& value);
-
-/*
- * Tag constants.
- */
-enum JdwpTag {
- JT_ARRAY = '[',
- JT_BYTE = 'B',
- JT_CHAR = 'C',
- JT_OBJECT = 'L',
- JT_FLOAT = 'F',
- JT_DOUBLE = 'D',
- JT_INT = 'I',
- JT_LONG = 'J',
- JT_SHORT = 'S',
- JT_VOID = 'V',
- JT_BOOLEAN = 'Z',
- JT_STRING = 's',
- JT_THREAD = 't',
- JT_THREAD_GROUP = 'g',
- JT_CLASS_LOADER = 'l',
- JT_CLASS_OBJECT = 'c',
-};
-std::ostream& operator<<(std::ostream& os, const JdwpTag& value);
-
-} // namespace JDWP
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_JDWP_CONSTANTS_H_
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
deleted file mode 100644
index ca8774de037..00000000000
--- a/runtime/jdwp/jdwp_event.cc
+++ /dev/null
@@ -1,1385 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jdwp/jdwp_event.h"
-
-#include <stddef.h> /* for offsetof() */
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "android-base/stringprintf.h"
-
-#include "art_field-inl.h"
-#include "art_method-inl.h"
-#include "base/logging.h" // For VLOG.
-#include "debugger.h"
-#include "jdwp/jdwp_constants.h"
-#include "jdwp/jdwp_expand_buf.h"
-#include "jdwp/jdwp_priv.h"
-#include "jdwp/object_registry-inl.h"
-#include "obj_ptr-inl.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-inl.h"
-
-#include "handle_scope-inl.h"
-
-/*
-General notes:
-
-The event add/remove stuff usually happens from the debugger thread,
-in response to requests from the debugger, but can also happen as the
-result of an event in an arbitrary thread (e.g. an event with a "count"
-mod expires). It's important to keep the event list locked when processing
-events.
-
-Event posting can happen from any thread. The JDWP thread will not usually
-post anything but VM start/death, but if a JDWP request causes a class
-to be loaded, the ClassPrepare event will come from the JDWP thread.
-
-
-We can have serialization issues when we post an event to the debugger.
-For example, a thread could send an "I hit a breakpoint and am suspending
-myself" message to the debugger. Before it manages to suspend itself, the
-debugger's response ("not interested, resume thread") arrives and is
-processed. We try to resume a thread that hasn't yet suspended.
-
-This means that, after posting an event to the debugger, we need to wait
-for the event thread to suspend itself (and, potentially, all other threads)
-before processing any additional requests from the debugger. While doing
-so we need to be aware that multiple threads may be hitting breakpoints
-or other events simultaneously, so we either need to wait for all of them
-or serialize the events with each other.
-
-The current mechanism works like this:
- Event thread:
- - If I'm going to suspend, grab the "I am posting an event" token. Wait
- for it if it's not currently available.
- - Post the event to the debugger.
- - If appropriate, suspend others and then myself. As part of suspending
- myself, release the "I am posting" token.
- JDWP thread:
- - When an event arrives, see if somebody is posting an event. If so,
- sleep until we can acquire the "I am posting an event" token. Release
- it immediately and continue processing -- the event we have already
- received should not interfere with other events that haven't yet
- been posted.
-
-Some care must be taken to avoid deadlock:
-
- - thread A and thread B exit near-simultaneously, and post thread-death
- events with a "suspend all" clause
- - thread A gets the event token, thread B sits and waits for it
- - thread A wants to suspend all other threads, but thread B is waiting
- for the token and can't be suspended
-
-So we need to mark thread B in such a way that thread A doesn't wait for it.
-
-If we just bracket the "grab event token" call with a change to VMWAIT
-before sleeping, the switch back to RUNNING state when we get the token
-will cause thread B to suspend (remember, thread A's global suspend is
-still in force, even after it releases the token). Suspending while
-holding the event token is very bad, because it prevents the JDWP thread
-from processing incoming messages.
-
-We need to change to VMWAIT state at the *start* of posting an event,
-and stay there until we either finish posting the event or decide to
-put ourselves to sleep. That way we don't interfere with anyone else and
-don't allow anyone else to interfere with us.
-*/
-
-namespace art {
-
-namespace JDWP {
-
-using android::base::StringPrintf;
-
-/*
- * Stuff to compare against when deciding if a mod matches. Only the
- * values for mods valid for the event being evaluated will be filled in.
- * The rest will be zeroed.
- * Must be allocated on the stack only. This is enforced by removing the
- * operator new.
- */
-struct ModBasket {
- explicit ModBasket(Thread* self)
- : hs(self), pLoc(nullptr), thread(self),
- locationClass(hs.NewHandle<mirror::Class>(nullptr)),
- exceptionClass(hs.NewHandle<mirror::Class>(nullptr)),
- caught(false),
- field(nullptr),
- thisPtr(hs.NewHandle<mirror::Object>(nullptr)) { }
-
- StackHandleScope<3> hs;
- const EventLocation* pLoc; /* LocationOnly */
- std::string className; /* ClassMatch/ClassExclude */
- Thread* const thread; /* ThreadOnly */
- MutableHandle<mirror::Class> locationClass; /* ClassOnly */
- MutableHandle<mirror::Class> exceptionClass; /* ExceptionOnly */
- bool caught; /* ExceptionOnly */
- ArtField* field; /* FieldOnly */
- MutableHandle<mirror::Object> thisPtr; /* InstanceOnly */
- /* nothing for StepOnly -- handled differently */
-
- private:
- DISALLOW_ALLOCATION(); // forbids allocation on the heap.
- DISALLOW_IMPLICIT_CONSTRUCTORS(ModBasket);
-};
-
-static bool NeedsFullDeoptimization(JdwpEventKind eventKind) {
- if (!Dbg::RequiresDeoptimization()) {
- // We don't need deoptimization for debugging.
- return false;
- }
- switch (eventKind) {
- case EK_METHOD_ENTRY:
- case EK_METHOD_EXIT:
- case EK_METHOD_EXIT_WITH_RETURN_VALUE:
- case EK_FIELD_ACCESS:
- case EK_FIELD_MODIFICATION:
- return true;
- default:
- return false;
- }
-}
-
-// Returns the instrumentation event the DebugInstrumentationListener must
-// listen to in order to properly report the given JDWP event to the debugger.
-static uint32_t GetInstrumentationEventFor(JdwpEventKind eventKind) {
- switch (eventKind) {
- case EK_BREAKPOINT:
- case EK_SINGLE_STEP:
- return instrumentation::Instrumentation::kDexPcMoved;
- case EK_EXCEPTION:
- case EK_EXCEPTION_CATCH:
- return instrumentation::Instrumentation::kExceptionThrown;
- case EK_METHOD_ENTRY:
- return instrumentation::Instrumentation::kMethodEntered;
- case EK_METHOD_EXIT:
- case EK_METHOD_EXIT_WITH_RETURN_VALUE:
- return instrumentation::Instrumentation::kMethodExited;
- case EK_FIELD_ACCESS:
- return instrumentation::Instrumentation::kFieldRead;
- case EK_FIELD_MODIFICATION:
- return instrumentation::Instrumentation::kFieldWritten;
- default:
- return 0;
- }
-}
-
-/*
- * Add an event to the list. Ordering is not important.
- *
- * If something prevents the event from being registered, e.g. it's a
- * single-step request on a thread that doesn't exist, the event will
- * not be added to the list, and an appropriate error will be returned.
- */
-JdwpError JdwpState::RegisterEvent(JdwpEvent* pEvent) {
- CHECK(pEvent != nullptr);
- CHECK(pEvent->prev == nullptr);
- CHECK(pEvent->next == nullptr);
-
- {
- /*
- * If one or more "break"-type mods are used, register them with
- * the interpreter.
- */
- DeoptimizationRequest req;
- for (int i = 0; i < pEvent->modCount; i++) {
- const JdwpEventMod* pMod = &pEvent->mods[i];
- if (pMod->modKind == MK_LOCATION_ONLY) {
- // Should only concern breakpoint, field access, field modification, step, and exception
- // events.
- // However breakpoint requires specific handling. Field access, field modification and step
- // events need full deoptimization to be reported while exception event is reported during
- // exception handling.
- if (pEvent->eventKind == EK_BREAKPOINT) {
- Dbg::WatchLocation(&pMod->locationOnly.loc, &req);
- }
- } else if (pMod->modKind == MK_STEP) {
- /* should only be for EK_SINGLE_STEP; should only be one */
- JdwpStepSize size = static_cast<JdwpStepSize>(pMod->step.size);
- JdwpStepDepth depth = static_cast<JdwpStepDepth>(pMod->step.depth);
- JdwpError status = Dbg::ConfigureStep(pMod->step.threadId, size, depth);
- if (status != ERR_NONE) {
- return status;
- }
- }
- }
- if (NeedsFullDeoptimization(pEvent->eventKind)) {
- CHECK_EQ(req.GetKind(), DeoptimizationRequest::kNothing);
- CHECK(req.Method() == nullptr);
- req.SetKind(DeoptimizationRequest::kFullDeoptimization);
- }
- Dbg::RequestDeoptimization(req);
- }
- uint32_t instrumentation_event = GetInstrumentationEventFor(pEvent->eventKind);
- if (instrumentation_event != 0) {
- DeoptimizationRequest req;
- req.SetKind(DeoptimizationRequest::kRegisterForEvent);
- req.SetInstrumentationEvent(instrumentation_event);
- Dbg::RequestDeoptimization(req);
- }
-
- {
- /*
- * Add to list.
- */
- MutexLock mu(Thread::Current(), event_list_lock_);
- if (event_list_ != nullptr) {
- pEvent->next = event_list_;
- event_list_->prev = pEvent;
- }
- event_list_ = pEvent;
- ++event_list_size_;
- }
-
- Dbg::ManageDeoptimization();
-
- return ERR_NONE;
-}
-
-void JdwpState::UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass) {
- VLOG(jdwp) << "Removing events within " << klass->PrettyClass();
- StackHandleScope<1> hs(Thread::Current());
- Handle<mirror::Class> h_klass(hs.NewHandle(klass));
- std::vector<JdwpEvent*> to_remove;
- MutexLock mu(Thread::Current(), event_list_lock_);
- for (JdwpEvent* cur_event = event_list_; cur_event != nullptr; cur_event = cur_event->next) {
- // Fill in the to_remove list
- bool found_event = false;
- for (int i = 0; i < cur_event->modCount && !found_event; i++) {
- JdwpEventMod& mod = cur_event->mods[i];
- switch (mod.modKind) {
- case MK_LOCATION_ONLY: {
- JdwpLocation& loc = mod.locationOnly.loc;
- JdwpError error;
- ObjPtr<mirror::Class> breakpoint_class(
- Dbg::GetObjectRegistry()->Get<art::mirror::Class>(loc.class_id, &error));
- DCHECK_EQ(error, ERR_NONE);
- if (breakpoint_class == h_klass.Get()) {
- to_remove.push_back(cur_event);
- found_event = true;
- }
- break;
- }
- default:
- // TODO Investigate how we should handle non-locationOnly events.
- break;
- }
- }
- }
-
- for (JdwpEvent* event : to_remove) {
- UnregisterEvent(event);
- EventFree(event);
- }
-}
-
-/*
- * Remove an event from the list. This will also remove the event from
- * any optimization tables, e.g. breakpoints.
- *
- * Does not free the JdwpEvent.
- *
- * Grab the eventLock before calling here.
- */
-void JdwpState::UnregisterEvent(JdwpEvent* pEvent) {
- if (pEvent->prev == nullptr) {
- /* head of the list */
- CHECK(event_list_ == pEvent);
-
- event_list_ = pEvent->next;
- } else {
- pEvent->prev->next = pEvent->next;
- }
-
- if (pEvent->next != nullptr) {
- pEvent->next->prev = pEvent->prev;
- pEvent->next = nullptr;
- }
- pEvent->prev = nullptr;
-
- {
- /*
- * Unhook us from the interpreter, if necessary.
- */
- DeoptimizationRequest req;
- for (int i = 0; i < pEvent->modCount; i++) {
- JdwpEventMod* pMod = &pEvent->mods[i];
- if (pMod->modKind == MK_LOCATION_ONLY) {
- // Like in RegisterEvent, we need specific handling for breakpoint only.
- if (pEvent->eventKind == EK_BREAKPOINT) {
- Dbg::UnwatchLocation(&pMod->locationOnly.loc, &req);
- }
- }
- if (pMod->modKind == MK_STEP) {
- /* should only be for EK_SINGLE_STEP; should only be one */
- Dbg::UnconfigureStep(pMod->step.threadId);
- }
- }
- if (NeedsFullDeoptimization(pEvent->eventKind)) {
- CHECK_EQ(req.GetKind(), DeoptimizationRequest::kNothing);
- CHECK(req.Method() == nullptr);
- req.SetKind(DeoptimizationRequest::kFullUndeoptimization);
- }
- Dbg::RequestDeoptimization(req);
- }
- uint32_t instrumentation_event = GetInstrumentationEventFor(pEvent->eventKind);
- if (instrumentation_event != 0) {
- DeoptimizationRequest req;
- req.SetKind(DeoptimizationRequest::kUnregisterForEvent);
- req.SetInstrumentationEvent(instrumentation_event);
- Dbg::RequestDeoptimization(req);
- }
-
- --event_list_size_;
- CHECK(event_list_size_ != 0 || event_list_ == nullptr);
-}
-
-/*
- * Remove the event with the given ID from the list.
- *
- */
-void JdwpState::UnregisterEventById(uint32_t requestId) {
- bool found = false;
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
-
- for (JdwpEvent* pEvent = event_list_; pEvent != nullptr; pEvent = pEvent->next) {
- if (pEvent->requestId == requestId) {
- found = true;
- UnregisterEvent(pEvent);
- EventFree(pEvent);
- break; /* there can be only one with a given ID */
- }
- }
- }
-
- if (found) {
- Dbg::ManageDeoptimization();
- } else {
- // Failure to find the event isn't really an error. For instance, it looks like Eclipse will
- // try to be extra careful and will explicitly remove one-off single-step events (using a
- // 'count' event modifier of 1). So the event may have already been removed as part of the
- // event notification (see JdwpState::CleanupMatchList).
- VLOG(jdwp) << StringPrintf("No match when removing event reqId=0x%04x", requestId);
- }
-}
-
-/*
- * Remove all entries from the event list.
- */
-void JdwpState::UnregisterAll() {
- MutexLock mu(Thread::Current(), event_list_lock_);
-
- JdwpEvent* pEvent = event_list_;
- while (pEvent != nullptr) {
- JdwpEvent* pNextEvent = pEvent->next;
-
- UnregisterEvent(pEvent);
- EventFree(pEvent);
- pEvent = pNextEvent;
- }
-
- event_list_ = nullptr;
-}
-
-/*
- * Allocate a JdwpEvent struct with enough space to hold the specified
- * number of mod records.
- */
-JdwpEvent* EventAlloc(int numMods) {
- JdwpEvent* newEvent;
- int allocSize = offsetof(JdwpEvent, mods) + numMods * sizeof(newEvent->mods[0]);
- newEvent = reinterpret_cast<JdwpEvent*>(malloc(allocSize));
- memset(newEvent, 0, allocSize);
- return newEvent;
-}
-
-/*
- * Free a JdwpEvent.
- *
- * Do not call this until the event has been removed from the list.
- */
-void EventFree(JdwpEvent* pEvent) {
- if (pEvent == nullptr) {
- return;
- }
-
- /* make sure it was removed from the list */
- CHECK(pEvent->prev == nullptr);
- CHECK(pEvent->next == nullptr);
- /* want to check state->event_list_ != pEvent */
-
- /*
- * Free any hairy bits in the mods.
- */
- for (int i = 0; i < pEvent->modCount; i++) {
- if (pEvent->mods[i].modKind == MK_CLASS_MATCH) {
- free(pEvent->mods[i].classMatch.classPattern);
- pEvent->mods[i].classMatch.classPattern = nullptr;
- }
- if (pEvent->mods[i].modKind == MK_CLASS_EXCLUDE) {
- free(pEvent->mods[i].classExclude.classPattern);
- pEvent->mods[i].classExclude.classPattern = nullptr;
- }
- }
-
- free(pEvent);
-}
-
-/*
- * Run through the list and remove any entries with an expired "count" mod
- * from the event list.
- */
-void JdwpState::CleanupMatchList(const std::vector<JdwpEvent*>& match_list) {
- for (JdwpEvent* pEvent : match_list) {
- for (int i = 0; i < pEvent->modCount; ++i) {
- if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) {
- VLOG(jdwp) << StringPrintf("##### Removing expired event (requestId=%#" PRIx32 ")",
- pEvent->requestId);
- UnregisterEvent(pEvent);
- EventFree(pEvent);
- break;
- }
- }
- }
-}
-
-/*
- * Match a string against a "restricted regular expression", which is just
- * a string that may start or end with '*' (e.g. "*.Foo" or "java.*").
- *
- * ("Restricted name globbing" might have been a better term.)
- */
-static bool PatternMatch(const char* pattern, const std::string& target) {
- size_t patLen = strlen(pattern);
- if (pattern[0] == '*') {
- patLen--;
- if (target.size() < patLen) {
- return false;
- }
- return strcmp(pattern+1, target.c_str() + (target.size()-patLen)) == 0;
- } else if (pattern[patLen-1] == '*') {
- return strncmp(pattern, target.c_str(), patLen-1) == 0;
- } else {
- return strcmp(pattern, target.c_str()) == 0;
- }
-}
-
-/*
- * See if the event's mods match up with the contents of "basket".
- *
- * If we find a Count mod before rejecting an event, we decrement it. We
- * need to do this even if later mods cause us to ignore the event.
- */
-static bool ModsMatch(JdwpEvent* pEvent, const ModBasket& basket)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JdwpEventMod* pMod = pEvent->mods;
-
- for (int i = pEvent->modCount; i > 0; i--, pMod++) {
- switch (pMod->modKind) {
- case MK_COUNT:
- CHECK_GT(pMod->count.count, 0);
- pMod->count.count--;
- if (pMod->count.count > 0) {
- return false;
- }
- break;
- case MK_CONDITIONAL:
- LOG(FATAL) << "Unexpected MK_CONDITIONAL"; // should not be getting these
- UNREACHABLE();
- case MK_THREAD_ONLY:
- if (!Dbg::MatchThread(pMod->threadOnly.threadId, basket.thread)) {
- return false;
- }
- break;
- case MK_CLASS_ONLY:
- if (!Dbg::MatchType(basket.locationClass.Get(), pMod->classOnly.refTypeId)) {
- return false;
- }
- break;
- case MK_CLASS_MATCH:
- if (!PatternMatch(pMod->classMatch.classPattern, basket.className)) {
- return false;
- }
- break;
- case MK_CLASS_EXCLUDE:
- if (PatternMatch(pMod->classMatch.classPattern, basket.className)) {
- return false;
- }
- break;
- case MK_LOCATION_ONLY:
- if (!Dbg::MatchLocation(pMod->locationOnly.loc, *basket.pLoc)) {
- return false;
- }
- break;
- case MK_EXCEPTION_ONLY:
- if (pMod->exceptionOnly.refTypeId != 0 &&
- !Dbg::MatchType(basket.exceptionClass.Get(), pMod->exceptionOnly.refTypeId)) {
- return false;
- }
- if ((basket.caught && !pMod->exceptionOnly.caught) ||
- (!basket.caught && !pMod->exceptionOnly.uncaught)) {
- return false;
- }
- break;
- case MK_FIELD_ONLY:
- if (!Dbg::MatchField(pMod->fieldOnly.refTypeId, pMod->fieldOnly.fieldId, basket.field)) {
- return false;
- }
- break;
- case MK_STEP:
- if (!Dbg::MatchThread(pMod->step.threadId, basket.thread)) {
- return false;
- }
- break;
- case MK_INSTANCE_ONLY:
- if (!Dbg::MatchInstance(pMod->instanceOnly.objectId, basket.thisPtr.Get())) {
- return false;
- }
- break;
- default:
- LOG(FATAL) << "unknown mod kind " << pMod->modKind;
- UNREACHABLE();
- }
- }
- return true;
-}
-
-/*
- * Find all events of type "event_kind" with mods that match up with the
- * rest of the arguments while holding the event list lock. This method
- * is used by FindMatchingEvents below.
- *
- * Found events are appended to "match_list" so this may be called multiple times for grouped
- * events.
- *
- * DO NOT call this multiple times for the same eventKind, as Count mods are
- * decremented during the scan.
- */
-void JdwpState::FindMatchingEventsLocked(JdwpEventKind event_kind, const ModBasket& basket,
- std::vector<JdwpEvent*>* match_list) {
- for (JdwpEvent* pEvent = event_list_; pEvent != nullptr; pEvent = pEvent->next) {
- if (pEvent->eventKind == event_kind && ModsMatch(pEvent, basket)) {
- match_list->push_back(pEvent);
- }
- }
-}
-
-/*
- * Find all events of type "event_kind" with mods that match up with the
- * rest of the arguments and return true if at least one event matches,
- * false otherwise.
- *
- * Found events are appended to "match_list" so this may be called multiple
- * times for grouped events.
- *
- * DO NOT call this multiple times for the same eventKind, as Count mods are
- * decremented during the scan.
- */
-bool JdwpState::FindMatchingEvents(JdwpEventKind event_kind, const ModBasket& basket,
- std::vector<JdwpEvent*>* match_list) {
- MutexLock mu(Thread::Current(), event_list_lock_);
- match_list->reserve(event_list_size_);
- FindMatchingEventsLocked(event_kind, basket, match_list);
- return !match_list->empty();
-}
-
-/*
- * Scan through the list of matches and determine the most severe
- * suspension policy.
- */
-static JdwpSuspendPolicy ScanSuspendPolicy(const std::vector<JdwpEvent*>& match_list) {
- JdwpSuspendPolicy policy = SP_NONE;
-
- for (JdwpEvent* pEvent : match_list) {
- if (pEvent->suspend_policy > policy) {
- policy = pEvent->suspend_policy;
- }
- }
-
- return policy;
-}
-
-/*
- * Three possibilities:
- * SP_NONE - do nothing
- * SP_EVENT_THREAD - suspend ourselves
- * SP_ALL - suspend everybody except JDWP support thread
- */
-void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id) {
- VLOG(jdwp) << "SuspendByPolicy(" << suspend_policy << ")";
- if (suspend_policy == SP_NONE) {
- return;
- }
-
- if (suspend_policy == SP_ALL) {
- Dbg::SuspendVM();
- } else {
- CHECK_EQ(suspend_policy, SP_EVENT_THREAD);
- }
-
- /* this is rare but possible -- see CLASS_PREPARE handling */
- if (thread_self_id == debug_thread_id_) {
- LOG(INFO) << "NOTE: SuspendByPolicy not suspending JDWP thread";
- return;
- }
-
- while (true) {
- Dbg::SuspendSelf();
-
- /*
- * The JDWP thread has told us (and possibly all other threads) to
- * resume. See if it has left anything in our DebugInvokeReq mailbox.
- */
- DebugInvokeReq* const pReq = Dbg::GetInvokeReq();
- if (pReq == nullptr) {
- break;
- }
-
- // Execute method.
- Dbg::ExecuteMethod(pReq);
- }
-}
-
-void JdwpState::SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy suspend_policy,
- ObjectId threadId) {
- Thread* const self = Thread::Current();
- self->AssertThreadSuspensionIsAllowable();
- CHECK(pReq != nullptr);
- CHECK_EQ(threadId, Dbg::GetThreadSelfId()) << "Only the current thread can suspend itself";
- /* send request and possibly suspend ourselves */
- ScopedThreadSuspension sts(self, kWaitingForDebuggerSend);
- if (suspend_policy != SP_NONE) {
- AcquireJdwpTokenForEvent(threadId);
- }
- EventFinish(pReq);
- {
- // Before suspending, we change our state to kSuspended so the debugger sees us as RUNNING.
- ScopedThreadStateChange stsc(self, kSuspended);
- SuspendByPolicy(suspend_policy, threadId);
- }
-}
-
-/*
- * Determine if there is a method invocation in progress in the current
- * thread.
- *
- * We look at the "invoke_needed" flag in the per-thread DebugInvokeReq
- * state. If set, we're in the process of invoking a method.
- */
-bool JdwpState::InvokeInProgress() {
- DebugInvokeReq* pReq = Dbg::GetInvokeReq();
- return pReq != nullptr;
-}
-
-void JdwpState::AcquireJdwpTokenForCommand() {
- CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread";
- SetWaitForJdwpToken(debug_thread_id_);
-}
-
-void JdwpState::ReleaseJdwpTokenForCommand() {
- CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread";
- ClearWaitForJdwpToken();
-}
-
-void JdwpState::AcquireJdwpTokenForEvent(ObjectId threadId) {
- SetWaitForJdwpToken(threadId);
-}
-
-void JdwpState::ReleaseJdwpTokenForEvent() {
- ClearWaitForJdwpToken();
-}
-
-/*
- * We need the JDWP thread to hold off on doing stuff while we post an
- * event and then suspend ourselves.
- *
- * This could go to sleep waiting for another thread, so it's important
- * that the thread be marked as VMWAIT before calling here.
- */
-void JdwpState::SetWaitForJdwpToken(ObjectId threadId) {
- bool waited = false;
- Thread* const self = Thread::Current();
- CHECK_NE(threadId, 0u);
- CHECK_NE(self->GetState(), kRunnable);
- Locks::mutator_lock_->AssertNotHeld(self);
-
- /* this is held for very brief periods; contention is unlikely */
- MutexLock mu(self, jdwp_token_lock_);
-
- if (jdwp_token_owner_thread_id_ == threadId) {
- // Only the debugger thread may already hold the event token. For instance, it may trigger
- // a CLASS_PREPARE event while processing a command that initializes a class.
- CHECK_EQ(threadId, debug_thread_id_) << "Non-debugger thread is already holding event token";
- } else {
- /*
- * If another thread is already doing stuff, wait for it. This can
- * go to sleep indefinitely.
- */
-
- while (jdwp_token_owner_thread_id_ != 0) {
- VLOG(jdwp) << StringPrintf("event in progress (%#" PRIx64 "), %#" PRIx64 " sleeping",
- jdwp_token_owner_thread_id_, threadId);
- waited = true;
- jdwp_token_cond_.Wait(self);
- }
-
- if (waited || threadId != debug_thread_id_) {
- VLOG(jdwp) << StringPrintf("event token grabbed (%#" PRIx64 ")", threadId);
- }
- jdwp_token_owner_thread_id_ = threadId;
- }
-}
-
-/*
- * Clear the threadId and signal anybody waiting.
- */
-void JdwpState::ClearWaitForJdwpToken() {
- /*
- * Grab the mutex. Don't try to go in/out of VMWAIT mode, as this
- * function is called by Dbg::SuspendSelf(), and the transition back
- * to RUNNING would confuse it.
- */
- Thread* const self = Thread::Current();
- MutexLock mu(self, jdwp_token_lock_);
-
- CHECK_NE(jdwp_token_owner_thread_id_, 0U);
- VLOG(jdwp) << StringPrintf("cleared event token (%#" PRIx64 ")", jdwp_token_owner_thread_id_);
-
- jdwp_token_owner_thread_id_ = 0;
- jdwp_token_cond_.Signal(self);
-}
-
-/*
- * Prep an event. Allocates storage for the message and leaves space for
- * the header.
- */
-static ExpandBuf* eventPrep() {
- ExpandBuf* pReq = expandBufAlloc();
- expandBufAddSpace(pReq, kJDWPHeaderLen);
- return pReq;
-}
-
-/*
- * Write the header into the buffer and send the packet off to the debugger.
- *
- * Takes ownership of "pReq" (currently discards it).
- */
-void JdwpState::EventFinish(ExpandBuf* pReq) {
- uint8_t* buf = expandBufGetBuffer(pReq);
-
- Set4BE(buf + kJDWPHeaderSizeOffset, expandBufGetLength(pReq));
- Set4BE(buf + kJDWPHeaderIdOffset, NextRequestSerial());
- Set1(buf + kJDWPHeaderFlagsOffset, 0); /* flags */
- Set1(buf + kJDWPHeaderCmdSetOffset, kJDWPEventCmdSet);
- Set1(buf + kJDWPHeaderCmdOffset, kJDWPEventCompositeCmd);
-
- SendRequest(pReq);
-
- expandBufFree(pReq);
-}
-
-
-/*
- * Tell the debugger that we have finished initializing. This is always
- * sent, even if the debugger hasn't requested it.
- *
- * This should be sent "before the main thread is started and before
- * any application code has been executed". The thread ID in the message
- * must be for the main thread.
- */
-void JdwpState::PostVMStart() {
- JdwpSuspendPolicy suspend_policy = (options_->suspend) ? SP_ALL : SP_NONE;
- ObjectId threadId = Dbg::GetThreadSelfId();
-
- VLOG(jdwp) << "EVENT: " << EK_VM_START;
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, 1);
- expandBufAdd1(pReq, EK_VM_START);
- expandBufAdd4BE(pReq, 0); /* requestId */
- expandBufAddObjectId(pReq, threadId);
-
- Dbg::ManageDeoptimization();
-
- /* send request and possibly suspend ourselves */
- SendRequestAndPossiblySuspend(pReq, suspend_policy, threadId);
-}
-
-static void LogMatchingEventsAndThread(const std::vector<JdwpEvent*>& match_list,
- ObjectId thread_id)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- for (size_t i = 0, e = match_list.size(); i < e; ++i) {
- JdwpEvent* pEvent = match_list[i];
- VLOG(jdwp) << "EVENT #" << i << ": " << pEvent->eventKind
- << StringPrintf(" (requestId=%#" PRIx32 ")", pEvent->requestId);
- }
- std::string thread_name;
- JdwpError error = Dbg::GetThreadName(thread_id, &thread_name);
- if (error != JDWP::ERR_NONE) {
- thread_name = "<unknown>";
- }
- VLOG(jdwp) << StringPrintf(" thread=%#" PRIx64, thread_id) << " " << thread_name;
-}
-
-static void SetJdwpLocationFromEventLocation(const JDWP::EventLocation* event_location,
- JDWP::JdwpLocation* jdwp_location)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(event_location != nullptr);
- DCHECK(jdwp_location != nullptr);
- Dbg::SetJdwpLocation(jdwp_location, event_location->method, event_location->dex_pc);
-}
-
-/*
- * A location of interest has been reached. This handles:
- * Breakpoint
- * SingleStep
- * MethodEntry
- * MethodExit
- * These four types must be grouped together in a single response. The
- * "eventFlags" indicates the type of event(s) that have happened.
- *
- * Valid mods:
- * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, InstanceOnly
- * LocationOnly (for breakpoint/step only)
- * Step (for step only)
- *
- * Interesting test cases:
- * - Put a breakpoint on a native method. Eclipse creates METHOD_ENTRY
- * and METHOD_EXIT events with a ClassOnly mod on the method's class.
- * - Use "run to line". Eclipse creates a BREAKPOINT with Count=1.
- * - Single-step to a line with a breakpoint. Should get a single
- * event message with both events in it.
- */
-void JdwpState::PostLocationEvent(const EventLocation* pLoc,
- ObjPtr<mirror::Object> thisPtr,
- int eventFlags,
- const JValue* returnValue) {
- DCHECK(pLoc != nullptr);
- DCHECK(pLoc->method != nullptr);
- DCHECK_EQ(pLoc->method->IsStatic(), thisPtr == nullptr);
-
- ModBasket basket(Thread::Current());
- basket.pLoc = pLoc;
- basket.locationClass.Assign(pLoc->method->GetDeclaringClass());
- basket.thisPtr.Assign(thisPtr);
- basket.className = Dbg::GetClassName(basket.locationClass.Get());
-
- /*
- * On rare occasions we may need to execute interpreted code in the VM
- * while handling a request from the debugger. Don't fire breakpoints
- * while doing so. (I don't think we currently do this at all, so
- * this is mostly paranoia.)
- */
- if (basket.thread == GetDebugThread()) {
- VLOG(jdwp) << "Ignoring location event in JDWP thread";
- return;
- }
-
- /*
- * The debugger variable display tab may invoke the interpreter to format
- * complex objects. We want to ignore breakpoints and method entry/exit
- * traps while working on behalf of the debugger.
- *
- * If we don't ignore them, the VM will get hung up, because we'll
- * suspend on a breakpoint while the debugger is still waiting for its
- * method invocation to complete.
- */
- if (InvokeInProgress()) {
- VLOG(jdwp) << "Not checking breakpoints during invoke (" << basket.className << ")";
- return;
- }
-
- std::vector<JdwpEvent*> match_list;
- {
- // We use the locked version because we have multiple possible match events.
- MutexLock mu(Thread::Current(), event_list_lock_);
- match_list.reserve(event_list_size_);
- if ((eventFlags & Dbg::kBreakpoint) != 0) {
- FindMatchingEventsLocked(EK_BREAKPOINT, basket, &match_list);
- }
- if ((eventFlags & Dbg::kSingleStep) != 0) {
- FindMatchingEventsLocked(EK_SINGLE_STEP, basket, &match_list);
- }
- if ((eventFlags & Dbg::kMethodEntry) != 0) {
- FindMatchingEventsLocked(EK_METHOD_ENTRY, basket, &match_list);
- }
- if ((eventFlags & Dbg::kMethodExit) != 0) {
- FindMatchingEventsLocked(EK_METHOD_EXIT, basket, &match_list);
- FindMatchingEventsLocked(EK_METHOD_EXIT_WITH_RETURN_VALUE, basket, &match_list);
- }
- }
- if (match_list.empty()) {
- // No matching event.
- return;
- }
- JdwpSuspendPolicy suspend_policy = ScanSuspendPolicy(match_list);
-
- ObjectId thread_id = Dbg::GetThreadId(basket.thread);
- JDWP::JdwpLocation jdwp_location;
- SetJdwpLocationFromEventLocation(pLoc, &jdwp_location);
-
- if (VLOG_IS_ON(jdwp)) {
- LogMatchingEventsAndThread(match_list, thread_id);
- VLOG(jdwp) << " location=" << jdwp_location;
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
- }
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, match_list.size());
-
- for (const JdwpEvent* pEvent : match_list) {
- expandBufAdd1(pReq, pEvent->eventKind);
- expandBufAdd4BE(pReq, pEvent->requestId);
- expandBufAddObjectId(pReq, thread_id);
- expandBufAddLocation(pReq, jdwp_location);
- if (pEvent->eventKind == EK_METHOD_EXIT_WITH_RETURN_VALUE) {
- Dbg::OutputMethodReturnValue(jdwp_location.method_id, returnValue, pReq);
- }
- }
-
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CleanupMatchList(match_list);
- }
-
- Dbg::ManageDeoptimization();
-
- SendRequestAndPossiblySuspend(pReq, suspend_policy, thread_id);
-}
-
-void JdwpState::PostFieldEvent(const EventLocation* pLoc,
- ArtField* field,
- ObjPtr<mirror::Object> this_object,
- const JValue* fieldValue,
- bool is_modification) {
- DCHECK(pLoc != nullptr);
- DCHECK(field != nullptr);
- DCHECK_EQ(fieldValue != nullptr, is_modification);
- DCHECK_EQ(field->IsStatic(), this_object == nullptr);
-
- ModBasket basket(Thread::Current());
- basket.pLoc = pLoc;
- basket.locationClass.Assign(pLoc->method->GetDeclaringClass());
- basket.thisPtr.Assign(this_object);
- basket.className = Dbg::GetClassName(basket.locationClass.Get());
- basket.field = field;
-
- if (InvokeInProgress()) {
- VLOG(jdwp) << "Not posting field event during invoke (" << basket.className << ")";
- return;
- }
-
- std::vector<JdwpEvent*> match_list;
- const JdwpEventKind match_kind = (is_modification) ? EK_FIELD_MODIFICATION : EK_FIELD_ACCESS;
- if (!FindMatchingEvents(match_kind, basket, &match_list)) {
- // No matching event.
- return;
- }
-
- JdwpSuspendPolicy suspend_policy = ScanSuspendPolicy(match_list);
- ObjectId thread_id = Dbg::GetThreadId(basket.thread);
- ObjectRegistry* registry = Dbg::GetObjectRegistry();
- ObjectId instance_id = registry->Add(basket.thisPtr);
- RefTypeId field_type_id = registry->AddRefType(field->GetDeclaringClass());
- FieldId field_id = Dbg::ToFieldId(field);
- JDWP::JdwpLocation jdwp_location;
- SetJdwpLocationFromEventLocation(pLoc, &jdwp_location);
-
- if (VLOG_IS_ON(jdwp)) {
- LogMatchingEventsAndThread(match_list, thread_id);
- VLOG(jdwp) << " location=" << jdwp_location;
- VLOG(jdwp) << StringPrintf(" this=%#" PRIx64, instance_id);
- VLOG(jdwp) << StringPrintf(" type=%#" PRIx64, field_type_id) << " "
- << Dbg::GetClassName(field_id);
- VLOG(jdwp) << StringPrintf(" field=%#" PRIx64, field_id) << " "
- << Dbg::GetFieldName(field_id);
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
- }
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, match_list.size());
-
- // Get field's reference type tag.
- JDWP::JdwpTypeTag type_tag = Dbg::GetTypeTag(field->GetDeclaringClass());
-
- // Get instance type tag.
- uint8_t tag;
- {
- ScopedObjectAccessUnchecked soa(Thread::Current());
- tag = Dbg::TagFromObject(soa, basket.thisPtr.Get());
- }
-
- for (const JdwpEvent* pEvent : match_list) {
- expandBufAdd1(pReq, pEvent->eventKind);
- expandBufAdd4BE(pReq, pEvent->requestId);
- expandBufAddObjectId(pReq, thread_id);
- expandBufAddLocation(pReq, jdwp_location);
- expandBufAdd1(pReq, type_tag);
- expandBufAddRefTypeId(pReq, field_type_id);
- expandBufAddFieldId(pReq, field_id);
- expandBufAdd1(pReq, tag);
- expandBufAddObjectId(pReq, instance_id);
- if (is_modification) {
- Dbg::OutputFieldValue(field_id, fieldValue, pReq);
- }
- }
-
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CleanupMatchList(match_list);
- }
-
- Dbg::ManageDeoptimization();
-
- SendRequestAndPossiblySuspend(pReq, suspend_policy, thread_id);
-}
-
-/*
- * A thread is starting or stopping.
- *
- * Valid mods:
- * Count, ThreadOnly
- */
-void JdwpState::PostThreadChange(Thread* thread, bool start) {
- CHECK_EQ(thread, Thread::Current());
-
- /*
- * I don't think this can happen.
- */
- if (InvokeInProgress()) {
- LOG(WARNING) << "Not posting thread change during invoke";
- return;
- }
-
- // We need the java.lang.Thread object associated to the starting/ending
- // thread to get its JDWP id. Therefore we can't report event if there
- // is no Java peer. This happens when the runtime shuts down and re-attaches
- // the current thread without creating a Java peer.
- if (thread->GetPeer() == nullptr) {
- return;
- }
-
- ModBasket basket(thread);
-
- std::vector<JdwpEvent*> match_list;
- const JdwpEventKind match_kind = (start) ? EK_THREAD_START : EK_THREAD_DEATH;
- if (!FindMatchingEvents(match_kind, basket, &match_list)) {
- // No matching event.
- return;
- }
-
- JdwpSuspendPolicy suspend_policy = ScanSuspendPolicy(match_list);
- ObjectId thread_id = Dbg::GetThreadId(basket.thread);
-
- if (VLOG_IS_ON(jdwp)) {
- LogMatchingEventsAndThread(match_list, thread_id);
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
- }
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, match_list.size());
-
- for (const JdwpEvent* pEvent : match_list) {
- expandBufAdd1(pReq, pEvent->eventKind);
- expandBufAdd4BE(pReq, pEvent->requestId);
- expandBufAdd8BE(pReq, thread_id);
- }
-
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CleanupMatchList(match_list);
- }
-
- Dbg::ManageDeoptimization();
-
- SendRequestAndPossiblySuspend(pReq, suspend_policy, thread_id);
-}
-
-/*
- * Send a polite "VM is dying" message to the debugger.
- *
- * Skips the usual "event token" stuff.
- */
-bool JdwpState::PostVMDeath() {
- VLOG(jdwp) << "EVENT: " << EK_VM_DEATH;
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, SP_NONE);
- expandBufAdd4BE(pReq, 1);
-
- expandBufAdd1(pReq, EK_VM_DEATH);
- expandBufAdd4BE(pReq, 0);
- EventFinish(pReq);
- return true;
-}
-
-/*
- * An exception has been thrown. It may or may not have been caught.
- *
- * Valid mods:
- * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly,
- * ExceptionOnly, InstanceOnly
- *
- * The "exceptionId" has not been added to the GC-visible object registry,
- * because there's a pretty good chance that we're not going to send it
- * up the debugger.
- */
-void JdwpState::PostException(const EventLocation* pThrowLoc,
- ObjPtr<mirror::Throwable> exception_object,
- const EventLocation* pCatchLoc,
- ObjPtr<mirror::Object> thisPtr) {
- DCHECK(exception_object != nullptr);
- DCHECK(pThrowLoc != nullptr);
- DCHECK(pCatchLoc != nullptr);
- if (pThrowLoc->method != nullptr) {
- DCHECK_EQ(pThrowLoc->method->IsStatic(), thisPtr == nullptr);
- } else {
- VLOG(jdwp) << "Unexpected: exception event with empty throw location";
- }
-
- ModBasket basket(Thread::Current());
- basket.pLoc = pThrowLoc;
- if (pThrowLoc->method != nullptr) {
- basket.locationClass.Assign(pThrowLoc->method->GetDeclaringClass());
- }
- basket.className = Dbg::GetClassName(basket.locationClass.Get());
- basket.exceptionClass.Assign(exception_object->GetClass());
- basket.caught = (pCatchLoc->method != nullptr);
- basket.thisPtr.Assign(thisPtr);
-
- /* don't try to post an exception caused by the debugger */
- if (InvokeInProgress()) {
- VLOG(jdwp) << "Not posting exception hit during invoke (" << basket.className << ")";
- return;
- }
-
- std::vector<JdwpEvent*> match_list;
- if (!FindMatchingEvents(EK_EXCEPTION, basket, &match_list)) {
- // No matching event.
- return;
- }
-
- JdwpSuspendPolicy suspend_policy = ScanSuspendPolicy(match_list);
- ObjectId thread_id = Dbg::GetThreadId(basket.thread);
- ObjectRegistry* registry = Dbg::GetObjectRegistry();
- ObjectId exceptionId = registry->Add(exception_object);
- JDWP::JdwpLocation jdwp_throw_location;
- JDWP::JdwpLocation jdwp_catch_location;
- SetJdwpLocationFromEventLocation(pThrowLoc, &jdwp_throw_location);
- SetJdwpLocationFromEventLocation(pCatchLoc, &jdwp_catch_location);
-
- if (VLOG_IS_ON(jdwp)) {
- std::string exceptionClassName(mirror::Class::PrettyDescriptor(exception_object->GetClass()));
-
- LogMatchingEventsAndThread(match_list, thread_id);
- VLOG(jdwp) << " throwLocation=" << jdwp_throw_location;
- if (jdwp_catch_location.class_id == 0) {
- VLOG(jdwp) << " catchLocation=uncaught";
- } else {
- VLOG(jdwp) << " catchLocation=" << jdwp_catch_location;
- }
- VLOG(jdwp) << StringPrintf(" exception=%#" PRIx64, exceptionId) << " "
- << exceptionClassName;
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
- }
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, match_list.size());
-
- for (const JdwpEvent* pEvent : match_list) {
- expandBufAdd1(pReq, pEvent->eventKind);
- expandBufAdd4BE(pReq, pEvent->requestId);
- expandBufAddObjectId(pReq, thread_id);
- expandBufAddLocation(pReq, jdwp_throw_location);
- expandBufAdd1(pReq, JT_OBJECT);
- expandBufAddObjectId(pReq, exceptionId);
- expandBufAddLocation(pReq, jdwp_catch_location);
- }
-
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CleanupMatchList(match_list);
- }
-
- Dbg::ManageDeoptimization();
-
- SendRequestAndPossiblySuspend(pReq, suspend_policy, thread_id);
-}
-
-/*
- * Announce that a class has been loaded.
- *
- * Valid mods:
- * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude
- */
-void JdwpState::PostClassPrepare(ObjPtr<mirror::Class> klass) {
- DCHECK(klass != nullptr);
-
- ModBasket basket(Thread::Current());
- basket.locationClass.Assign(klass);
- basket.className = Dbg::GetClassName(basket.locationClass.Get());
-
- /* suppress class prep caused by debugger */
- if (InvokeInProgress()) {
- VLOG(jdwp) << "Not posting class prep caused by invoke (" << basket.className << ")";
- return;
- }
-
- std::vector<JdwpEvent*> match_list;
- if (!FindMatchingEvents(EK_CLASS_PREPARE, basket, &match_list)) {
- // No matching event.
- return;
- }
-
- JdwpSuspendPolicy suspend_policy = ScanSuspendPolicy(match_list);
- ObjectId thread_id = Dbg::GetThreadId(basket.thread);
- ObjectRegistry* registry = Dbg::GetObjectRegistry();
- RefTypeId class_id = registry->AddRefType(basket.locationClass);
-
- // OLD-TODO - we currently always send both "verified" and "prepared" since
- // debuggers seem to like that. There might be some advantage to honesty,
- // since the class may not yet be verified.
- int status = JDWP::CS_VERIFIED | JDWP::CS_PREPARED;
- JDWP::JdwpTypeTag tag = Dbg::GetTypeTag(basket.locationClass.Get());
- std::string temp;
- std::string signature(basket.locationClass->GetDescriptor(&temp));
-
- if (VLOG_IS_ON(jdwp)) {
- LogMatchingEventsAndThread(match_list, thread_id);
- VLOG(jdwp) << StringPrintf(" type=%#" PRIx64, class_id) << " " << signature;
- VLOG(jdwp) << " suspend_policy=" << suspend_policy;
- }
-
- ObjectId reported_thread_id = thread_id;
- if (reported_thread_id == debug_thread_id_) {
- /*
- * JDWP says that, for a class prep in the debugger thread, we
- * should set thread to null and if any threads were supposed
- * to be suspended then we suspend all other threads.
- */
- VLOG(jdwp) << " NOTE: class prepare in debugger thread!";
- reported_thread_id = 0;
- if (suspend_policy == SP_EVENT_THREAD) {
- suspend_policy = SP_ALL;
- }
- }
-
- ExpandBuf* pReq = eventPrep();
- expandBufAdd1(pReq, suspend_policy);
- expandBufAdd4BE(pReq, match_list.size());
-
- for (const JdwpEvent* pEvent : match_list) {
- expandBufAdd1(pReq, pEvent->eventKind);
- expandBufAdd4BE(pReq, pEvent->requestId);
- expandBufAddObjectId(pReq, reported_thread_id);
- expandBufAdd1(pReq, tag);
- expandBufAddRefTypeId(pReq, class_id);
- expandBufAddUtf8String(pReq, signature);
- expandBufAdd4BE(pReq, status);
- }
-
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CleanupMatchList(match_list);
- }
-
- Dbg::ManageDeoptimization();
-
- SendRequestAndPossiblySuspend(pReq, suspend_policy, thread_id);
-}
-
-/*
- * Setup the header for a chunk of DDM data.
- */
-void JdwpState::SetupChunkHeader(uint32_t type, size_t data_len, size_t header_size,
- uint8_t* out_header) {
- CHECK_EQ(header_size, static_cast<size_t>(kJDWPHeaderLen + 8));
- /* form the header (JDWP plus DDMS) */
- Set4BE(out_header, header_size + data_len);
- Set4BE(out_header + 4, NextRequestSerial());
- Set1(out_header + 8, 0); /* flags */
- Set1(out_header + 9, kJDWPDdmCmdSet);
- Set1(out_header + 10, kJDWPDdmCmd);
- Set4BE(out_header + 11, type);
- Set4BE(out_header + 15, data_len);
-}
-
-/*
- * Send up a chunk of DDM data.
- *
- * While this takes the form of a JDWP "event", it doesn't interact with
- * other debugger traffic, and can't suspend the VM, so we skip all of
- * the fun event token gymnastics.
- */
-void JdwpState::DdmSendChunkV(uint32_t type, const iovec* iov, int iov_count) {
- uint8_t header[kJDWPHeaderLen + 8] = { 0 };
- size_t dataLen = 0;
-
- CHECK(iov != nullptr);
- CHECK_GT(iov_count, 0);
- CHECK_LT(iov_count, 10);
-
- /*
- * "Wrap" the contents of the iovec with a JDWP/DDMS header. We do
- * this by creating a new copy of the vector with space for the header.
- */
- std::vector<iovec> wrapiov;
- wrapiov.push_back(iovec());
- for (int i = 0; i < iov_count; i++) {
- wrapiov.push_back(iov[i]);
- dataLen += iov[i].iov_len;
- }
-
- SetupChunkHeader(type, dataLen, sizeof(header), header);
-
- wrapiov[0].iov_base = header;
- wrapiov[0].iov_len = sizeof(header);
-
- // Try to avoid blocking GC during a send, but only safe when not using mutexes at a lower-level
- // than mutator for lock ordering reasons.
- Thread* self = Thread::Current();
- bool safe_to_release_mutator_lock_over_send = !Locks::mutator_lock_->IsExclusiveHeld(self);
- if (safe_to_release_mutator_lock_over_send) {
- for (size_t i = 0; i < kMutatorLock; ++i) {
- if (self->GetHeldMutex(static_cast<LockLevel>(i)) != nullptr) {
- safe_to_release_mutator_lock_over_send = false;
- break;
- }
- }
- }
- if (safe_to_release_mutator_lock_over_send) {
- // Change state to waiting to allow GC, ... while we're sending.
- ScopedThreadSuspension sts(self, kWaitingForDebuggerSend);
- SendBufferedRequest(type, wrapiov);
- } else {
- // Send and possibly block GC...
- SendBufferedRequest(type, wrapiov);
- }
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_event.h b/runtime/jdwp/jdwp_event.h
deleted file mode 100644
index d2697619999..00000000000
--- a/runtime/jdwp/jdwp_event.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * Handle registration of events, and debugger event notification.
- */
-#ifndef ART_RUNTIME_JDWP_JDWP_EVENT_H_
-#define ART_RUNTIME_JDWP_JDWP_EVENT_H_
-
-#include "jdwp/jdwp.h"
-#include "jdwp/jdwp_constants.h"
-#include "jdwp/jdwp_expand_buf.h"
-
-namespace art {
-
-namespace JDWP {
-
-/*
- * Event modifiers. A JdwpEvent may have zero or more of these.
- */
-union JdwpEventMod {
- JdwpModKind modKind;
- struct {
- JdwpModKind modKind;
- int count;
- } count;
- struct {
- JdwpModKind modKind;
- uint32_t exprId;
- } conditional;
- struct {
- JdwpModKind modKind;
- ObjectId threadId;
- } threadOnly;
- struct {
- JdwpModKind modKind;
- RefTypeId refTypeId;
- } classOnly;
- struct {
- JdwpModKind modKind;
- char* classPattern;
- } classMatch;
- struct {
- JdwpModKind modKind;
- char* classPattern;
- } classExclude;
- struct {
- JdwpModKind modKind;
- JdwpLocation loc;
- } locationOnly;
- struct {
- JdwpModKind modKind;
- uint8_t caught;
- uint8_t uncaught;
- RefTypeId refTypeId;
- } exceptionOnly;
- struct {
- JdwpModKind modKind;
- RefTypeId refTypeId;
- FieldId fieldId;
- } fieldOnly;
- struct {
- JdwpModKind modKind;
- ObjectId threadId;
- int size; /* JdwpStepSize */
- int depth; /* JdwpStepDepth */
- } step;
- struct {
- JdwpModKind modKind;
- ObjectId objectId;
- } instanceOnly;
-};
-
-/*
- * One of these for every registered event.
- *
- * We over-allocate the struct to hold the modifiers.
- */
-struct JdwpEvent {
- JdwpEvent* prev; /* linked list */
- JdwpEvent* next;
-
- JdwpEventKind eventKind; /* what kind of event is this? */
- JdwpSuspendPolicy suspend_policy; /* suspend all, none, or self? */
- int modCount; /* #of entries in mods[] */
- uint32_t requestId; /* serial#, reported to debugger */
-
- JdwpEventMod mods[1]; /* MUST be last field in struct */
-};
-
-/*
- * Allocate an event structure with enough space.
- */
-JdwpEvent* EventAlloc(int numMods);
-void EventFree(JdwpEvent* pEvent);
-
-} // namespace JDWP
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_JDWP_EVENT_H_
diff --git a/runtime/jdwp/jdwp_expand_buf.cc b/runtime/jdwp/jdwp_expand_buf.cc
deleted file mode 100644
index 4b4ca0e4a30..00000000000
--- a/runtime/jdwp/jdwp_expand_buf.cc
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * Implementation of an expandable byte buffer. Designed for serializing
- * primitive values, e.g. JDWP replies.
- */
-
-#include "jdwp/jdwp_expand_buf.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <android-base/logging.h>
-
-#include "jdwp/jdwp.h"
-#include "jdwp/jdwp_bits.h"
-
-namespace art {
-
-namespace JDWP {
-
-/*
- * Data structure used to track buffer use.
- */
-struct ExpandBuf {
- uint8_t* storage;
- int curLen;
- int maxLen;
-};
-
-#define kInitialStorage 64
-
-/*
- * Allocate a JdwpBuf and some initial storage.
- */
-ExpandBuf* expandBufAlloc() {
- ExpandBuf* newBuf = new ExpandBuf;
- newBuf->storage = reinterpret_cast<uint8_t*>(malloc(kInitialStorage));
- newBuf->curLen = 0;
- newBuf->maxLen = kInitialStorage;
- return newBuf;
-}
-
-/*
- * Free a JdwpBuf and associated storage.
- */
-void expandBufFree(ExpandBuf* pBuf) {
- if (pBuf == nullptr) {
- return;
- }
-
- free(pBuf->storage);
- delete pBuf;
-}
-
-/*
- * Get a pointer to the start of the buffer.
- */
-uint8_t* expandBufGetBuffer(ExpandBuf* pBuf) {
- return pBuf->storage;
-}
-
-/*
- * Get the amount of data currently in the buffer.
- */
-size_t expandBufGetLength(ExpandBuf* pBuf) {
- return pBuf->curLen;
-}
-
-/*
- * Ensure that the buffer has enough space to hold incoming data. If it
- * doesn't, resize the buffer.
- */
-static void ensureSpace(ExpandBuf* pBuf, int newCount) {
- if (pBuf->curLen + newCount <= pBuf->maxLen) {
- return;
- }
-
- while (pBuf->curLen + newCount > pBuf->maxLen) {
- pBuf->maxLen *= 2;
- }
-
- uint8_t* newPtr = reinterpret_cast<uint8_t*>(realloc(pBuf->storage, pBuf->maxLen));
- if (newPtr == nullptr) {
- LOG(FATAL) << "realloc(" << pBuf->maxLen << ") failed";
- }
-
- pBuf->storage = newPtr;
-}
-
-/*
- * Allocate some space in the buffer.
- */
-uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize) {
- uint8_t* gapStart;
-
- ensureSpace(pBuf, gapSize);
- gapStart = pBuf->storage + pBuf->curLen;
- /* do we want to garbage-fill the gap for debugging? */
- pBuf->curLen += gapSize;
-
- return gapStart;
-}
-
-/*
- * Append a byte.
- */
-void expandBufAdd1(ExpandBuf* pBuf, uint8_t val) {
- ensureSpace(pBuf, sizeof(val));
- *(pBuf->storage + pBuf->curLen) = val;
- pBuf->curLen++;
-}
-
-/*
- * Append two big-endian bytes.
- */
-void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val) {
- ensureSpace(pBuf, sizeof(val));
- Set2BE(pBuf->storage + pBuf->curLen, val);
- pBuf->curLen += sizeof(val);
-}
-
-/*
- * Append four big-endian bytes.
- */
-void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val) {
- ensureSpace(pBuf, sizeof(val));
- Set4BE(pBuf->storage + pBuf->curLen, val);
- pBuf->curLen += sizeof(val);
-}
-
-/*
- * Append eight big-endian bytes.
- */
-void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t val) {
- ensureSpace(pBuf, sizeof(val));
- Set8BE(pBuf->storage + pBuf->curLen, val);
- pBuf->curLen += sizeof(val);
-}
-
-static void SetUtf8String(uint8_t* buf, const char* str, size_t strLen) {
- Set4BE(buf, strLen);
- if (str != nullptr) {
- memcpy(buf + sizeof(uint32_t), str, strLen);
- }
-}
-
-/*
- * Add a UTF8 string as a 4-byte length followed by a non-nullptr-terminated
- * string.
- *
- * Because these strings are coming out of the VM, it's safe to assume that
- * they can be null-terminated (either they don't have null bytes or they
- * have stored null bytes in a multi-byte encoding).
- */
-void expandBufAddUtf8String(ExpandBuf* pBuf, const char* s) {
- int strLen = (s != nullptr ? strlen(s) : 0);
- ensureSpace(pBuf, sizeof(uint32_t) + strLen);
- SetUtf8String(pBuf->storage + pBuf->curLen, s, strLen);
- pBuf->curLen += sizeof(uint32_t) + strLen;
-}
-
-void expandBufAddUtf8String(ExpandBuf* pBuf, const std::string& s) {
- ensureSpace(pBuf, sizeof(uint32_t) + s.size());
- SetUtf8String(pBuf->storage + pBuf->curLen, s.data(), s.size());
- pBuf->curLen += sizeof(uint32_t) + s.size();
-}
-
-void expandBufAddLocation(ExpandBuf* buf, const JdwpLocation& location) {
- expandBufAdd1(buf, location.type_tag);
- expandBufAddObjectId(buf, location.class_id);
- expandBufAddMethodId(buf, location.method_id);
- expandBufAdd8BE(buf, location.dex_pc);
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_expand_buf.h b/runtime/jdwp/jdwp_expand_buf.h
deleted file mode 100644
index 81e01e2100b..00000000000
--- a/runtime/jdwp/jdwp_expand_buf.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * Expanding byte buffer, with primitives for appending basic data types.
- */
-#ifndef ART_RUNTIME_JDWP_JDWP_EXPAND_BUF_H_
-#define ART_RUNTIME_JDWP_JDWP_EXPAND_BUF_H_
-
-#include <string>
-
-#include <stddef.h>
-#include <stdint.h>
-
-namespace art {
-
-namespace JDWP {
-
-struct ExpandBuf; /* private */
-struct JdwpLocation;
-
-/* create a new struct */
-ExpandBuf* expandBufAlloc();
-/* free storage */
-void expandBufFree(ExpandBuf* pBuf);
-
-/*
- * Accessors. The buffer pointer and length will only be valid until more
- * data is added.
- */
-uint8_t* expandBufGetBuffer(ExpandBuf* pBuf);
-size_t expandBufGetLength(ExpandBuf* pBuf);
-
-/*
- * The "add" operations allocate additional storage and append the data.
- *
- * There are no "get" operations included with this "class", other than
- * GetBuffer(). If you want to get or set data from a position other
- * than the end, get a pointer to the buffer and use the inline functions
- * defined elsewhere.
- *
- * expandBufAddSpace() returns a pointer to the *start* of the region
- * added.
- */
-uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize);
-void expandBufAdd1(ExpandBuf* pBuf, uint8_t val);
-void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val);
-void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val);
-void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t val);
-void expandBufAddUtf8String(ExpandBuf* pBuf, const char* s);
-void expandBufAddUtf8String(ExpandBuf* pBuf, const std::string& s);
-void expandBufAddLocation(ExpandBuf* pReply, const JdwpLocation& location);
-
-} // namespace JDWP
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_JDWP_EXPAND_BUF_H_
diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc
deleted file mode 100644
index 37365ff9b59..00000000000
--- a/runtime/jdwp/jdwp_handler.cc
+++ /dev/null
@@ -1,1714 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <memory>
-#include <string>
-
-#include "android-base/stringprintf.h"
-
-#include "base/atomic.h"
-#include "base/hex_dump.h"
-#include "base/logging.h" // For VLOG.
-#include "base/macros.h"
-#include "debugger.h"
-#include "dex/utf.h"
-#include "jdwp/jdwp_constants.h"
-#include "jdwp/jdwp_event.h"
-#include "jdwp/jdwp_expand_buf.h"
-#include "jdwp/jdwp_priv.h"
-#include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-current-inl.h"
-
-namespace art {
-
-namespace JDWP {
-
-using android::base::StringPrintf;
-
-std::string DescribeField(const FieldId& field_id) {
- return StringPrintf("%#" PRIx64 " (%s)", field_id, Dbg::GetFieldName(field_id).c_str());
-}
-
-std::string DescribeMethod(const MethodId& method_id) {
- return StringPrintf("%#" PRIx64 " (%s)", method_id, Dbg::GetMethodName(method_id).c_str());
-}
-
-std::string DescribeRefTypeId(const RefTypeId& ref_type_id) {
- std::string signature("unknown");
- Dbg::GetSignature(ref_type_id, &signature);
- return StringPrintf("%#" PRIx64 " (%s)", ref_type_id, signature.c_str());
-}
-
-static JdwpError WriteTaggedObject(ExpandBuf* reply, ObjectId object_id)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint8_t tag;
- JdwpError rc = Dbg::GetObjectTag(object_id, &tag);
- if (rc == ERR_NONE) {
- expandBufAdd1(reply, tag);
- expandBufAddObjectId(reply, object_id);
- }
- return rc;
-}
-
-static JdwpError WriteTaggedObjectList(ExpandBuf* reply, const std::vector<ObjectId>& objects)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- expandBufAdd4BE(reply, objects.size());
- for (size_t i = 0; i < objects.size(); ++i) {
- JdwpError rc = WriteTaggedObject(reply, objects[i]);
- if (rc != ERR_NONE) {
- return rc;
- }
- }
- return ERR_NONE;
-}
-
-/*
- * Common code for *_InvokeMethod requests.
- *
- * If "is_constructor" is set, this returns "object_id" rather than the
- * expected-to-be-void return value of the called function.
- */
-static JdwpError RequestInvoke(JdwpState*, Request* request,
- ObjectId thread_id, ObjectId object_id,
- RefTypeId class_id, MethodId method_id, bool is_constructor)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK(!is_constructor || object_id != 0);
-
- int32_t arg_count = request->ReadSigned32("argument count");
-
- VLOG(jdwp) << StringPrintf(" --> thread_id=%#" PRIx64 " object_id=%#" PRIx64,
- thread_id, object_id);
- VLOG(jdwp) << StringPrintf(" class_id=%#" PRIx64 " method_id=%#" PRIx64 " %s.%s",
- class_id, method_id, Dbg::GetClassName(class_id).c_str(),
- Dbg::GetMethodName(method_id).c_str());
- VLOG(jdwp) << StringPrintf(" %d args:", arg_count);
-
- std::unique_ptr<JdwpTag[]> argTypes(arg_count > 0 ? new JdwpTag[arg_count] : nullptr);
- std::unique_ptr<uint64_t[]> argValues(arg_count > 0 ? new uint64_t[arg_count] : nullptr);
- for (int32_t i = 0; i < arg_count; ++i) {
- argTypes[i] = request->ReadTag();
- size_t width = Dbg::GetTagWidth(argTypes[i]);
- argValues[i] = request->ReadValue(width);
- VLOG(jdwp) << " " << argTypes[i] << StringPrintf("(%zd): %#" PRIx64, width,
- argValues[i]);
- }
-
- uint32_t options = request->ReadUnsigned32("InvokeOptions bit flags");
- VLOG(jdwp) << StringPrintf(" options=0x%04x%s%s", options,
- (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "",
- (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
-
- JDWP::JdwpError error = Dbg::PrepareInvokeMethod(request->GetId(), thread_id, object_id,
- class_id, method_id, arg_count,
- argValues.get(), argTypes.get(), options);
- if (error == JDWP::ERR_NONE) {
- // We successfully requested the invoke. The event thread now owns the arguments array in its
- // DebugInvokeReq mailbox.
- argValues.release();
- }
- return error;
-}
-
-static JdwpError VM_Version(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Text information on runtime version.
- std::string version(StringPrintf("Android Runtime %s", Runtime::Current()->GetVersion()));
- expandBufAddUtf8String(pReply, version);
-
- // JDWP version numbers, major and minor.
- expandBufAdd4BE(pReply, 1);
- expandBufAdd4BE(pReply, 6);
-
- // "java.version".
- expandBufAddUtf8String(pReply, "1.6.0");
-
- // "java.vm.name".
- expandBufAddUtf8String(pReply, "Dalvik");
-
- return ERR_NONE;
-}
-
-/*
- * Given a class JNI signature (e.g. "Ljava/lang/Error;"), return the
- * referenceTypeID. We need to send back more than one if the class has
- * been loaded by multiple class loaders.
- */
-static JdwpError VM_ClassesBySignature(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::string classDescriptor(request->ReadUtf8String());
-
- std::vector<RefTypeId> ids;
- Dbg::FindLoadedClassBySignature(classDescriptor.c_str(), &ids);
-
- expandBufAdd4BE(pReply, ids.size());
-
- for (size_t i = 0; i < ids.size(); ++i) {
- // Get class vs. interface and status flags.
- JDWP::JdwpTypeTag type_tag;
- uint32_t class_status;
- JDWP::JdwpError status = Dbg::GetClassInfo(ids[i], &type_tag, &class_status, nullptr);
- if (status != ERR_NONE) {
- return status;
- }
-
- expandBufAdd1(pReply, type_tag);
- expandBufAddRefTypeId(pReply, ids[i]);
- expandBufAdd4BE(pReply, class_status);
- }
-
- return ERR_NONE;
-}
-
-/*
- * Handle request for the thread IDs of all running threads.
- *
- * We exclude ourselves from the list, because we don't allow ourselves
- * to be suspended, and that violates some JDWP expectations.
- */
-static JdwpError VM_AllThreads(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::vector<ObjectId> thread_ids;
- Dbg::GetThreads(nullptr /* all thread groups */, &thread_ids);
-
- expandBufAdd4BE(pReply, thread_ids.size());
- for (uint32_t i = 0; i < thread_ids.size(); ++i) {
- expandBufAddObjectId(pReply, thread_ids[i]);
- }
-
- return ERR_NONE;
-}
-
-/*
- * List all thread groups that do not have a parent.
- */
-static JdwpError VM_TopLevelThreadGroups(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- /*
- * TODO: maintain a list of parentless thread groups in the VM.
- *
- * For now, just return "system". Application threads are created
- * in "main", which is a child of "system".
- */
- uint32_t groups = 1;
- expandBufAdd4BE(pReply, groups);
- ObjectId thread_group_id = Dbg::GetSystemThreadGroupId();
- expandBufAddObjectId(pReply, thread_group_id);
-
- return ERR_NONE;
-}
-
-/*
- * Respond with the sizes of the basic debugger types.
- */
-static JdwpError VM_IDSizes(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- expandBufAdd4BE(pReply, sizeof(FieldId));
- expandBufAdd4BE(pReply, sizeof(MethodId));
- expandBufAdd4BE(pReply, sizeof(ObjectId));
- expandBufAdd4BE(pReply, sizeof(RefTypeId));
- expandBufAdd4BE(pReply, sizeof(FrameId));
- return ERR_NONE;
-}
-
-static JdwpError VM_Dispose(JdwpState*, Request*, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Dbg::Dispose();
- return ERR_NONE;
-}
-
-/*
- * Suspend the execution of the application running in the VM (i.e. suspend
- * all threads).
- *
- * This needs to increment the "suspend count" on all threads.
- */
-static JdwpError VM_Suspend(JdwpState*, Request*, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Thread* self = Thread::Current();
- ScopedThreadSuspension sts(self, kWaitingForDebuggerSuspension);
- Dbg::SuspendVM();
- return ERR_NONE;
-}
-
-/*
- * Resume execution. Decrements the "suspend count" of all threads.
- */
-static JdwpError VM_Resume(JdwpState*, Request*, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Dbg::ResumeVM();
- return ERR_NONE;
-}
-
-static JdwpError VM_Exit(JdwpState* state, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t exit_status = request->ReadUnsigned32("exit_status");
- state->ExitAfterReplying(exit_status);
- return ERR_NONE;
-}
-
-/*
- * Create a new string in the VM and return its ID.
- *
- * (Ctrl-Shift-I in Eclipse on an array of objects causes it to create the
- * string "java.util.Arrays".)
- */
-static JdwpError VM_CreateString(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::string str(request->ReadUtf8String());
- ObjectId string_id;
- JdwpError status = Dbg::CreateString(str, &string_id);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAddObjectId(pReply, string_id);
- return ERR_NONE;
-}
-
-static JdwpError VM_ClassPaths(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- expandBufAddUtf8String(pReply, "/");
-
- std::vector<std::string> class_path;
- Split(Runtime::Current()->GetClassPathString(), ':', &class_path);
- expandBufAdd4BE(pReply, class_path.size());
- for (const std::string& str : class_path) {
- expandBufAddUtf8String(pReply, str);
- }
-
- std::vector<std::string> boot_class_path = Runtime::Current()->GetBootClassPath();
- expandBufAdd4BE(pReply, boot_class_path.size());
- for (const std::string& str : boot_class_path) {
- expandBufAddUtf8String(pReply, str);
- }
-
- return ERR_NONE;
-}
-
-static JdwpError VM_DisposeObjects(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- size_t object_count = request->ReadUnsigned32("object_count");
- for (size_t i = 0; i < object_count; ++i) {
- ObjectId object_id = request->ReadObjectId();
- uint32_t reference_count = request->ReadUnsigned32("reference_count");
- Dbg::DisposeObject(object_id, reference_count);
- }
- return ERR_NONE;
-}
-
-static JdwpError VM_Capabilities(JdwpState*, Request*, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- expandBufAdd1(reply, true); // canWatchFieldModification
- expandBufAdd1(reply, true); // canWatchFieldAccess
- expandBufAdd1(reply, true); // canGetBytecodes
- expandBufAdd1(reply, true); // canGetSyntheticAttribute
- expandBufAdd1(reply, true); // canGetOwnedMonitorInfo
- expandBufAdd1(reply, true); // canGetCurrentContendedMonitor
- expandBufAdd1(reply, true); // canGetMonitorInfo
- return ERR_NONE;
-}
-
-static JdwpError VM_CapabilitiesNew(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // The first few capabilities are the same as those reported by the older call.
- VM_Capabilities(nullptr, request, reply);
-
- expandBufAdd1(reply, false); // canRedefineClasses
- expandBufAdd1(reply, false); // canAddMethod
- expandBufAdd1(reply, false); // canUnrestrictedlyRedefineClasses
- expandBufAdd1(reply, false); // canPopFrames
- expandBufAdd1(reply, true); // canUseInstanceFilters
- expandBufAdd1(reply, true); // canGetSourceDebugExtension
- expandBufAdd1(reply, false); // canRequestVMDeathEvent
- expandBufAdd1(reply, false); // canSetDefaultStratum
- expandBufAdd1(reply, true); // 1.6: canGetInstanceInfo
- expandBufAdd1(reply, false); // 1.6: canRequestMonitorEvents
- expandBufAdd1(reply, true); // 1.6: canGetMonitorFrameInfo
- expandBufAdd1(reply, false); // 1.6: canUseSourceNameFilters
- expandBufAdd1(reply, false); // 1.6: canGetConstantPool
- expandBufAdd1(reply, false); // 1.6: canForceEarlyReturn
-
- // Fill in reserved22 through reserved32; note count started at 1.
- for (size_t i = 22; i <= 32; ++i) {
- expandBufAdd1(reply, false);
- }
- return ERR_NONE;
-}
-
-static JdwpError VM_AllClassesImpl(ExpandBuf* pReply, bool descriptor_and_status, bool generic)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- std::vector<JDWP::RefTypeId> classes;
- Dbg::GetClassList(&classes);
-
- expandBufAdd4BE(pReply, classes.size());
-
- for (size_t i = 0; i < classes.size(); ++i) {
- static const char genericSignature[1] = "";
- JDWP::JdwpTypeTag type_tag;
- std::string descriptor;
- uint32_t class_status;
- JDWP::JdwpError status = Dbg::GetClassInfo(classes[i], &type_tag, &class_status, &descriptor);
- if (status != ERR_NONE) {
- return status;
- }
-
- expandBufAdd1(pReply, type_tag);
- expandBufAddRefTypeId(pReply, classes[i]);
- if (descriptor_and_status) {
- expandBufAddUtf8String(pReply, descriptor);
- if (generic) {
- expandBufAddUtf8String(pReply, genericSignature);
- }
- expandBufAdd4BE(pReply, class_status);
- }
- }
-
- return ERR_NONE;
-}
-
-static JdwpError VM_AllClasses(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return VM_AllClassesImpl(pReply, true, false);
-}
-
-static JdwpError VM_AllClassesWithGeneric(JdwpState*, Request*, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return VM_AllClassesImpl(pReply, true, true);
-}
-
-static JdwpError VM_InstanceCounts(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- int32_t class_count = request->ReadSigned32("class count");
- if (class_count < 0) {
- return ERR_ILLEGAL_ARGUMENT;
- }
- std::vector<RefTypeId> class_ids;
- for (int32_t i = 0; i < class_count; ++i) {
- class_ids.push_back(request->ReadRefTypeId());
- }
-
- std::vector<uint64_t> counts;
- JdwpError rc = Dbg::GetInstanceCounts(class_ids, &counts);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- expandBufAdd4BE(pReply, counts.size());
- for (size_t i = 0; i < counts.size(); ++i) {
- expandBufAdd8BE(pReply, counts[i]);
- }
- return ERR_NONE;
-}
-
-static JdwpError RT_Modifiers(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::GetModifiers(refTypeId, pReply);
-}
-
-/*
- * Get values from static fields in a reference type.
- */
-static JdwpError RT_GetValues(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- int32_t field_count = request->ReadSigned32("field count");
- expandBufAdd4BE(pReply, field_count);
- for (int32_t i = 0; i < field_count; ++i) {
- FieldId fieldId = request->ReadFieldId();
- JdwpError status = Dbg::GetStaticFieldValue(refTypeId, fieldId, pReply);
- if (status != ERR_NONE) {
- return status;
- }
- }
- return ERR_NONE;
-}
-
-/*
- * Get the name of the source file in which a reference type was declared.
- */
-static JdwpError RT_SourceFile(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- std::string source_file;
- JdwpError status = Dbg::GetSourceFile(refTypeId, &source_file);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAddUtf8String(pReply, source_file);
- return ERR_NONE;
-}
-
-/*
- * Return the current status of the reference type.
- */
-static JdwpError RT_Status(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- JDWP::JdwpTypeTag type_tag;
- uint32_t class_status;
- JDWP::JdwpError status = Dbg::GetClassInfo(refTypeId, &type_tag, &class_status, nullptr);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAdd4BE(pReply, class_status);
- return ERR_NONE;
-}
-
-/*
- * Return interfaces implemented directly by this class.
- */
-static JdwpError RT_Interfaces(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::OutputDeclaredInterfaces(refTypeId, pReply);
-}
-
-/*
- * Return the class object corresponding to this type.
- */
-static JdwpError RT_ClassObject(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- ObjectId class_object_id;
- JdwpError status = Dbg::GetClassObject(refTypeId, &class_object_id);
- if (status != ERR_NONE) {
- return status;
- }
- VLOG(jdwp) << StringPrintf(" --> ObjectId %#" PRIx64, class_object_id);
- expandBufAddObjectId(pReply, class_object_id);
- return ERR_NONE;
-}
-
-/*
- * Returns the value of the SourceDebugExtension attribute.
- */
-static JdwpError RT_SourceDebugExtension(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- /* referenceTypeId in, string out */
- RefTypeId refTypeId = request->ReadRefTypeId();
- std::string extension_data;
- JdwpError status = Dbg::GetSourceDebugExtension(refTypeId, &extension_data);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAddUtf8String(pReply, extension_data);
- return ERR_NONE;
-}
-
-static JdwpError RT_Signature(JdwpState*, Request* request, ExpandBuf* pReply, bool with_generic)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
-
- std::string signature;
- JdwpError status = Dbg::GetSignature(refTypeId, &signature);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAddUtf8String(pReply, signature);
- if (with_generic) {
- expandBufAddUtf8String(pReply, "");
- }
- return ERR_NONE;
-}
-
-static JdwpError RT_Signature(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return RT_Signature(state, request, pReply, false);
-}
-
-static JdwpError RT_SignatureWithGeneric(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return RT_Signature(state, request, pReply, true);
-}
-
-/*
- * Return the instance of java.lang.ClassLoader that loaded the specified
- * reference type, or null if it was loaded by the system loader.
- */
-static JdwpError RT_ClassLoader(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::GetClassLoader(refTypeId, pReply);
-}
-
-/*
- * Given a referenceTypeId, return a block of stuff that describes the
- * fields declared by a class.
- */
-static JdwpError RT_FieldsWithGeneric(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::OutputDeclaredFields(refTypeId, true, pReply);
-}
-
-// Obsolete equivalent of FieldsWithGeneric, without the generic type information.
-static JdwpError RT_Fields(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::OutputDeclaredFields(refTypeId, false, pReply);
-}
-
-/*
- * Given a referenceTypeID, return a block of goodies describing the
- * methods declared by a class.
- */
-static JdwpError RT_MethodsWithGeneric(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::OutputDeclaredMethods(refTypeId, true, pReply);
-}
-
-// Obsolete equivalent of MethodsWithGeneric, without the generic type information.
-static JdwpError RT_Methods(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- return Dbg::OutputDeclaredMethods(refTypeId, false, pReply);
-}
-
-static JdwpError RT_Instances(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- int32_t max_count = request->ReadSigned32("max count");
- if (max_count < 0) {
- return ERR_ILLEGAL_ARGUMENT;
- }
-
- std::vector<ObjectId> instances;
- JdwpError rc = Dbg::GetInstances(class_id, max_count, &instances);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- return WriteTaggedObjectList(reply, instances);
-}
-
-/*
- * Return the immediate superclass of a class.
- */
-static JdwpError CT_Superclass(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- RefTypeId superClassId;
- JdwpError status = Dbg::GetSuperclass(class_id, &superClassId);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAddRefTypeId(pReply, superClassId);
- return ERR_NONE;
-}
-
-/*
- * Set static class values.
- */
-static JdwpError CT_SetValues(JdwpState* , Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- int32_t values_count = request->ReadSigned32("values count");
-
- UNUSED(class_id);
-
- for (int32_t i = 0; i < values_count; ++i) {
- FieldId fieldId = request->ReadFieldId();
- JDWP::JdwpTag fieldTag = Dbg::GetStaticFieldBasicTag(fieldId);
- size_t width = Dbg::GetTagWidth(fieldTag);
- uint64_t value = request->ReadValue(width);
-
- VLOG(jdwp) << " --> field=" << fieldId << " tag=" << fieldTag << " --> " << value;
- JdwpError status = Dbg::SetStaticFieldValue(fieldId, value, width);
- if (status != ERR_NONE) {
- return status;
- }
- }
-
- return ERR_NONE;
-}
-
-/*
- * Invoke a static method.
- *
- * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on
- * values in the "variables" display.
- */
-static JdwpError CT_InvokeMethod(JdwpState* state, Request* request,
- ExpandBuf* pReply ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- ObjectId thread_id = request->ReadThreadId();
- MethodId method_id = request->ReadMethodId();
-
- return RequestInvoke(state, request, thread_id, 0, class_id, method_id, false);
-}
-
-/*
- * Create a new object of the requested type, and invoke the specified
- * constructor.
- *
- * Example: in IntelliJ, create a watch on "new String(myByteArray)" to
- * see the contents of a byte[] as a string.
- */
-static JdwpError CT_NewInstance(JdwpState* state, Request* request,
- ExpandBuf* pReply ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- ObjectId thread_id = request->ReadThreadId();
- MethodId method_id = request->ReadMethodId();
-
- ObjectId object_id;
- JdwpError status = Dbg::CreateObject(class_id, &object_id);
- if (status != ERR_NONE) {
- return status;
- }
- return RequestInvoke(state, request, thread_id, object_id, class_id, method_id, true);
-}
-
-/*
- * Create a new array object of the requested type and length.
- */
-static JdwpError AT_newInstance(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId arrayTypeId = request->ReadRefTypeId();
- int32_t length = request->ReadSigned32("length");
-
- ObjectId object_id;
- JdwpError status = Dbg::CreateArrayObject(arrayTypeId, length, &object_id);
- if (status != ERR_NONE) {
- return status;
- }
- expandBufAdd1(pReply, JT_ARRAY);
- expandBufAddObjectId(pReply, object_id);
- return ERR_NONE;
-}
-
-/*
- * Invoke a static method on an interface.
- */
-static JdwpError IT_InvokeMethod(JdwpState* state, Request* request,
- ExpandBuf* pReply ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- ObjectId thread_id = request->ReadThreadId();
- MethodId method_id = request->ReadMethodId();
-
- return RequestInvoke(state, request, thread_id, 0, class_id, method_id, false);
-}
-
-/*
- * Return line number information for the method, if present.
- */
-static JdwpError M_LineTable(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId refTypeId = request->ReadRefTypeId();
- MethodId method_id = request->ReadMethodId();
-
- Dbg::OutputLineTable(refTypeId, method_id, pReply);
-
- return ERR_NONE;
-}
-
-static JdwpError M_VariableTable(JdwpState*, Request* request, ExpandBuf* pReply,
- bool generic)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- MethodId method_id = request->ReadMethodId();
-
- // We could return ERR_ABSENT_INFORMATION here if the DEX file was built without local variable
- // information. That will cause Eclipse to make a best-effort attempt at displaying local
- // variables anonymously. However, the attempt isn't very good, so we're probably better off just
- // not showing anything.
- Dbg::OutputVariableTable(class_id, method_id, generic, pReply);
- return ERR_NONE;
-}
-
-static JdwpError M_VariableTable(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return M_VariableTable(state, request, pReply, false);
-}
-
-static JdwpError M_VariableTableWithGeneric(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return M_VariableTable(state, request, pReply, true);
-}
-
-static JdwpError M_Bytecodes(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_id = request->ReadRefTypeId();
- MethodId method_id = request->ReadMethodId();
-
- std::vector<uint8_t> bytecodes;
- JdwpError rc = Dbg::GetBytecodes(class_id, method_id, &bytecodes);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- expandBufAdd4BE(reply, bytecodes.size());
- for (size_t i = 0; i < bytecodes.size(); ++i) {
- expandBufAdd1(reply, bytecodes[i]);
- }
-
- return ERR_NONE;
-}
-
-static JdwpError M_IsObsolete(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- request->ReadRefTypeId(); // unused reference type ID
- MethodId id = request->ReadMethodId();
- expandBufAdd1(reply, Dbg::IsMethodObsolete(id));
- return ERR_NONE;
-}
-
-/*
- * Given an object reference, return the runtime type of the object
- * (class or array).
- *
- * This can get called on different things, e.g. thread_id gets
- * passed in here.
- */
-static JdwpError OR_ReferenceType(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- return Dbg::GetReferenceType(object_id, pReply);
-}
-
-/*
- * Get values from the fields of an object.
- */
-static JdwpError OR_GetValues(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- int32_t field_count = request->ReadSigned32("field count");
-
- expandBufAdd4BE(pReply, field_count);
- for (int32_t i = 0; i < field_count; ++i) {
- FieldId fieldId = request->ReadFieldId();
- JdwpError status = Dbg::GetFieldValue(object_id, fieldId, pReply);
- if (status != ERR_NONE) {
- return status;
- }
- }
-
- return ERR_NONE;
-}
-
-/*
- * Set values in the fields of an object.
- */
-static JdwpError OR_SetValues(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- int32_t field_count = request->ReadSigned32("field count");
-
- for (int32_t i = 0; i < field_count; ++i) {
- FieldId fieldId = request->ReadFieldId();
-
- JDWP::JdwpTag fieldTag = Dbg::GetFieldBasicTag(fieldId);
- size_t width = Dbg::GetTagWidth(fieldTag);
- uint64_t value = request->ReadValue(width);
-
- VLOG(jdwp) << " --> fieldId=" << fieldId << " tag=" << fieldTag << "(" << width << ") value=" << value;
- JdwpError status = Dbg::SetFieldValue(object_id, fieldId, value, width);
- if (status != ERR_NONE) {
- return status;
- }
- }
-
- return ERR_NONE;
-}
-
-static JdwpError OR_MonitorInfo(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- return Dbg::GetMonitorInfo(object_id, reply);
-}
-
-/*
- * Invoke an instance method. The invocation must occur in the specified
- * thread, which must have been suspended by an event.
- *
- * The call is synchronous. All threads in the VM are resumed, unless the
- * SINGLE_THREADED flag is set.
- *
- * If you ask Eclipse to "inspect" an object (or ask JDB to "print" an
- * object), it will try to invoke the object's toString() function. This
- * feature becomes crucial when examining ArrayLists with Eclipse.
- */
-static JdwpError OR_InvokeMethod(JdwpState* state, Request* request,
- ExpandBuf* pReply ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- ObjectId thread_id = request->ReadThreadId();
- RefTypeId class_id = request->ReadRefTypeId();
- MethodId method_id = request->ReadMethodId();
-
- return RequestInvoke(state, request, thread_id, object_id, class_id, method_id, false);
-}
-
-static JdwpError OR_DisableCollection(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- return Dbg::DisableCollection(object_id);
-}
-
-static JdwpError OR_EnableCollection(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- return Dbg::EnableCollection(object_id);
-}
-
-static JdwpError OR_IsCollected(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- bool is_collected;
- JdwpError rc = Dbg::IsCollected(object_id, &is_collected);
- expandBufAdd1(pReply, is_collected ? 1 : 0);
- return rc;
-}
-
-static JdwpError OR_ReferringObjects(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId object_id = request->ReadObjectId();
- int32_t max_count = request->ReadSigned32("max count");
- if (max_count < 0) {
- return ERR_ILLEGAL_ARGUMENT;
- }
-
- std::vector<ObjectId> referring_objects;
- JdwpError rc = Dbg::GetReferringObjects(object_id, max_count, &referring_objects);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- return WriteTaggedObjectList(reply, referring_objects);
-}
-
-/*
- * Return the string value in a string object.
- */
-static JdwpError SR_Value(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId stringObject = request->ReadObjectId();
- std::string str;
- JDWP::JdwpError error = Dbg::StringToUtf8(stringObject, &str);
- if (error != JDWP::ERR_NONE) {
- return error;
- }
-
- VLOG(jdwp) << StringPrintf(" --> %s", PrintableString(str.c_str()).c_str());
-
- expandBufAddUtf8String(pReply, str);
-
- return ERR_NONE;
-}
-
-/*
- * Return a thread's name.
- */
-static JdwpError TR_Name(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- std::string name;
- JdwpError error = Dbg::GetThreadName(thread_id, &name);
- if (error != ERR_NONE) {
- return error;
- }
- VLOG(jdwp) << StringPrintf(" Name of thread %#" PRIx64 " is \"%s\"", thread_id, name.c_str());
- expandBufAddUtf8String(pReply, name);
-
- return ERR_NONE;
-}
-
-/*
- * Suspend the specified thread.
- *
- * It's supposed to remain suspended even if interpreted code wants to
- * resume it; only the JDI is allowed to resume it.
- */
-static JdwpError TR_Suspend(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- if (thread_id == Dbg::GetThreadSelfId()) {
- LOG(INFO) << " Warning: ignoring request to suspend self";
- return ERR_THREAD_NOT_SUSPENDED;
- }
-
- Thread* self = Thread::Current();
- ScopedThreadSuspension sts(self, kWaitingForDebuggerSend);
- JdwpError result = Dbg::SuspendThread(thread_id);
- return result;
-}
-
-/*
- * Resume the specified thread.
- */
-static JdwpError TR_Resume(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- if (thread_id == Dbg::GetThreadSelfId()) {
- LOG(INFO) << " Warning: ignoring request to resume self";
- return ERR_NONE;
- }
-
- Dbg::ResumeThread(thread_id);
- return ERR_NONE;
-}
-
-/*
- * Return status of specified thread.
- */
-static JdwpError TR_Status(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- JDWP::JdwpThreadStatus threadStatus;
- JDWP::JdwpSuspendStatus suspendStatus;
- JdwpError error = Dbg::GetThreadStatus(thread_id, &threadStatus, &suspendStatus);
- if (error != ERR_NONE) {
- return error;
- }
-
- VLOG(jdwp) << " --> " << threadStatus << ", " << suspendStatus;
-
- expandBufAdd4BE(pReply, threadStatus);
- expandBufAdd4BE(pReply, suspendStatus);
-
- return ERR_NONE;
-}
-
-/*
- * Return the thread group that the specified thread is a member of.
- */
-static JdwpError TR_ThreadGroup(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
- return Dbg::GetThreadGroup(thread_id, pReply);
-}
-
-/*
- * Return the current call stack of a suspended thread.
- *
- * If the thread isn't suspended, the error code isn't defined, but should
- * be THREAD_NOT_SUSPENDED.
- */
-static JdwpError TR_Frames(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
- uint32_t start_frame = request->ReadUnsigned32("start frame");
- uint32_t length = request->ReadUnsigned32("length");
-
- size_t actual_frame_count;
- JdwpError error = Dbg::GetThreadFrameCount(thread_id, &actual_frame_count);
- if (error != ERR_NONE) {
- return error;
- }
-
- if (actual_frame_count <= 0) {
- return ERR_THREAD_NOT_SUSPENDED; // 0 means no managed frames (which means "in native").
- }
-
- if (start_frame > actual_frame_count) {
- return ERR_INVALID_INDEX;
- }
- if (length == static_cast<uint32_t>(-1)) {
- length = actual_frame_count - start_frame;
- }
- if (start_frame + length > actual_frame_count) {
- return ERR_INVALID_LENGTH;
- }
-
- return Dbg::GetThreadFrames(thread_id, start_frame, length, pReply);
-}
-
-/*
- * Returns the #of frames on the specified thread, which must be suspended.
- */
-static JdwpError TR_FrameCount(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- size_t frame_count;
- JdwpError rc = Dbg::GetThreadFrameCount(thread_id, &frame_count);
- if (rc != ERR_NONE) {
- return rc;
- }
- expandBufAdd4BE(pReply, static_cast<uint32_t>(frame_count));
-
- return ERR_NONE;
-}
-
-static JdwpError TR_OwnedMonitors(Request* request, ExpandBuf* reply, bool with_stack_depths)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- std::vector<ObjectId> monitors;
- std::vector<uint32_t> stack_depths;
- JdwpError rc = Dbg::GetOwnedMonitors(thread_id, &monitors, &stack_depths);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- expandBufAdd4BE(reply, monitors.size());
- for (size_t i = 0; i < monitors.size(); ++i) {
- rc = WriteTaggedObject(reply, monitors[i]);
- if (rc != ERR_NONE) {
- return rc;
- }
- if (with_stack_depths) {
- expandBufAdd4BE(reply, stack_depths[i]);
- }
- }
- return ERR_NONE;
-}
-
-static JdwpError TR_OwnedMonitors(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return TR_OwnedMonitors(request, reply, false);
-}
-
-static JdwpError TR_OwnedMonitorsStackDepthInfo(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return TR_OwnedMonitors(request, reply, true);
-}
-
-static JdwpError TR_CurrentContendedMonitor(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
-
- ObjectId contended_monitor;
- JdwpError rc = Dbg::GetContendedMonitor(thread_id, &contended_monitor);
- if (rc != ERR_NONE) {
- return rc;
- }
- return WriteTaggedObject(reply, contended_monitor);
-}
-
-static JdwpError TR_Interrupt(JdwpState*, Request* request, ExpandBuf* reply ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
- return Dbg::Interrupt(thread_id);
-}
-
-/*
- * Return the debug suspend count for the specified thread.
- *
- * (The thread *might* still be running -- it might not have examined
- * its suspend count recently.)
- */
-static JdwpError TR_DebugSuspendCount(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
- return Dbg::GetThreadDebugSuspendCount(thread_id, pReply);
-}
-
-/*
- * Return the name of a thread group.
- *
- * The Eclipse debugger recognizes "main" and "system" as special.
- */
-static JdwpError TGR_Name(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_group_id = request->ReadThreadGroupId();
- return Dbg::GetThreadGroupName(thread_group_id, pReply);
-}
-
-/*
- * Returns the thread group -- if any -- that contains the specified
- * thread group.
- */
-static JdwpError TGR_Parent(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_group_id = request->ReadThreadGroupId();
- return Dbg::GetThreadGroupParent(thread_group_id, pReply);
-}
-
-/*
- * Return the active threads and thread groups that are part of the
- * specified thread group.
- */
-static JdwpError TGR_Children(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_group_id = request->ReadThreadGroupId();
- return Dbg::GetThreadGroupChildren(thread_group_id, pReply);
-}
-
-/*
- * Return the #of components in the array.
- */
-static JdwpError AR_Length(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId array_id = request->ReadArrayId();
-
- int32_t length;
- JdwpError status = Dbg::GetArrayLength(array_id, &length);
- if (status != ERR_NONE) {
- return status;
- }
- VLOG(jdwp) << " --> " << length;
-
- expandBufAdd4BE(pReply, length);
-
- return ERR_NONE;
-}
-
-/*
- * Return the values from an array.
- */
-static JdwpError AR_GetValues(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId array_id = request->ReadArrayId();
- uint32_t offset = request->ReadUnsigned32("offset");
- uint32_t length = request->ReadUnsigned32("length");
- return Dbg::OutputArray(array_id, offset, length, pReply);
-}
-
-/*
- * Set values in an array.
- */
-static JdwpError AR_SetValues(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId array_id = request->ReadArrayId();
- uint32_t offset = request->ReadUnsigned32("offset");
- uint32_t count = request->ReadUnsigned32("count");
- return Dbg::SetArrayElements(array_id, offset, count, request);
-}
-
-static JdwpError CLR_VisibleClasses(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- request->ReadObjectId(); // classLoaderObject
- // TODO: we should only return classes which have the given class loader as a defining or
- // initiating loader. The former would be easy; the latter is hard, because we don't have
- // any such notion.
- return VM_AllClassesImpl(pReply, false, false);
-}
-
-// Delete function class to use std::unique_ptr with JdwpEvent.
-struct JdwpEventDeleter {
- void operator()(JdwpEvent* event) {
- EventFree(event);
- }
-};
-
-/*
- * Set an event trigger.
- *
- * Reply with a requestID.
- */
-static JdwpError ER_Set(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JdwpEventKind event_kind = request->ReadEnum1<JdwpEventKind>("event kind");
- JdwpSuspendPolicy suspend_policy = request->ReadEnum1<JdwpSuspendPolicy>("suspend policy");
- int32_t modifier_count = request->ReadSigned32("modifier count");
-
- CHECK_LT(modifier_count, 256); /* reasonableness check */
-
- std::unique_ptr<JDWP::JdwpEvent, JdwpEventDeleter> pEvent(EventAlloc(modifier_count));
- pEvent->eventKind = event_kind;
- pEvent->suspend_policy = suspend_policy;
- pEvent->modCount = modifier_count;
-
- /*
- * Read modifiers. Ordering may be significant (see explanation of Count
- * mods in JDWP doc).
- */
- for (int32_t i = 0; i < modifier_count; ++i) {
- JdwpEventMod& mod = pEvent->mods[i];
- mod.modKind = request->ReadModKind();
- switch (mod.modKind) {
- case MK_COUNT:
- {
- // Report once, when "--count" reaches 0.
- uint32_t count = request->ReadUnsigned32("count");
- if (count == 0) {
- return ERR_INVALID_COUNT;
- }
- mod.count.count = count;
- }
- break;
- case MK_CONDITIONAL:
- {
- // Conditional on expression.
- uint32_t exprId = request->ReadUnsigned32("expr id");
- mod.conditional.exprId = exprId;
- }
- break;
- case MK_THREAD_ONLY:
- {
- // Only report events in specified thread.
- ObjectId thread_id = request->ReadThreadId();
- mod.threadOnly.threadId = thread_id;
- }
- break;
- case MK_CLASS_ONLY:
- {
- // For ClassPrepare, MethodEntry.
- RefTypeId class_id = request->ReadRefTypeId();
- mod.classOnly.refTypeId = class_id;
- }
- break;
- case MK_CLASS_MATCH:
- {
- // Restrict events to matching classes.
- // pattern is "java.foo.*", we want "java/foo/*".
- std::string pattern(request->ReadUtf8String());
- std::replace(pattern.begin(), pattern.end(), '.', '/');
- mod.classMatch.classPattern = strdup(pattern.c_str());
- }
- break;
- case MK_CLASS_EXCLUDE:
- {
- // Restrict events to non-matching classes.
- // pattern is "java.foo.*", we want "java/foo/*".
- std::string pattern(request->ReadUtf8String());
- std::replace(pattern.begin(), pattern.end(), '.', '/');
- mod.classExclude.classPattern = strdup(pattern.c_str());
- }
- break;
- case MK_LOCATION_ONLY:
- {
- // Restrict certain events based on location.
- JdwpLocation location = request->ReadLocation();
- mod.locationOnly.loc = location;
- }
- break;
- case MK_EXCEPTION_ONLY:
- {
- // Modifies EK_EXCEPTION events,
- mod.exceptionOnly.refTypeId = request->ReadRefTypeId(); // null => all exceptions.
- mod.exceptionOnly.caught = request->ReadEnum1<uint8_t>("caught");
- mod.exceptionOnly.uncaught = request->ReadEnum1<uint8_t>("uncaught");
- }
- break;
- case MK_FIELD_ONLY:
- {
- // For field access/modification events.
- RefTypeId declaring = request->ReadRefTypeId();
- FieldId fieldId = request->ReadFieldId();
- mod.fieldOnly.refTypeId = declaring;
- mod.fieldOnly.fieldId = fieldId;
- }
- break;
- case MK_STEP:
- {
- // For use with EK_SINGLE_STEP.
- ObjectId thread_id = request->ReadThreadId();
- uint32_t size = request->ReadUnsigned32("step size");
- uint32_t depth = request->ReadUnsigned32("step depth");
- VLOG(jdwp) << StringPrintf(" Step: thread=%#" PRIx64, thread_id)
- << " size=" << JdwpStepSize(size) << " depth=" << JdwpStepDepth(depth);
-
- mod.step.threadId = thread_id;
- mod.step.size = size;
- mod.step.depth = depth;
- }
- break;
- case MK_INSTANCE_ONLY:
- {
- // Report events related to a specific object.
- ObjectId instance = request->ReadObjectId();
- mod.instanceOnly.objectId = instance;
- }
- break;
- default:
- LOG(WARNING) << "Unsupported modifier " << mod.modKind << " for event " << pEvent->eventKind;
- return JDWP::ERR_NOT_IMPLEMENTED;
- }
- }
-
- /*
- * We reply with an integer "requestID".
- */
- uint32_t requestId = state->NextEventSerial();
- expandBufAdd4BE(pReply, requestId);
-
- pEvent->requestId = requestId;
-
- VLOG(jdwp) << StringPrintf(" --> event requestId=%#x", requestId);
-
- /* add it to the list */
- // TODO: RegisterEvent() should take std::unique_ptr<>.
- JdwpError err = state->RegisterEvent(pEvent.get());
- if (err != ERR_NONE) {
- /* registration failed, probably because event is bogus */
- LOG(WARNING) << "WARNING: event request rejected";
- return err;
- }
- pEvent.release(); // NOLINT b/117926937
- return ERR_NONE;
-}
-
-static JdwpError ER_Clear(JdwpState* state, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- request->ReadEnum1<JdwpEventKind>("event kind");
- uint32_t requestId = request->ReadUnsigned32("request id");
-
- // Failure to find an event with a matching ID is a no-op
- // and does not return an error.
- state->UnregisterEventById(requestId);
- return ERR_NONE;
-}
-
-/*
- * Return the values of arguments and local variables.
- */
-static JdwpError SF_GetValues(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return Dbg::GetLocalValues(request, pReply);
-}
-
-/*
- * Set the values of arguments and local variables.
- */
-static JdwpError SF_SetValues(JdwpState*, Request* request, ExpandBuf*)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return Dbg::SetLocalValues(request);
-}
-
-static JdwpError SF_ThisObject(JdwpState*, Request* request, ExpandBuf* reply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjectId thread_id = request->ReadThreadId();
- FrameId frame_id = request->ReadFrameId();
-
- ObjectId object_id;
- JdwpError rc = Dbg::GetThisObject(thread_id, frame_id, &object_id);
- if (rc != ERR_NONE) {
- return rc;
- }
-
- return WriteTaggedObject(reply, object_id);
-}
-
-/*
- * Return the reference type reflected by this class object.
- *
- * This appears to be required because ReferenceTypeId values are NEVER
- * reused, whereas ClassIds can be recycled like any other object. (Either
- * that, or I have no idea what this is for.)
- */
-static JdwpError COR_ReflectedType(JdwpState*, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- RefTypeId class_object_id = request->ReadRefTypeId();
- return Dbg::GetReflectedType(class_object_id, pReply);
-}
-
-/*
- * Handle a DDM packet with a single chunk in it.
- */
-static JdwpError DDM_Chunk(JdwpState* state, Request* request, ExpandBuf* pReply)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- state->NotifyDdmsActive();
- uint8_t* replyBuf = nullptr;
- int replyLen = -1;
- if (Dbg::DdmHandlePacket(request, &replyBuf, &replyLen)) {
- // If they want to send something back, we copy it into the buffer.
- // TODO: consider altering the JDWP stuff to hold the packet header
- // in a separate buffer. That would allow us to writev() DDM traffic
- // instead of copying it into the expanding buffer. The reduction in
- // heap requirements is probably more valuable than the efficiency.
- CHECK_GT(replyLen, 0);
- memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen);
- delete[] replyBuf;
- }
- return ERR_NONE;
-}
-
-/*
- * Handler map decl.
- */
-using JdwpRequestHandler = JdwpError(*)(JdwpState* state, Request* request, ExpandBuf* reply);
-
-struct JdwpHandlerMap {
- uint8_t cmdSet;
- uint8_t cmd;
- JdwpRequestHandler func;
- const char* name;
-};
-
-/*
- * Map commands to functions.
- *
- * Command sets 0-63 are incoming requests, 64-127 are outbound requests,
- * and 128-256 are vendor-defined.
- */
-static const JdwpHandlerMap gHandlers[] = {
- /* VirtualMachine command set (1) */
- { 1, 1, VM_Version, "VirtualMachine.Version" },
- { 1, 2, VM_ClassesBySignature, "VirtualMachine.ClassesBySignature" },
- { 1, 3, VM_AllClasses, "VirtualMachine.AllClasses" },
- { 1, 4, VM_AllThreads, "VirtualMachine.AllThreads" },
- { 1, 5, VM_TopLevelThreadGroups, "VirtualMachine.TopLevelThreadGroups" },
- { 1, 6, VM_Dispose, "VirtualMachine.Dispose" },
- { 1, 7, VM_IDSizes, "VirtualMachine.IDSizes" },
- { 1, 8, VM_Suspend, "VirtualMachine.Suspend" },
- { 1, 9, VM_Resume, "VirtualMachine.Resume" },
- { 1, 10, VM_Exit, "VirtualMachine.Exit" },
- { 1, 11, VM_CreateString, "VirtualMachine.CreateString" },
- { 1, 12, VM_Capabilities, "VirtualMachine.Capabilities" },
- { 1, 13, VM_ClassPaths, "VirtualMachine.ClassPaths" },
- { 1, 14, VM_DisposeObjects, "VirtualMachine.DisposeObjects" },
- { 1, 15, nullptr, "VirtualMachine.HoldEvents" },
- { 1, 16, nullptr, "VirtualMachine.ReleaseEvents" },
- { 1, 17, VM_CapabilitiesNew, "VirtualMachine.CapabilitiesNew" },
- { 1, 18, nullptr, "VirtualMachine.RedefineClasses" },
- { 1, 19, nullptr, "VirtualMachine.SetDefaultStratum" },
- { 1, 20, VM_AllClassesWithGeneric, "VirtualMachine.AllClassesWithGeneric" },
- { 1, 21, VM_InstanceCounts, "VirtualMachine.InstanceCounts" },
-
- /* ReferenceType command set (2) */
- { 2, 1, RT_Signature, "ReferenceType.Signature" },
- { 2, 2, RT_ClassLoader, "ReferenceType.ClassLoader" },
- { 2, 3, RT_Modifiers, "ReferenceType.Modifiers" },
- { 2, 4, RT_Fields, "ReferenceType.Fields" },
- { 2, 5, RT_Methods, "ReferenceType.Methods" },
- { 2, 6, RT_GetValues, "ReferenceType.GetValues" },
- { 2, 7, RT_SourceFile, "ReferenceType.SourceFile" },
- { 2, 8, nullptr, "ReferenceType.NestedTypes" },
- { 2, 9, RT_Status, "ReferenceType.Status" },
- { 2, 10, RT_Interfaces, "ReferenceType.Interfaces" },
- { 2, 11, RT_ClassObject, "ReferenceType.ClassObject" },
- { 2, 12, RT_SourceDebugExtension, "ReferenceType.SourceDebugExtension" },
- { 2, 13, RT_SignatureWithGeneric, "ReferenceType.SignatureWithGeneric" },
- { 2, 14, RT_FieldsWithGeneric, "ReferenceType.FieldsWithGeneric" },
- { 2, 15, RT_MethodsWithGeneric, "ReferenceType.MethodsWithGeneric" },
- { 2, 16, RT_Instances, "ReferenceType.Instances" },
- { 2, 17, nullptr, "ReferenceType.ClassFileVersion" },
- { 2, 18, nullptr, "ReferenceType.ConstantPool" },
-
- /* ClassType command set (3) */
- { 3, 1, CT_Superclass, "ClassType.Superclass" },
- { 3, 2, CT_SetValues, "ClassType.SetValues" },
- { 3, 3, CT_InvokeMethod, "ClassType.InvokeMethod" },
- { 3, 4, CT_NewInstance, "ClassType.NewInstance" },
-
- /* ArrayType command set (4) */
- { 4, 1, AT_newInstance, "ArrayType.NewInstance" },
-
- /* InterfaceType command set (5) */
- { 5, 1, IT_InvokeMethod, "InterfaceType.InvokeMethod" },
-
- /* Method command set (6) */
- { 6, 1, M_LineTable, "Method.LineTable" },
- { 6, 2, M_VariableTable, "Method.VariableTable" },
- { 6, 3, M_Bytecodes, "Method.Bytecodes" },
- { 6, 4, M_IsObsolete, "Method.IsObsolete" },
- { 6, 5, M_VariableTableWithGeneric, "Method.VariableTableWithGeneric" },
-
- /* Field command set (8) */
-
- /* ObjectReference command set (9) */
- { 9, 1, OR_ReferenceType, "ObjectReference.ReferenceType" },
- { 9, 2, OR_GetValues, "ObjectReference.GetValues" },
- { 9, 3, OR_SetValues, "ObjectReference.SetValues" },
- { 9, 4, nullptr, "ObjectReference.UNUSED" },
- { 9, 5, OR_MonitorInfo, "ObjectReference.MonitorInfo" },
- { 9, 6, OR_InvokeMethod, "ObjectReference.InvokeMethod" },
- { 9, 7, OR_DisableCollection, "ObjectReference.DisableCollection" },
- { 9, 8, OR_EnableCollection, "ObjectReference.EnableCollection" },
- { 9, 9, OR_IsCollected, "ObjectReference.IsCollected" },
- { 9, 10, OR_ReferringObjects, "ObjectReference.ReferringObjects" },
-
- /* StringReference command set (10) */
- { 10, 1, SR_Value, "StringReference.Value" },
-
- /* ThreadReference command set (11) */
- { 11, 1, TR_Name, "ThreadReference.Name" },
- { 11, 2, TR_Suspend, "ThreadReference.Suspend" },
- { 11, 3, TR_Resume, "ThreadReference.Resume" },
- { 11, 4, TR_Status, "ThreadReference.Status" },
- { 11, 5, TR_ThreadGroup, "ThreadReference.ThreadGroup" },
- { 11, 6, TR_Frames, "ThreadReference.Frames" },
- { 11, 7, TR_FrameCount, "ThreadReference.FrameCount" },
- { 11, 8, TR_OwnedMonitors, "ThreadReference.OwnedMonitors" },
- { 11, 9, TR_CurrentContendedMonitor, "ThreadReference.CurrentContendedMonitor" },
- { 11, 10, nullptr, "ThreadReference.Stop" },
- { 11, 11, TR_Interrupt, "ThreadReference.Interrupt" },
- { 11, 12, TR_DebugSuspendCount, "ThreadReference.SuspendCount" },
- { 11, 13, TR_OwnedMonitorsStackDepthInfo, "ThreadReference.OwnedMonitorsStackDepthInfo" },
- { 11, 14, nullptr, "ThreadReference.ForceEarlyReturn" },
-
- /* ThreadGroupReference command set (12) */
- { 12, 1, TGR_Name, "ThreadGroupReference.Name" },
- { 12, 2, TGR_Parent, "ThreadGroupReference.Parent" },
- { 12, 3, TGR_Children, "ThreadGroupReference.Children" },
-
- /* ArrayReference command set (13) */
- { 13, 1, AR_Length, "ArrayReference.Length" },
- { 13, 2, AR_GetValues, "ArrayReference.GetValues" },
- { 13, 3, AR_SetValues, "ArrayReference.SetValues" },
-
- /* ClassLoaderReference command set (14) */
- { 14, 1, CLR_VisibleClasses, "ClassLoaderReference.VisibleClasses" },
-
- /* EventRequest command set (15) */
- { 15, 1, ER_Set, "EventRequest.Set" },
- { 15, 2, ER_Clear, "EventRequest.Clear" },
- { 15, 3, nullptr, "EventRequest.ClearAllBreakpoints" },
-
- /* StackFrame command set (16) */
- { 16, 1, SF_GetValues, "StackFrame.GetValues" },
- { 16, 2, SF_SetValues, "StackFrame.SetValues" },
- { 16, 3, SF_ThisObject, "StackFrame.ThisObject" },
- { 16, 4, nullptr, "StackFrame.PopFrames" },
-
- /* ClassObjectReference command set (17) */
- { 17, 1, COR_ReflectedType, "ClassObjectReference.ReflectedType" },
-
- /* Event command set (64) */
- { 64, 100, nullptr, "Event.Composite" }, // sent from VM to debugger, never received by VM
-
- { 199, 1, DDM_Chunk, "DDM.Chunk" },
-};
-
-static const char* GetCommandName(Request* request) {
- for (size_t i = 0; i < arraysize(gHandlers); ++i) {
- if (gHandlers[i].cmdSet == request->GetCommandSet() &&
- gHandlers[i].cmd == request->GetCommand()) {
- return gHandlers[i].name;
- }
- }
- return "?UNKNOWN?";
-}
-
-static std::string DescribeCommand(Request* request) {
- std::string result;
- result += "REQUEST: ";
- result += GetCommandName(request);
- result += StringPrintf(" (length=%zu id=0x%06x)", request->GetLength(), request->GetId());
- return result;
-}
-
-// Returns true if the given command_set and command identify an "invoke" command.
-static bool IsInvokeCommand(uint8_t command_set, uint8_t command) {
- if (command_set == kJDWPClassTypeCmdSet) {
- return command == kJDWPClassTypeInvokeMethodCmd || command == kJDWPClassTypeNewInstanceCmd;
- } else if (command_set == kJDWPObjectReferenceCmdSet) {
- return command == kJDWPObjectReferenceInvokeCmd;
- } else if (command_set == kJDWPInterfaceTypeCmdSet) {
- return command == kJDWPInterfaceTypeInvokeMethodCmd;
- } else {
- return false;
- }
-}
-
-/*
- * Process a request from the debugger. The skip_reply flag is set to true to indicate to the
- * caller the reply must not be sent to the debugger. This is used for invoke commands where the
- * reply is sent by the event thread after completing the invoke.
- *
- * On entry, the JDWP thread is in VMWAIT.
- */
-size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply, bool* skip_reply) {
- JdwpError result = ERR_NONE;
- *skip_reply = false;
-
- if (request->GetCommandSet() != kJDWPDdmCmdSet) {
- /*
- * Activity from a debugger, not merely ddms. Mark us as having an
- * active debugger session, and zero out the last-activity timestamp
- * so waitForDebugger() doesn't return if we stall for a bit here.
- */
- Dbg::GoActive();
- last_activity_time_ms_.store(0, std::memory_order_seq_cst);
- }
-
- /*
- * If a debugger event has fired in another thread, wait until the
- * initiating thread has suspended itself before processing commands
- * from the debugger. Otherwise we (the JDWP thread) could be told to
- * resume the thread before it has suspended.
- *
- * Note that we MUST clear the event token before waking the event
- * thread up, or risk waiting for the thread to suspend after we've
- * told it to resume.
- */
- AcquireJdwpTokenForCommand();
-
- /*
- * Tell the VM that we're running and shouldn't be interrupted by GC.
- * Do this after anything that can stall indefinitely.
- */
- Thread* self = Thread::Current();
- ScopedObjectAccess soa(self);
-
- expandBufAddSpace(pReply, kJDWPHeaderLen);
-
- size_t i;
- for (i = 0; i < arraysize(gHandlers); ++i) {
- if (gHandlers[i].cmdSet == request->GetCommandSet() &&
- gHandlers[i].cmd == request->GetCommand() &&
- gHandlers[i].func != nullptr) {
- VLOG(jdwp) << DescribeCommand(request);
- result = (*gHandlers[i].func)(this, request, pReply);
- if (result == ERR_NONE) {
- request->CheckConsumed();
- }
- self->AssertNoPendingException();
- break;
- }
- }
- if (i == arraysize(gHandlers)) {
- LOG(ERROR) << "Command not implemented: " << DescribeCommand(request);
- LOG(ERROR) << HexDump(request->data(), request->size(), false, "");
- result = ERR_NOT_IMPLEMENTED;
- }
-
- size_t replyLength = 0U;
- if (result == ERR_NONE && IsInvokeCommand(request->GetCommandSet(), request->GetCommand())) {
- // We successfully request an invoke in the event thread. It will send the reply once the
- // invoke completes so we must not send it now.
- *skip_reply = true;
- } else {
- /*
- * Set up the reply header.
- *
- * If we encountered an error, only send the header back.
- */
- uint8_t* replyBuf = expandBufGetBuffer(pReply);
- replyLength = (result == ERR_NONE) ? expandBufGetLength(pReply) : kJDWPHeaderLen;
- Set4BE(replyBuf + kJDWPHeaderSizeOffset, replyLength);
- Set4BE(replyBuf + kJDWPHeaderIdOffset, request->GetId());
- Set1(replyBuf + kJDWPHeaderFlagsOffset, kJDWPFlagReply);
- Set2BE(replyBuf + kJDWPHeaderErrorCodeOffset, result);
-
- CHECK_GT(expandBufGetLength(pReply), 0U) << GetCommandName(request) << " " << request->GetId();
-
- size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
- VLOG(jdwp) << "REPLY: " << GetCommandName(request) << " " << result << " (length=" << respLen << ")";
- if (false) {
- VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen, false, "");
- }
- }
-
- VLOG(jdwp) << "----------";
-
- /*
- * Update last-activity timestamp. We really only need this during
- * the initial setup. Only update if this is a non-DDMS packet.
- */
- if (request->GetCommandSet() != kJDWPDdmCmdSet) {
- last_activity_time_ms_.store(MilliTime(), std::memory_order_seq_cst);
- }
-
- return replyLength;
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
deleted file mode 100644
index 447e3bf45ba..00000000000
--- a/runtime/jdwp/jdwp_main.cc
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <errno.h>
-#include <stdlib.h>
-#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "android-base/stringprintf.h"
-
-#include "base/atomic.h"
-#include "base/logging.h" // For VLOG.
-#include "base/time_utils.h"
-#include "debugger.h"
-#include "jdwp/jdwp_priv.h"
-#include "scoped_thread_state_change-inl.h"
-
-namespace art {
-
-namespace JDWP {
-
-using android::base::StringPrintf;
-
-static void* StartJdwpThread(void* arg);
-
-
-static bool ParseJdwpOption(const std::string& name,
- const std::string& value,
- JdwpOptions* jdwp_options) {
- if (name == "transport") {
- if (value == "dt_socket") {
- jdwp_options->transport = JDWP::kJdwpTransportSocket;
- } else if (value == "dt_android_adb") {
- jdwp_options->transport = JDWP::kJdwpTransportAndroidAdb;
- } else {
- jdwp_options->transport = JDWP::kJdwpTransportUnknown;
- LOG(ERROR) << "JDWP transport not supported: " << value;
- return false;
- }
- } else if (name == "server") {
- if (value == "n") {
- jdwp_options->server = false;
- } else if (value == "y") {
- jdwp_options->server = true;
- } else {
- LOG(ERROR) << "JDWP option 'server' must be 'y' or 'n'";
- return false;
- }
- } else if (name == "suspend") {
- if (value == "n") {
- jdwp_options->suspend = false;
- } else if (value == "y") {
- jdwp_options->suspend = true;
- } else {
- LOG(ERROR) << "JDWP option 'suspend' must be 'y' or 'n'";
- return false;
- }
- } else if (name == "address") {
- /* this is either <port> or <host>:<port> */
- std::string port_string;
- jdwp_options->host.clear();
- std::string::size_type colon = value.find(':');
- if (colon != std::string::npos) {
- jdwp_options->host = value.substr(0, colon);
- port_string = value.substr(colon + 1);
- } else {
- port_string = value;
- }
- if (port_string.empty()) {
- LOG(ERROR) << "JDWP address missing port: " << value;
- return false;
- }
- char* end;
- uint64_t port = strtoul(port_string.c_str(), &end, 10);
- if (*end != '\0' || port > 0xffff) {
- LOG(ERROR) << "JDWP address has junk in port field: " << value;
- return false;
- }
- jdwp_options->port = port;
- } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") {
- /* valid but unsupported */
- LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'";
- } else {
- LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'";
- }
-
- return true;
-}
-
-bool ParseJdwpOptions(const std::string& options, JdwpOptions* jdwp_options) {
- VLOG(jdwp) << "ParseJdwpOptions: " << options;
-
- if (options == "help") {
- LOG(ERROR) << "Example: -XjdwpOptions:transport=dt_socket,address=8000,server=y\n"
- << "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
- << "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n";
- return false;
- }
-
- const std::string s;
-
- std::vector<std::string> pairs;
- Split(options, ',', &pairs);
-
- for (const std::string& jdwp_option : pairs) {
- std::string::size_type equals_pos = jdwp_option.find('=');
- if (equals_pos == std::string::npos) {
- LOG(ERROR) << s << "Can't parse JDWP option '" << jdwp_option << "' in '" << options << "'";
- return false;
- }
-
- bool parse_attempt = ParseJdwpOption(jdwp_option.substr(0, equals_pos),
- jdwp_option.substr(equals_pos + 1),
- jdwp_options);
- if (!parse_attempt) {
- // We fail to parse this JDWP option.
- return parse_attempt;
- }
- }
-
- if (jdwp_options->transport == JDWP::kJdwpTransportUnknown) {
- LOG(ERROR) << s << "Must specify JDWP transport: " << options;
- return false;
- }
-#if ART_TARGET_ANDROID
- if (jdwp_options->transport == JDWP::kJdwpTransportNone) {
- jdwp_options->transport = JDWP::kJdwpTransportAndroidAdb;
- LOG(WARNING) << "no JDWP transport specified. Defaulting to dt_android_adb";
- }
-#endif
- if (!jdwp_options->server && (jdwp_options->host.empty() || jdwp_options->port == 0)) {
- LOG(ERROR) << s << "Must specify JDWP host and port when server=n: " << options;
- return false;
- }
-
- return true;
-}
-
-/*
- * JdwpNetStateBase class implementation
- */
-JdwpNetStateBase::JdwpNetStateBase(JdwpState* state)
- : state_(state), socket_lock_("JdwpNetStateBase lock", kJdwpSocketLock) {
- clientSock = -1;
- wake_pipe_[0] = -1;
- wake_pipe_[1] = -1;
- input_count_ = 0;
- awaiting_handshake_ = false;
-}
-
-JdwpNetStateBase::~JdwpNetStateBase() {
- if (wake_pipe_[0] != -1) {
- close(wake_pipe_[0]);
- wake_pipe_[0] = -1;
- }
- if (wake_pipe_[1] != -1) {
- close(wake_pipe_[1]);
- wake_pipe_[1] = -1;
- }
-}
-
-bool JdwpNetStateBase::MakePipe() {
- if (pipe(wake_pipe_) == -1) {
- PLOG(ERROR) << "pipe failed";
- return false;
- }
- return true;
-}
-
-void JdwpNetStateBase::WakePipe() {
- // If we might be sitting in select, kick us loose.
- if (wake_pipe_[1] != -1) {
- VLOG(jdwp) << "+++ writing to wake pipe";
- TEMP_FAILURE_RETRY(write(wake_pipe_[1], "", 1));
- }
-}
-
-void JdwpNetStateBase::ConsumeBytes(size_t count) {
- CHECK_GT(count, 0U);
- CHECK_LE(count, input_count_);
-
- if (count == input_count_) {
- input_count_ = 0;
- return;
- }
-
- memmove(input_buffer_, input_buffer_ + count, input_count_ - count);
- input_count_ -= count;
-}
-
-bool JdwpNetStateBase::HaveFullPacket() {
- if (awaiting_handshake_) {
- return (input_count_ >= kMagicHandshakeLen);
- }
- if (input_count_ < 4) {
- return false;
- }
- uint32_t length = Get4BE(input_buffer_);
- return (input_count_ >= length);
-}
-
-bool JdwpNetStateBase::IsAwaitingHandshake() {
- return awaiting_handshake_;
-}
-
-void JdwpNetStateBase::SetAwaitingHandshake(bool new_state) {
- awaiting_handshake_ = new_state;
-}
-
-bool JdwpNetStateBase::IsConnected() {
- return clientSock >= 0;
-}
-
-// Close a connection from a debugger (which may have already dropped us).
-// Resets the state so we're ready to receive a new connection.
-// Only called from the JDWP thread.
-void JdwpNetStateBase::Close() {
- if (clientSock < 0) {
- return;
- }
-
- VLOG(jdwp) << "+++ closing JDWP connection on fd " << clientSock;
-
- close(clientSock);
- clientSock = -1;
-}
-
-/*
- * Write a packet of "length" bytes. Grabs a mutex to assure atomicity.
- */
-ssize_t JdwpNetStateBase::WritePacket(ExpandBuf* pReply, size_t length) {
- DCHECK_LE(length, expandBufGetLength(pReply));
- if (!IsConnected()) {
- LOG(WARNING) << "Connection with debugger is closed";
- return -1;
- }
- MutexLock mu(Thread::Current(), socket_lock_);
- return TEMP_FAILURE_RETRY(write(clientSock, expandBufGetBuffer(pReply), length));
-}
-
-/*
- * Write a buffered packet. Grabs a mutex to assure atomicity.
- */
-ssize_t JdwpNetStateBase::WriteBufferedPacket(const std::vector<iovec>& iov) {
- MutexLock mu(Thread::Current(), socket_lock_);
- return WriteBufferedPacketLocked(iov);
-}
-
-ssize_t JdwpNetStateBase::WriteBufferedPacketLocked(const std::vector<iovec>& iov) {
- socket_lock_.AssertHeld(Thread::Current());
- DCHECK(IsConnected()) << "Connection with debugger is closed";
- return TEMP_FAILURE_RETRY(writev(clientSock, &iov[0], iov.size()));
-}
-
-bool JdwpState::IsConnected() {
- return netState != nullptr && netState->IsConnected();
-}
-
-void JdwpState::SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov) {
- if (!IsConnected()) {
- // Can happen with some DDMS events.
- VLOG(jdwp) << "Not sending JDWP packet: no debugger attached!";
- return;
- }
-
- size_t expected = 0;
- for (size_t i = 0; i < iov.size(); ++i) {
- expected += iov[i].iov_len;
- }
-
- errno = 0;
- ssize_t actual = netState->WriteBufferedPacket(iov);
- if (static_cast<size_t>(actual) != expected) {
- PLOG(ERROR) << StringPrintf("Failed to send JDWP packet %c%c%c%c to debugger (%zd of %zu)",
- static_cast<char>(type >> 24),
- static_cast<char>(type >> 16),
- static_cast<char>(type >> 8),
- static_cast<char>(type),
- actual, expected);
- }
-}
-
-void JdwpState::SendRequest(ExpandBuf* pReq) {
- if (!IsConnected()) {
- // Can happen with some DDMS events.
- VLOG(jdwp) << "Not sending JDWP packet: no debugger attached!";
- return;
- }
-
- errno = 0;
- ssize_t actual = netState->WritePacket(pReq, expandBufGetLength(pReq));
- if (static_cast<size_t>(actual) != expandBufGetLength(pReq)) {
- PLOG(ERROR) << StringPrintf("Failed to send JDWP packet to debugger (%zd of %zu)",
- actual, expandBufGetLength(pReq));
- }
-}
-
-/*
- * Get the next "request" serial number. We use this when sending
- * packets to the debugger.
- */
-uint32_t JdwpState::NextRequestSerial() {
- return request_serial_++;
-}
-
-/*
- * Get the next "event" serial number. We use this in the response to
- * message type EventRequest.Set.
- */
-uint32_t JdwpState::NextEventSerial() {
- return event_serial_++;
-}
-
-JdwpState::JdwpState(const JdwpOptions* options)
- : options_(options),
- thread_start_lock_("JDWP thread start lock", kJdwpStartLock),
- thread_start_cond_("JDWP thread start condition variable", thread_start_lock_),
- pthread_(0),
- thread_(nullptr),
- debug_thread_started_(false),
- debug_thread_id_(0),
- run(false),
- netState(nullptr),
- attach_lock_("JDWP attach lock", kJdwpAttachLock),
- attach_cond_("JDWP attach condition variable", attach_lock_),
- last_activity_time_ms_(0),
- request_serial_(0x10000000),
- event_serial_(0x20000000),
- event_list_lock_("JDWP event list lock", kJdwpEventListLock),
- event_list_(nullptr),
- event_list_size_(0),
- jdwp_token_lock_("JDWP token lock"),
- jdwp_token_cond_("JDWP token condition variable", jdwp_token_lock_),
- jdwp_token_owner_thread_id_(0),
- ddm_is_active_(false),
- should_exit_(false),
- exit_status_(0),
- shutdown_lock_("JDWP shutdown lock", kJdwpShutdownLock),
- shutdown_cond_("JDWP shutdown condition variable", shutdown_lock_),
- processing_request_(false) {
- Locks::AddToExpectedMutexesOnWeakRefAccess(&event_list_lock_);
-}
-
-/*
- * Initialize JDWP.
- *
- * Does not return until JDWP thread is running, but may return before
- * the thread is accepting network connections.
- */
-JdwpState* JdwpState::Create(const JdwpOptions* options) {
- Thread* self = Thread::Current();
- Locks::mutator_lock_->AssertNotHeld(self);
- std::unique_ptr<JdwpState> state(new JdwpState(options));
- switch (options->transport) {
- case kJdwpTransportSocket:
- InitSocketTransport(state.get(), options);
- break;
-#ifdef ART_TARGET_ANDROID
- case kJdwpTransportAndroidAdb:
- InitAdbTransport(state.get(), options);
- break;
-#endif
- default:
- LOG(FATAL) << "Unknown transport: " << options->transport;
- }
- {
- /*
- * Grab a mutex before starting the thread. This ensures they
- * won't signal the cond var before we're waiting.
- */
- state->thread_start_lock_.AssertNotHeld(self);
- MutexLock thread_start_locker(self, state->thread_start_lock_);
-
- /*
- * We have bound to a port, or are trying to connect outbound to a
- * debugger. Create the JDWP thread and let it continue the mission.
- */
- CHECK_PTHREAD_CALL(pthread_create, (&state->pthread_, nullptr, StartJdwpThread, state.get()),
- "JDWP thread");
-
- /*
- * Wait until the thread finishes basic initialization.
- */
- while (!state->debug_thread_started_) {
- state->thread_start_cond_.Wait(self);
- }
- }
-
- if (options->suspend) {
- /*
- * For suspend=y, wait for the debugger to connect to us or for us to
- * connect to the debugger.
- *
- * The JDWP thread will signal us when it connects successfully or
- * times out (for timeout=xxx), so we have to check to see what happened
- * when we wake up.
- */
- {
- ScopedThreadStateChange tsc(self, kWaitingForDebuggerToAttach);
- MutexLock attach_locker(self, state->attach_lock_);
- while (state->debug_thread_id_ == 0) {
- state->attach_cond_.Wait(self);
- }
- }
- if (!state->IsActive()) {
- LOG(ERROR) << "JDWP connection failed";
- return nullptr;
- }
-
- LOG(INFO) << "JDWP connected";
-
- /*
- * Ordinarily we would pause briefly to allow the debugger to set
- * breakpoints and so on, but for "suspend=y" the VM init code will
- * pause the VM when it sends the VM_START message.
- */
- }
-
- return state.release();
-}
-
-/*
- * Reset all session-related state. There should not be an active connection
- * to the client at this point. The rest of the VM still thinks there is
- * a debugger attached.
- *
- * This includes freeing up the debugger event list.
- */
-void JdwpState::ResetState() {
- /* could reset the serial numbers, but no need to */
-
- UnregisterAll();
- {
- MutexLock mu(Thread::Current(), event_list_lock_);
- CHECK(event_list_ == nullptr);
- }
-
- /*
- * Should not have one of these in progress. If the debugger went away
- * mid-request, though, we could see this.
- */
- if (jdwp_token_owner_thread_id_ != 0) {
- LOG(WARNING) << "Resetting state while event in progress";
- DCHECK(false);
- }
-}
-
-/*
- * Tell the JDWP thread to shut down. Frees "state".
- */
-JdwpState::~JdwpState() {
- if (netState != nullptr) {
- /*
- * Close down the network to inspire the thread to halt. If a request is being processed,
- * we need to wait for it to finish first.
- */
- {
- Thread* self = Thread::Current();
- MutexLock mu(self, shutdown_lock_);
- while (processing_request_) {
- VLOG(jdwp) << "JDWP command in progress: wait for it to finish ...";
- shutdown_cond_.Wait(self);
- }
-
- VLOG(jdwp) << "JDWP shutting down net...";
- netState->Shutdown();
- }
-
- if (debug_thread_started_) {
- run = false;
- void* threadReturn;
- if (pthread_join(pthread_, &threadReturn) != 0) {
- LOG(WARNING) << "JDWP thread join failed";
- }
- }
-
- VLOG(jdwp) << "JDWP freeing netstate...";
- delete netState;
- netState = nullptr;
- }
- CHECK(netState == nullptr);
-
- ResetState();
-
- Locks::RemoveFromExpectedMutexesOnWeakRefAccess(&event_list_lock_);
-}
-
-/*
- * Are we talking to a debugger?
- */
-bool JdwpState::IsActive() {
- return IsConnected();
-}
-
-// Returns "false" if we encounter a connection-fatal error.
-bool JdwpState::HandlePacket() {
- Thread* const self = Thread::Current();
- {
- MutexLock mu(self, shutdown_lock_);
- processing_request_ = true;
- }
- JdwpNetStateBase* netStateBase = netState;
- CHECK(netStateBase != nullptr) << "Connection has been closed";
- JDWP::Request request(netStateBase->input_buffer_, netStateBase->input_count_);
-
- ExpandBuf* pReply = expandBufAlloc();
- bool skip_reply = false;
- size_t replyLength = ProcessRequest(&request, pReply, &skip_reply);
- ssize_t cc = 0;
- if (!skip_reply) {
- cc = netStateBase->WritePacket(pReply, replyLength);
- } else {
- DCHECK_EQ(replyLength, 0U);
- }
- expandBufFree(pReply);
-
- /*
- * We processed this request and sent its reply so we can release the JDWP token.
- */
- ReleaseJdwpTokenForCommand();
-
- if (cc != static_cast<ssize_t>(replyLength)) {
- PLOG(ERROR) << "Failed sending reply to debugger";
- return false;
- }
- netStateBase->ConsumeBytes(request.GetLength());
- {
- MutexLock mu(self, shutdown_lock_);
- processing_request_ = false;
- shutdown_cond_.Broadcast(self);
- }
- return true;
-}
-
-/*
- * Entry point for JDWP thread. The thread was created through the VM
- * mechanisms, so there is a java/lang/Thread associated with us.
- */
-static void* StartJdwpThread(void* arg) {
- JdwpState* state = reinterpret_cast<JdwpState*>(arg);
- CHECK(state != nullptr);
-
- state->Run();
- return nullptr;
-}
-
-void JdwpState::Run() {
- Runtime* runtime = Runtime::Current();
- CHECK(runtime->AttachCurrentThread("JDWP", true, runtime->GetSystemThreadGroup(),
- !runtime->IsAotCompiler()));
-
- VLOG(jdwp) << "JDWP: thread running";
-
- /*
- * Finish initializing, then notify the creating thread that
- * we're running.
- */
- thread_ = Thread::Current();
- run = true;
-
- {
- MutexLock locker(thread_, thread_start_lock_);
- debug_thread_started_ = true;
- thread_start_cond_.Broadcast(thread_);
- }
-
- /* set the thread state to kWaitingInMainDebuggerLoop so GCs don't wait for us */
- CHECK_EQ(thread_->GetState(), kNative);
- Locks::mutator_lock_->AssertNotHeld(thread_);
- thread_->SetState(kWaitingInMainDebuggerLoop);
-
- /*
- * Loop forever if we're in server mode, processing connections. In
- * non-server mode, we bail out of the thread when the debugger drops
- * us.
- *
- * We broadcast a notification when a debugger attaches, after we
- * successfully process the handshake.
- */
- while (run) {
- if (options_->server) {
- /*
- * Block forever, waiting for a connection. To support the
- * "timeout=xxx" option we'll need to tweak this.
- */
- if (!netState->Accept()) {
- break;
- }
- } else {
- /*
- * If we're not acting as a server, we need to connect out to the
- * debugger. To support the "timeout=xxx" option we need to
- * have a timeout if the handshake reply isn't received in a
- * reasonable amount of time.
- */
- if (!netState->Establish(options_)) {
- /* wake anybody who was waiting for us to succeed */
- MutexLock mu(thread_, attach_lock_);
- debug_thread_id_ = static_cast<ObjectId>(-1);
- attach_cond_.Broadcast(thread_);
- break;
- }
- }
-
- /* prep debug code to handle the new connection */
- Dbg::Connected();
-
- /* process requests until the debugger drops */
- bool first = true;
- while (!Dbg::IsDisposed()) {
- // sanity check -- shouldn't happen?
- CHECK_EQ(thread_->GetState(), kWaitingInMainDebuggerLoop);
-
- if (!netState->ProcessIncoming()) {
- /* blocking read */
- break;
- }
-
- if (should_exit_) {
- exit(exit_status_);
- }
-
- if (first && !netState->IsAwaitingHandshake()) {
- /* handshake worked, tell the interpreter that we're active */
- first = false;
-
- /* set thread ID; requires object registry to be active */
- {
- ScopedObjectAccess soa(thread_);
- debug_thread_id_ = Dbg::GetThreadSelfId();
- }
-
- /* wake anybody who's waiting for us */
- MutexLock mu(thread_, attach_lock_);
- attach_cond_.Broadcast(thread_);
- }
- }
-
- netState->Close();
-
- if (ddm_is_active_) {
- ddm_is_active_ = false;
-
- /* broadcast the disconnect; must be in RUNNING state */
- ScopedObjectAccess soa(thread_);
- Dbg::DdmDisconnected();
- }
-
- {
- ScopedObjectAccess soa(thread_);
-
- // Release session state, e.g. remove breakpoint instructions.
- ResetState();
- }
- // Tell the rest of the runtime that the debugger is no longer around.
- Dbg::Disconnected();
-
- /* if we had threads suspended, resume them now */
- Dbg::UndoDebuggerSuspensions();
-
- /* if we connected out, this was a one-shot deal */
- if (!options_->server) {
- run = false;
- }
- }
-
- /* back to native, for thread shutdown */
- CHECK_EQ(thread_->GetState(), kWaitingInMainDebuggerLoop);
- thread_->SetState(kNative);
-
- VLOG(jdwp) << "JDWP: thread detaching and exiting...";
- runtime->DetachCurrentThread();
-}
-
-void JdwpState::NotifyDdmsActive() {
- if (!ddm_is_active_) {
- ddm_is_active_ = true;
- Dbg::DdmConnected();
- }
-}
-
-Thread* JdwpState::GetDebugThread() {
- return thread_;
-}
-
-/*
- * Support routines for waitForDebugger().
- *
- * We can't have a trivial "waitForDebugger" function that returns the
- * instant the debugger connects, because we run the risk of executing code
- * before the debugger has had a chance to configure breakpoints or issue
- * suspend calls. It would be nice to just sit in the suspended state, but
- * most debuggers don't expect any threads to be suspended when they attach.
- *
- * There's no JDWP event we can post to tell the debugger, "we've stopped,
- * and we like it that way". We could send a fake breakpoint, which should
- * cause the debugger to immediately send a resume, but the debugger might
- * send the resume immediately or might throw an exception of its own upon
- * receiving a breakpoint event that it didn't ask for.
- *
- * What we really want is a "wait until the debugger is done configuring
- * stuff" event. We can approximate this with a "wait until the debugger
- * has been idle for a brief period".
- */
-
-/*
- * Return the time, in milliseconds, since the last debugger activity.
- *
- * Returns -1 if no debugger is attached, or 0 if we're in the middle of
- * processing a debugger request.
- */
-int64_t JdwpState::LastDebuggerActivity() {
- if (!Dbg::IsDebuggerActive()) {
- LOG(WARNING) << "no active debugger";
- return -1;
- }
-
- int64_t last = last_activity_time_ms_.load(std::memory_order_seq_cst);
-
- /* initializing or in the middle of something? */
- if (last == 0) {
- VLOG(jdwp) << "+++ last=busy";
- return 0;
- }
-
- /* now get the current time */
- int64_t now = MilliTime();
- CHECK_GE(now, last);
-
- VLOG(jdwp) << "+++ debugger interval=" << (now - last);
- return now - last;
-}
-
-void JdwpState::ExitAfterReplying(int exit_status) {
- LOG(WARNING) << "Debugger told VM to exit with status " << exit_status;
- should_exit_ = true;
- exit_status_ = exit_status;
-}
-
-std::ostream& operator<<(std::ostream& os, const JdwpLocation& rhs) {
- os << "JdwpLocation["
- << Dbg::GetClassName(rhs.class_id) << "." << Dbg::GetMethodName(rhs.method_id)
- << "@" << StringPrintf("%#" PRIx64, rhs.dex_pc) << " " << rhs.type_tag << "]";
- return os;
-}
-
-bool operator==(const JdwpLocation& lhs, const JdwpLocation& rhs) {
- return lhs.dex_pc == rhs.dex_pc && lhs.method_id == rhs.method_id &&
- lhs.class_id == rhs.class_id && lhs.type_tag == rhs.type_tag;
-}
-
-bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs) {
- return !(lhs == rhs);
-}
-
-bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs) {
- if (&lhs == &rhs) {
- return true;
- }
-
- return lhs.transport == rhs.transport &&
- lhs.server == rhs.server &&
- lhs.suspend == rhs.suspend &&
- lhs.host == rhs.host &&
- lhs.port == rhs.port;
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_options_test.cc b/runtime/jdwp/jdwp_options_test.cc
deleted file mode 100644
index 10c52e8cf8d..00000000000
--- a/runtime/jdwp/jdwp_options_test.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jdwp.h"
-
-#include "gtest/gtest.h"
-
-namespace art {
-namespace JDWP {
-
-TEST(JdwpOptionsTest, Options) {
- {
- /*
- * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
- */
- JDWP::JdwpOptions opt = JDWP::JdwpOptions();
- const char *opt_args = "transport=dt_socket,address=8000,server=y";
-
- EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
- EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportSocket);
- EXPECT_EQ(opt.port, 8000u);
- EXPECT_EQ(opt.server, true);
- EXPECT_EQ(opt.suspend, false);
- }
-
- {
- /*
- * Example: transport=dt_socket,address=localhost:6500,server=n
- */
- JDWP::JdwpOptions opt = JDWP::JdwpOptions();
- const char *opt_args = "transport=dt_socket,address=localhost:6500,server=y";
-
- EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
- EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportSocket);
- EXPECT_EQ(opt.port, 6500u);
- EXPECT_EQ(opt.host, "localhost");
- EXPECT_EQ(opt.server, true);
- EXPECT_EQ(opt.suspend, false);
- }
-
- {
- /*
- * Example: transport=dt_android_adb,server=n,suspend=y;
- */
- JDWP::JdwpOptions opt = JDWP::JdwpOptions();
- const char *opt_args = "transport=dt_android_adb,server=y";
-
- EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
- EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportAndroidAdb);
- EXPECT_EQ(opt.port, 0xFFFF);
- EXPECT_EQ(opt.host, "");
- EXPECT_EQ(opt.server, true);
- EXPECT_EQ(opt.suspend, false);
- }
-
- /*
- * Test failures
- */
- JDWP::JdwpOptions opt = JDWP::JdwpOptions();
- EXPECT_FALSE(ParseJdwpOptions("help", &opt));
- EXPECT_FALSE(ParseJdwpOptions("blabla", &opt));
- EXPECT_FALSE(ParseJdwpOptions("transport=dt_android_adb,server=n", &opt));
-}
-
-} // namespace JDWP
-} // namespace art
diff --git a/runtime/jdwp/jdwp_priv.h b/runtime/jdwp/jdwp_priv.h
deleted file mode 100644
index 4e1bda899fc..00000000000
--- a/runtime/jdwp/jdwp_priv.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/*
- * JDWP internal interfaces.
- */
-#ifndef ART_RUNTIME_JDWP_JDWP_PRIV_H_
-#define ART_RUNTIME_JDWP_JDWP_PRIV_H_
-
-#include "debugger.h"
-#include "jdwp/jdwp.h"
-#include "jdwp/jdwp_event.h"
-
-#include <pthread.h>
-#include <sys/uio.h>
-
-/*
- * JDWP constants.
- */
-static constexpr size_t kJDWPHeaderSizeOffset = 0U;
-static constexpr size_t kJDWPHeaderIdOffset = 4U;
-static constexpr size_t kJDWPHeaderFlagsOffset = 8U;
-static constexpr size_t kJDWPHeaderErrorCodeOffset = 9U;
-static constexpr size_t kJDWPHeaderCmdSetOffset = 9U;
-static constexpr size_t kJDWPHeaderCmdOffset = 10U;
-static constexpr size_t kJDWPHeaderLen = 11U;
-static constexpr uint8_t kJDWPFlagReply = 0x80;
-
-static constexpr const char kMagicHandshake[] = "JDWP-Handshake";
-static constexpr size_t kMagicHandshakeLen = sizeof(kMagicHandshake) - 1;
-
-/* Invoke commands */
-static constexpr uint8_t kJDWPClassTypeCmdSet = 3U;
-static constexpr uint8_t kJDWPClassTypeInvokeMethodCmd = 3U;
-static constexpr uint8_t kJDWPClassTypeNewInstanceCmd = 4U;
-static constexpr uint8_t kJDWPInterfaceTypeCmdSet = 5U;
-static constexpr uint8_t kJDWPInterfaceTypeInvokeMethodCmd = 1U;
-static constexpr uint8_t kJDWPObjectReferenceCmdSet = 9U;
-static constexpr uint8_t kJDWPObjectReferenceInvokeCmd = 6U;
-
-/* Event command */
-static constexpr uint8_t kJDWPEventCmdSet = 64U;
-static constexpr uint8_t kJDWPEventCompositeCmd = 100U;
-
-/* DDM support */
-static constexpr uint8_t kJDWPDdmCmdSet = 199U; // 0xc7, or 'G'+128
-static constexpr uint8_t kJDWPDdmCmd = 1U;
-
-namespace art {
-
-namespace JDWP {
-
-struct JdwpState;
-
-bool InitSocketTransport(JdwpState*, const JdwpOptions*);
-bool InitAdbTransport(JdwpState*, const JdwpOptions*);
-
-/*
- * Base class for the adb and socket JdwpNetState implementations.
- */
-class JdwpNetStateBase {
- public:
- explicit JdwpNetStateBase(JdwpState*);
- virtual ~JdwpNetStateBase();
-
- virtual bool Accept() = 0;
- virtual bool Establish(const JdwpOptions*) = 0;
- virtual void Shutdown() = 0;
- virtual bool ProcessIncoming() = 0;
-
- void ConsumeBytes(size_t byte_count);
-
- bool IsConnected();
-
- bool IsAwaitingHandshake();
-
- void Close();
-
- ssize_t WritePacket(ExpandBuf* pReply, size_t length) REQUIRES(!socket_lock_);
- ssize_t WriteBufferedPacket(const std::vector<iovec>& iov) REQUIRES(!socket_lock_);
- Mutex* GetSocketLock() {
- return &socket_lock_;
- }
- ssize_t WriteBufferedPacketLocked(const std::vector<iovec>& iov);
-
- int clientSock; // Active connection to debugger.
-
- int wake_pipe_[2]; // Used to break out of select.
-
- uint8_t input_buffer_[8192];
- size_t input_count_;
-
- protected:
- bool HaveFullPacket();
-
- bool MakePipe();
- void WakePipe();
-
- void SetAwaitingHandshake(bool new_state);
-
- JdwpState* state_;
-
- private:
- // Used to serialize writes to the socket.
- Mutex socket_lock_;
-
- // Are we waiting for the JDWP handshake?
- bool awaiting_handshake_;
-};
-
-} // namespace JDWP
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_JDWP_PRIV_H_
diff --git a/runtime/jdwp/jdwp_request.cc b/runtime/jdwp/jdwp_request.cc
deleted file mode 100644
index a77962e2fa0..00000000000
--- a/runtime/jdwp/jdwp_request.cc
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jdwp/jdwp.h"
-
-#include <inttypes.h>
-
-#include "android-base/stringprintf.h"
-
-#include "jdwp/jdwp_priv.h"
-
-namespace art {
-
-namespace JDWP {
-
-Request::Request(const uint8_t* bytes, uint32_t available) : p_(bytes) {
- byte_count_ = Read4BE();
- end_ = bytes + byte_count_;
- CHECK_LE(byte_count_, available);
-
- id_ = Read4BE();
- int8_t flags = Read1();
- if ((flags & kJDWPFlagReply) != 0) {
- LOG(FATAL) << "reply?!";
- }
-
- command_set_ = Read1();
- command_ = Read1();
-}
-
-Request::~Request() {
-}
-
-void Request::CheckConsumed() {
- if (p_ < end_) {
- CHECK(p_ == end_) << "read too few bytes: " << (end_ - p_);
- } else if (p_ > end_) {
- CHECK(p_ == end_) << "read too many bytes: " << (p_ - end_);
- }
-}
-
-std::string Request::ReadUtf8String() {
- uint32_t length = Read4BE();
- std::string s;
- s.resize(length);
- memcpy(&s[0], p_, length);
- p_ += length;
- VLOG(jdwp) << " string \"" << s << "\"";
- return s;
-}
-
-// Helper function: read a variable-width value from the input buffer.
-uint64_t Request::ReadValue(size_t width) {
- uint64_t value = -1;
- switch (width) {
- case 1: value = Read1(); break;
- case 2: value = Read2BE(); break;
- case 4: value = Read4BE(); break;
- case 8: value = Read8BE(); break;
- default: LOG(FATAL) << width;
- }
- return value;
-}
-
-int32_t Request::ReadSigned32(const char* what) {
- int32_t value = static_cast<int32_t>(Read4BE());
- VLOG(jdwp) << " " << what << " " << value;
- return value;
-}
-
-uint32_t Request::ReadUnsigned32(const char* what) {
- uint32_t value = Read4BE();
- VLOG(jdwp) << " " << what << " " << value;
- return value;
-}
-
-FieldId Request::ReadFieldId() {
- FieldId id = Read8BE();
- VLOG(jdwp) << " field id " << DescribeField(id);
- return id;
-}
-
-MethodId Request::ReadMethodId() {
- MethodId id = Read8BE();
- VLOG(jdwp) << " method id " << DescribeMethod(id);
- return id;
-}
-
-ObjectId Request::ReadObjectId(const char* specific_kind) {
- ObjectId id = Read8BE();
- VLOG(jdwp) << android::base::StringPrintf(" %s id %#" PRIx64, specific_kind, id);
- return id;
-}
-
-ObjectId Request::ReadArrayId() {
- return ReadObjectId("array");
-}
-
-ObjectId Request::ReadObjectId() {
- return ReadObjectId("object");
-}
-
-ObjectId Request::ReadThreadId() {
- return ReadObjectId("thread");
-}
-
-ObjectId Request::ReadThreadGroupId() {
- return ReadObjectId("thread group");
-}
-
-RefTypeId Request::ReadRefTypeId() {
- RefTypeId id = Read8BE();
- VLOG(jdwp) << " ref type id " << DescribeRefTypeId(id);
- return id;
-}
-
-FrameId Request::ReadFrameId() {
- FrameId id = Read8BE();
- VLOG(jdwp) << " frame id " << id;
- return id;
-}
-
-JdwpTag Request::ReadTag() {
- return ReadEnum1<JdwpTag>("tag");
-}
-
-JdwpTypeTag Request::ReadTypeTag() {
- return ReadEnum1<JdwpTypeTag>("type tag");
-}
-
-JdwpLocation Request::ReadLocation() {
- JdwpLocation location;
- memset(&location, 0, sizeof(location)); // Allows memcmp(3) later.
- location.type_tag = ReadTypeTag();
- location.class_id = ReadObjectId("class");
- location.method_id = ReadMethodId();
- location.dex_pc = Read8BE();
- VLOG(jdwp) << " location " << location;
- return location;
-}
-
-JdwpModKind Request::ReadModKind() {
- return ReadEnum1<JdwpModKind>("mod kind");
-}
-
-uint8_t Request::Read1() {
- return *p_++;
-}
-
-uint16_t Request::Read2BE() {
- uint16_t result = p_[0] << 8 | p_[1];
- p_ += 2;
- return result;
-}
-
-uint32_t Request::Read4BE() {
- uint32_t result = p_[0] << 24;
- result |= p_[1] << 16;
- result |= p_[2] << 8;
- result |= p_[3];
- p_ += 4;
- return result;
-}
-
-uint64_t Request::Read8BE() {
- uint64_t high = Read4BE();
- uint64_t low = Read4BE();
- return (high << 32) | low;
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/jdwp_socket.cc b/runtime/jdwp/jdwp_socket.cc
deleted file mode 100644
index b8b0e16fae7..00000000000
--- a/runtime/jdwp/jdwp_socket.cc
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "android-base/stringprintf.h"
-
-#include "base/logging.h" // For VLOG.
-#include "jdwp/jdwp_priv.h"
-
-namespace art {
-
-namespace JDWP {
-
-static constexpr uint16_t kBasePort = 8000;
-static constexpr uint16_t kMaxPort = 8040;
-
-/*
- * JDWP network state.
- *
- * We only talk to one debugger at a time.
- */
-struct JdwpSocketState : public JdwpNetStateBase {
- uint16_t listenPort;
- int listenSock; /* listen for connection from debugger */
-
- explicit JdwpSocketState(JdwpState* state)
- : JdwpNetStateBase(state),
- listenPort(0U),
- listenSock(-1),
- remote_port_(0U) {
- }
-
- bool Accept() override;
- bool Establish(const JdwpOptions*) override;
- void Shutdown() override;
- bool ProcessIncoming() override;
-
- private:
- in_addr remote_addr_;
- uint16_t remote_port_;
-};
-
-static JdwpSocketState* SocketStartup(JdwpState* state, uint16_t port, bool probe);
-
-/*
- * Set up some stuff for transport=dt_socket.
- */
-bool InitSocketTransport(JdwpState* state, const JdwpOptions* options) {
- uint16_t port = options->port;
-
- if (options->server) {
- if (options->port != 0) {
- /* try only the specified port */
- state->netState = SocketStartup(state, port, false);
- } else {
- /* scan through a range of ports, binding to the first available */
- for (port = kBasePort; port <= kMaxPort; port++) {
- state->netState = SocketStartup(state, port, true);
- if (state->netState != nullptr) {
- break;
- }
- }
- }
- if (state->netState == nullptr) {
- LOG(ERROR) << "JDWP net startup failed (req port=" << options->port << ")";
- return false;
- }
- } else {
- state->netState = SocketStartup(state, 0, false);
- }
-
- if (options->suspend) {
- LOG(INFO) << "JDWP will wait for debugger on port " << port;
- } else {
- LOG(INFO) << "JDWP will " << (options->server ? "listen" : "connect") << " on port " << port;
- }
-
- return true;
-}
-
-/*
- * Initialize JDWP stuff.
- *
- * Allocates a new state structure. If "port" is non-zero, this also
- * tries to bind to a listen port. If "port" is zero, we assume
- * we're preparing for an outbound connection, and return without binding
- * to anything.
- *
- * This may be called several times if we're probing for a port.
- *
- * Returns 0 on success.
- */
-static JdwpSocketState* SocketStartup(JdwpState* state, uint16_t port, bool probe) {
- JdwpSocketState* netState = new JdwpSocketState(state);
- if (port == 0) {
- return netState;
- }
-
- netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (netState->listenSock < 0) {
- PLOG(probe ? ::android::base::ERROR : ::android::base::FATAL) << "Socket create failed";
- goto fail;
- }
-
- /* allow immediate re-use */
- {
- int one = 1;
- if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
- PLOG(probe ? ::android::base::ERROR : ::android::base::FATAL)
- << "setsockopt(SO_REUSEADDR) failed";
- goto fail;
- }
- }
-
- union {
- sockaddr_in addrInet;
- sockaddr addrPlain;
- } addr;
- addr.addrInet.sin_family = AF_INET;
- addr.addrInet.sin_port = htons(port);
- inet_aton("127.0.0.1", &addr.addrInet.sin_addr);
-
- if (bind(netState->listenSock, &addr.addrPlain, sizeof(addr)) != 0) {
- PLOG(probe ? ::android::base::ERROR : ::android::base::FATAL)
- << "Attempt to bind to port " << port << " failed";
- goto fail;
- }
-
- netState->listenPort = port;
-
- if (listen(netState->listenSock, 5) != 0) {
- PLOG(probe ? ::android::base::ERROR : ::android::base::FATAL) << "Listen failed";
- goto fail;
- }
-
- return netState;
-
- fail:
- netState->Shutdown();
- delete netState;
- return nullptr;
-}
-
-/*
- * Shut down JDWP listener. Don't free state.
- *
- * This may be called from a non-JDWP thread as part of shutting the
- * JDWP thread down.
- *
- * (This is currently called several times during startup as we probe
- * for an open port.)
- */
-void JdwpSocketState::Shutdown() {
- int local_listenSock = this->listenSock;
- int local_clientSock = this->clientSock;
-
- /* clear these out so it doesn't wake up and try to reuse them */
- this->listenSock = this->clientSock = -1;
-
- /* "shutdown" dislodges blocking read() and accept() calls */
- if (local_listenSock != -1) {
- shutdown(local_listenSock, SHUT_RDWR);
- close(local_listenSock);
- }
- if (local_clientSock != -1) {
- shutdown(local_clientSock, SHUT_RDWR);
- close(local_clientSock);
- }
-
- WakePipe();
-}
-
-/*
- * Disable the TCP Nagle algorithm, which delays transmission of outbound
- * packets until the previous transmissions have been acked. JDWP does a
- * lot of back-and-forth with small packets, so this may help.
- */
-static int SetNoDelay(int fd) {
- int on = 1;
- int cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
- CHECK_EQ(cc, 0);
- return cc;
-}
-
-/*
- * Accept a connection. This will block waiting for somebody to show up.
- * If that's not desirable, use checkConnection() to make sure something
- * is pending.
- */
-bool JdwpSocketState::Accept() {
- union {
- sockaddr_in addrInet;
- sockaddr addrPlain;
- } addr;
- socklen_t addrlen;
- int sock;
-
- if (listenSock < 0) {
- return false; /* you're not listening! */
- }
-
- CHECK_EQ(clientSock, -1); /* must not already be talking */
-
- addrlen = sizeof(addr);
- do {
- sock = accept(listenSock, &addr.addrPlain, &addrlen);
- if (sock < 0 && errno != EINTR) {
- // When we call shutdown() on the socket, accept() returns with
- // EINVAL. Don't gripe about it.
- if (errno == EINVAL) {
- if (VLOG_IS_ON(jdwp)) {
- PLOG(ERROR) << "accept failed";
- }
- } else {
- PLOG(ERROR) << "accept failed";
- return false;
- }
- }
- } while (sock < 0);
-
- remote_addr_ = addr.addrInet.sin_addr;
- remote_port_ = ntohs(addr.addrInet.sin_port);
- VLOG(jdwp) << "+++ accepted connection from " << inet_ntoa(remote_addr_) << ":" << remote_port_;
-
- clientSock = sock;
- SetAwaitingHandshake(true);
- input_count_ = 0;
-
- VLOG(jdwp) << "Setting TCP_NODELAY on accepted socket";
- SetNoDelay(clientSock);
-
- if (!MakePipe()) {
- return false;
- }
-
- return true;
-}
-
-/*
- * Create a connection to a waiting debugger.
- */
-bool JdwpSocketState::Establish(const JdwpOptions* options) {
- union {
- sockaddr_in addrInet;
- sockaddr addrPlain;
- } addr;
- hostent* pEntry;
-
- CHECK(!options->server);
- CHECK(!options->host.empty());
- CHECK_NE(options->port, 0);
-
- /*
- * Start by resolving the host name.
- */
-#if defined(__linux__)
- // Initial size of the work buffer used in gethostbyname_r.
- //
- // The call to gethostbyname_r below requires a user-allocated buffer,
- // the size of which depends on the system. The initial implementation
- // used to use a 128-byte buffer, but that was not enough on some
- // systems (maybe because of IPv6), causing failures in JDWP host
- // testing; thus it was increased to 256.
- //
- // However, we should not use a fixed size: gethostbyname_r's
- // documentation states that if the work buffer is too small (i.e. if
- // gethostbyname_r returns `ERANGE`), then the function should be
- // called again with a bigger buffer. Which we do now, starting with
- // an initial 256-byte buffer, and doubling it until gethostbyname_r
- // accepts this size.
- static constexpr size_t kInitialAuxBufSize = 256;
-
- std::vector<char> auxBuf(kInitialAuxBufSize);
- hostent he;
- int error;
- int cc;
- while ((cc = gethostbyname_r(
- options->host.c_str(), &he, auxBuf.data(), auxBuf.size(), &pEntry, &error))
- == ERANGE) {
- // The work buffer `auxBuf` is too small; enlarge it.
- auxBuf.resize(auxBuf.size() * 2);
- }
- if (cc != 0 || pEntry == nullptr) {
- LOG(WARNING) << "gethostbyname_r('" << options->host << "') failed: " << hstrerror(error);
- return false;
- }
-#else
- h_errno = 0;
- pEntry = gethostbyname(options->host.c_str());
- if (pEntry == nullptr) {
- PLOG(WARNING) << "gethostbyname('" << options->host << "') failed";
- return false;
- }
-#endif
-
- /* copy it out ASAP to minimize risk of multithreaded annoyances */
- memcpy(&addr.addrInet.sin_addr, pEntry->h_addr, pEntry->h_length);
- addr.addrInet.sin_family = pEntry->h_addrtype;
-
- addr.addrInet.sin_port = htons(options->port);
-
- LOG(INFO) << "Connecting out to " << inet_ntoa(addr.addrInet.sin_addr) << ":"
- << ntohs(addr.addrInet.sin_port);
-
- /*
- * Create a socket.
- */
- clientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (clientSock < 0) {
- PLOG(ERROR) << "Unable to create socket";
- return false;
- }
-
- /*
- * Try to connect.
- */
- if (connect(clientSock, &addr.addrPlain, sizeof(addr)) != 0) {
- PLOG(ERROR) << "Unable to connect to " << inet_ntoa(addr.addrInet.sin_addr) << ":"
- << ntohs(addr.addrInet.sin_port);
- close(clientSock);
- clientSock = -1;
- return false;
- }
-
- LOG(INFO) << "Connection established to " << options->host << " ("
- << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port) << ")";
- SetAwaitingHandshake(true);
- input_count_ = 0;
-
- SetNoDelay(clientSock);
-
- if (!MakePipe()) {
- return false;
- }
-
- return true;
-}
-
-/*
- * Process incoming data. If no data is available, this will block until
- * some arrives.
- *
- * If we get a full packet, handle it.
- *
- * To take some of the mystery out of life, we want to reject incoming
- * connections if we already have a debugger attached. If we don't, the
- * debugger will just mysteriously hang until it times out. We could just
- * close the listen socket, but there's a good chance we won't be able to
- * bind to the same port again, which would confuse utilities.
- *
- * Returns "false" on error (indicating that the connection has been severed),
- * "true" if things are still okay.
- */
-bool JdwpSocketState::ProcessIncoming() {
- int readCount;
-
- CHECK_NE(clientSock, -1);
-
- if (!HaveFullPacket()) {
- /* read some more, looping until we have data */
- errno = 0;
- while (true) {
- int selCount;
- fd_set readfds;
- int maxfd = -1;
- int fd;
-
- FD_ZERO(&readfds);
-
- /* configure fds; note these may get zapped by another thread */
- fd = listenSock;
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- }
- fd = clientSock;
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- }
- fd = wake_pipe_[0];
- if (fd >= 0) {
- FD_SET(fd, &readfds);
- if (maxfd < fd) {
- maxfd = fd;
- }
- } else {
- LOG(INFO) << "NOTE: entering select w/o wakepipe";
- }
-
- if (maxfd < 0) {
- VLOG(jdwp) << "+++ all fds are closed";
- return false;
- }
-
- /*
- * Select blocks until it sees activity on the file descriptors.
- * Closing the local file descriptor does not count as activity,
- * so we can't rely on that to wake us up (it works for read()
- * and accept(), but not select()).
- *
- * We can do one of three things: (1) send a signal and catch
- * EINTR, (2) open an additional fd ("wake pipe") and write to
- * it when it's time to exit, or (3) time out periodically and
- * re-issue the select. We're currently using #2, as it's more
- * reliable than #1 and generally better than #3. Wastes two fds.
- */
- selCount = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr);
- if (selCount < 0) {
- if (errno == EINTR) {
- continue;
- }
- PLOG(ERROR) << "select failed";
- goto fail;
- }
-
- if (wake_pipe_[0] >= 0 && FD_ISSET(wake_pipe_[0], &readfds)) {
- if (listenSock >= 0) {
- LOG(ERROR) << "Exit wake set, but not exiting?";
- } else {
- VLOG(jdwp) << "Got wake-up signal, bailing out of select";
- }
- goto fail;
- }
- if (listenSock >= 0 && FD_ISSET(listenSock, &readfds)) {
- LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
- union {
- sockaddr_in addrInet;
- sockaddr addrPlain;
- } addr;
- socklen_t addrlen;
- int tmpSock;
- tmpSock = accept(listenSock, &addr.addrPlain, &addrlen);
- if (tmpSock < 0) {
- LOG(INFO) << "Weird -- accept failed";
- } else {
- close(tmpSock);
- }
- }
- if (clientSock >= 0 && FD_ISSET(clientSock, &readfds)) {
- readCount =
- read(clientSock, input_buffer_ + input_count_, sizeof(input_buffer_) - input_count_);
- if (readCount < 0) {
- /* read failed */
- if (errno != EINTR) {
- goto fail;
- }
- VLOG(jdwp) << "+++ EINTR hit";
- return true;
- } else if (readCount == 0) {
- /* EOF hit -- far end went away */
- VLOG(jdwp) << "+++ peer disconnected";
- goto fail;
- } else {
- break;
- }
- }
- }
-
- input_count_ += readCount;
- if (!HaveFullPacket()) {
- return true; /* still not there yet */
- }
- }
-
- /*
- * Special-case the initial handshake. For some bizarre reason we're
- * expected to emulate bad tty settings by echoing the request back
- * exactly as it was sent. Note the handshake is always initiated by
- * the debugger, no matter who connects to whom.
- *
- * Other than this one case, the protocol [claims to be] stateless.
- */
- if (IsAwaitingHandshake()) {
- if (memcmp(input_buffer_, kMagicHandshake, kMagicHandshakeLen) != 0) {
- LOG(ERROR) << android::base::StringPrintf("ERROR: bad handshake '%.14s'", input_buffer_);
- goto fail;
- }
-
- errno = 0;
- int cc = TEMP_FAILURE_RETRY(write(clientSock, input_buffer_, kMagicHandshakeLen));
- if (cc != kMagicHandshakeLen) {
- PLOG(ERROR) << "Failed writing handshake bytes ("
- << cc << " of " << kMagicHandshakeLen << ")";
- goto fail;
- }
-
- ConsumeBytes(kMagicHandshakeLen);
- SetAwaitingHandshake(false);
- VLOG(jdwp) << "+++ handshake complete";
- return true;
- }
-
- /*
- * Handle this packet.
- */
- return state_->HandlePacket();
-
- fail:
- Close();
- return false;
-}
-
-} // namespace JDWP
-
-} // namespace art
diff --git a/runtime/jdwp/object_registry-inl.h b/runtime/jdwp/object_registry-inl.h
deleted file mode 100644
index 7a9067bd536..00000000000
--- a/runtime/jdwp/object_registry-inl.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ART_RUNTIME_JDWP_OBJECT_REGISTRY_INL_H_
-#define ART_RUNTIME_JDWP_OBJECT_REGISTRY_INL_H_
-
-#include "object_registry.h"
-
-#include "obj_ptr-inl.h"
-
-namespace art {
-
-template<typename T>
-inline ObjPtr<T> ObjectRegistry::Get(JDWP::ObjectId id, JDWP::JdwpError* error) {
- if (id == 0) {
- *error = JDWP::ERR_NONE;
- return nullptr;
- }
- return ObjPtr<T>::DownCast(InternalGet(id, error));
-}
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_OBJECT_REGISTRY_INL_H_
diff --git a/runtime/jdwp/object_registry.cc b/runtime/jdwp/object_registry.cc
deleted file mode 100644
index e18c526fc33..00000000000
--- a/runtime/jdwp/object_registry.cc
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "object_registry.h"
-
-#include "handle_scope-inl.h"
-#include "jni/jni_internal.h"
-#include "mirror/class.h"
-#include "mirror/throwable.h"
-#include "obj_ptr-inl.h"
-#include "scoped_thread_state_change-inl.h"
-
-namespace art {
-
-std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs) {
- os << "ObjectRegistryEntry[" << rhs.jni_reference_type
- << ",reference=" << rhs.jni_reference
- << ",count=" << rhs.reference_count
- << ",id=" << rhs.id << "]";
- return os;
-}
-
-ObjectRegistry::ObjectRegistry()
- : lock_("ObjectRegistry lock", kJdwpObjectRegistryLock), next_id_(1) {
- Locks::AddToExpectedMutexesOnWeakRefAccess(&lock_);
-}
-
-ObjectRegistry::~ObjectRegistry() {
- Locks::RemoveFromExpectedMutexesOnWeakRefAccess(&lock_);
-}
-
-JDWP::RefTypeId ObjectRegistry::AddRefType(ObjPtr<mirror::Class> c) {
- return Add(c);
-}
-
-JDWP::RefTypeId ObjectRegistry::AddRefType(Handle<mirror::Class> c_h) {
- return Add(c_h);
-}
-
-JDWP::ObjectId ObjectRegistry::Add(ObjPtr<mirror::Object> o) {
- if (o == nullptr) {
- return 0;
- }
- Thread* const self = Thread::Current();
- StackHandleScope<1> hs(self);
- return InternalAdd(hs.NewHandle(o));
-}
-
-// Template instantiations must be declared below.
-template<class T>
-JDWP::ObjectId ObjectRegistry::Add(Handle<T> obj_h) {
- if (obj_h == nullptr) {
- return 0;
- }
- return InternalAdd(obj_h);
-}
-
-// Explicit template instantiation.
-template
-REQUIRES_SHARED(Locks::mutator_lock_)
-REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
-JDWP::ObjectId ObjectRegistry::Add(Handle<mirror::Object> obj_h);
-
-template
-REQUIRES_SHARED(Locks::mutator_lock_)
-REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_)
-JDWP::ObjectId ObjectRegistry::Add(Handle<mirror::Throwable> obj_h);
-
-template<class T>
-JDWP::ObjectId ObjectRegistry::InternalAdd(Handle<T> obj_h) {
- CHECK(obj_h != nullptr);
-
- Thread* const self = Thread::Current();
- self->AssertNoPendingException();
- // Object::IdentityHashCode may cause these locks to be held so check we do not already
- // hold them.
- Locks::thread_list_lock_->AssertNotHeld(self);
- Locks::thread_suspend_count_lock_->AssertNotHeld(self);
-
- // Call IdentityHashCode here to avoid a lock level violation between lock_ and monitor_lock.
- int32_t identity_hash_code = obj_h->IdentityHashCode();
-
- ScopedObjectAccessUnchecked soa(self);
- MutexLock mu(soa.Self(), lock_);
- ObjectRegistryEntry* entry = nullptr;
- if (ContainsLocked(soa.Self(), obj_h.Get(), identity_hash_code, &entry)) {
- // This object was already in our map.
- ++entry->reference_count;
- } else {
- entry = new ObjectRegistryEntry;
- entry->jni_reference_type = JNIWeakGlobalRefType;
- entry->jni_reference = nullptr;
- entry->reference_count = 0;
- entry->id = 0;
- entry->identity_hash_code = identity_hash_code;
- object_to_entry_.insert(std::make_pair(identity_hash_code, entry));
-
- // This object isn't in the registry yet, so add it.
- JNIEnv* env = soa.Env();
-
- jobject local_reference = soa.AddLocalReference<jobject>(obj_h.Get());
-
- entry->jni_reference_type = JNIWeakGlobalRefType;
- entry->jni_reference = env->NewWeakGlobalRef(local_reference);
- entry->reference_count = 1;
- entry->id = next_id_++;
-
- id_to_entry_.Put(entry->id, entry);
-
- env->DeleteLocalRef(local_reference);
- }
- return entry->id;
-}
-
-bool ObjectRegistry::ContainsLocked(Thread* self,
- ObjPtr<mirror::Object> o,
- int32_t identity_hash_code,
- ObjectRegistryEntry** out_entry) {
- DCHECK(o != nullptr);
- for (auto it = object_to_entry_.lower_bound(identity_hash_code), end = object_to_entry_.end();
- it != end && it->first == identity_hash_code; ++it) {
- ObjectRegistryEntry* entry = it->second;
- if (o == self->DecodeJObject(entry->jni_reference)) {
- if (out_entry != nullptr) {
- *out_entry = entry;
- }
- return true;
- }
- }
- return false;
-}
-
-void ObjectRegistry::Clear() {
- Thread* const self = Thread::Current();
-
- // We must not hold the mutator lock exclusively if we want to delete weak global
- // references. Otherwise this can lead to a deadlock with a running GC:
- // 1. GC thread disables access to weak global references, then releases
- // mutator lock.
- // 2. JDWP thread takes mutator lock exclusively after suspending all
- // threads.
- // 3. GC thread waits for shared mutator lock which is held by JDWP
- // thread.
- // 4. JDWP thread clears weak global references but need to wait for GC
- // thread to re-enable access to them.
- Locks::mutator_lock_->AssertNotExclusiveHeld(self);
-
- MutexLock mu(self, lock_);
- VLOG(jdwp) << "Object registry contained " << object_to_entry_.size() << " entries";
- // Delete all the JNI references.
- JNIEnv* env = self->GetJniEnv();
- for (const auto& pair : object_to_entry_) {
- const ObjectRegistryEntry* entry = pair.second;
- if (entry->jni_reference_type == JNIWeakGlobalRefType) {
- env->DeleteWeakGlobalRef(entry->jni_reference);
- } else {
- env->DeleteGlobalRef(entry->jni_reference);
- }
- delete entry;
- }
- // Clear the maps.
- object_to_entry_.clear();
- id_to_entry_.clear();
-}
-
-ObjPtr<mirror::Object> ObjectRegistry::InternalGet(JDWP::ObjectId id, JDWP::JdwpError* error) {
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- if (it == id_to_entry_.end()) {
- *error = JDWP::ERR_INVALID_OBJECT;
- return nullptr;
- }
- ObjectRegistryEntry& entry = *it->second;
- *error = JDWP::ERR_NONE;
- return self->DecodeJObject(entry.jni_reference);
-}
-
-jobject ObjectRegistry::GetJObject(JDWP::ObjectId id) {
- if (id == 0) {
- return nullptr;
- }
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- CHECK(it != id_to_entry_.end()) << id;
- ObjectRegistryEntry& entry = *it->second;
- return entry.jni_reference;
-}
-
-void ObjectRegistry::DisableCollection(JDWP::ObjectId id) {
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- CHECK(it != id_to_entry_.end());
- Promote(*it->second);
-}
-
-void ObjectRegistry::EnableCollection(JDWP::ObjectId id) {
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- CHECK(it != id_to_entry_.end());
- Demote(*it->second);
-}
-
-void ObjectRegistry::Demote(ObjectRegistryEntry& entry) {
- if (entry.jni_reference_type == JNIGlobalRefType) {
- Thread* self = Thread::Current();
- JNIEnv* env = self->GetJniEnv();
- jobject global = entry.jni_reference;
- entry.jni_reference = env->NewWeakGlobalRef(entry.jni_reference);
- entry.jni_reference_type = JNIWeakGlobalRefType;
- env->DeleteGlobalRef(global);
- }
-}
-
-void ObjectRegistry::Promote(ObjectRegistryEntry& entry) {
- if (entry.jni_reference_type == JNIWeakGlobalRefType) {
- Thread* self = Thread::Current();
- JNIEnv* env = self->GetJniEnv();
- jobject weak = entry.jni_reference;
- entry.jni_reference = env->NewGlobalRef(entry.jni_reference);
- entry.jni_reference_type = JNIGlobalRefType;
- env->DeleteWeakGlobalRef(weak);
- }
-}
-
-bool ObjectRegistry::IsCollected(JDWP::ObjectId id) {
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- CHECK(it != id_to_entry_.end());
- ObjectRegistryEntry& entry = *it->second;
- if (entry.jni_reference_type == JNIWeakGlobalRefType) {
- JNIEnv* env = self->GetJniEnv();
- return env->IsSameObject(entry.jni_reference, nullptr); // Has the jweak been collected?
- } else {
- return false; // We hold a strong reference, so we know this is live.
- }
-}
-
-void ObjectRegistry::DisposeObject(JDWP::ObjectId id, uint32_t reference_count) {
- Thread* self = Thread::Current();
- MutexLock mu(self, lock_);
- auto it = id_to_entry_.find(id);
- if (it == id_to_entry_.end()) {
- return;
- }
- ObjectRegistryEntry* entry = it->second;
- entry->reference_count -= reference_count;
- if (entry->reference_count <= 0) {
- JNIEnv* env = self->GetJniEnv();
- // Erase the object from the maps. Note object may be null if it's
- // a weak ref and the GC has cleared it.
- int32_t hash_code = entry->identity_hash_code;
- for (auto inner_it = object_to_entry_.lower_bound(hash_code), end = object_to_entry_.end();
- inner_it != end && inner_it->first == hash_code; ++inner_it) {
- if (entry == inner_it->second) {
- object_to_entry_.erase(inner_it);
- break;
- }
- }
- if (entry->jni_reference_type == JNIWeakGlobalRefType) {
- env->DeleteWeakGlobalRef(entry->jni_reference);
- } else {
- env->DeleteGlobalRef(entry->jni_reference);
- }
- id_to_entry_.erase(id);
- delete entry;
- }
-}
-
-} // namespace art
diff --git a/runtime/jdwp/object_registry.h b/runtime/jdwp/object_registry.h
deleted file mode 100644
index 7aa79b19bae..00000000000
--- a/runtime/jdwp/object_registry.h
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ART_RUNTIME_JDWP_OBJECT_REGISTRY_H_
-#define ART_RUNTIME_JDWP_OBJECT_REGISTRY_H_
-
-#include <jni.h>
-#include <stdint.h>
-
-#include <map>
-
-#include "base/casts.h"
-#include "base/safe_map.h"
-#include "handle.h"
-#include "jdwp/jdwp.h"
-#include "obj_ptr.h"
-
-namespace art {
-
-namespace mirror {
-class Object;
-class Class;
-} // namespace mirror
-
-struct ObjectRegistryEntry {
- // Is jni_reference a weak global or a regular global reference?
- jobjectRefType jni_reference_type;
-
- // The reference itself.
- jobject jni_reference;
-
- // A reference count, so we can implement DisposeObject.
- int32_t reference_count;
-
- // The corresponding id, so we only need one map lookup in Add.
- JDWP::ObjectId id;
-
- // The identity hash code of the object. This is the same as the key
- // for object_to_entry_. Store this for DisposeObject().
- int32_t identity_hash_code;
-};
-std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs);
-
-// Tracks those objects currently known to the debugger, so we can use consistent ids when
-// referring to them. Normally we keep JNI weak global references to objects, so they can
-// still be garbage collected. The debugger can ask us to retain objects, though, so we can
-// also promote references to regular JNI global references (and demote them back again if
-// the debugger tells us that's okay).
-class ObjectRegistry {
- public:
- ObjectRegistry();
- ~ObjectRegistry();
-
- JDWP::ObjectId Add(ObjPtr<mirror::Object> o)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_, !lock_);
-
- JDWP::RefTypeId AddRefType(ObjPtr<mirror::Class> c)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_, !lock_);
-
- template<class T>
- JDWP::ObjectId Add(Handle<T> obj_h)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_, !lock_);
-
- JDWP::RefTypeId AddRefType(Handle<mirror::Class> c_h)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_, !lock_);
-
- template<typename T>
- ObjPtr<T> Get(JDWP::ObjectId id, JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- void Clear() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- void DisableCollection(JDWP::ObjectId id)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- void EnableCollection(JDWP::ObjectId id)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- bool IsCollected(JDWP::ObjectId id)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- void DisposeObject(JDWP::ObjectId id, uint32_t reference_count)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- // This is needed to get the jobject instead of the ObjPtr<Object>.
- // Avoid using this and use standard Get when possible.
- jobject GetJObject(JDWP::ObjectId id) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- private:
- template<class T>
- JDWP::ObjectId InternalAdd(Handle<T> obj_h)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!lock_, !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
-
- ObjPtr<mirror::Object> InternalGet(JDWP::ObjectId id, JDWP::JdwpError* error)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!lock_);
-
- void Demote(ObjectRegistryEntry& entry)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(lock_);
-
- void Promote(ObjectRegistryEntry& entry)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(lock_);
-
- bool ContainsLocked(Thread* self,
- ObjPtr<mirror::Object> o,
- int32_t identity_hash_code,
- ObjectRegistryEntry** out_entry)
- REQUIRES(lock_) REQUIRES_SHARED(Locks::mutator_lock_);
-
- Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
- std::multimap<int32_t, ObjectRegistryEntry*> object_to_entry_ GUARDED_BY(lock_);
- SafeMap<JDWP::ObjectId, ObjectRegistryEntry*> id_to_entry_ GUARDED_BY(lock_);
-
- size_t next_id_ GUARDED_BY(lock_);
-};
-
-} // namespace art
-
-#endif // ART_RUNTIME_JDWP_OBJECT_REGISTRY_H_
diff --git a/runtime/jdwp_provider.h b/runtime/jdwp_provider.h
index 29fbc3f998e..9cd31456241 100644
--- a/runtime/jdwp_provider.h
+++ b/runtime/jdwp_provider.h
@@ -29,7 +29,6 @@ enum class JdwpProvider {
// should not be used and one should always call CanonicalizeJdwpProvider which will remove this
// value before using a JdwpProvider value.
kUnset,
- kInternal,
kAdbConnection,
// The current default provider. Used if you run -XjdwpProvider:default
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 2a5ab118642..de43c4f3840 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -172,7 +172,9 @@ static void VMDebug_stopEmulatorTracing(JNIEnv*, jclass) {
}
static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
- return Dbg::IsDebuggerActive();
+ // This function will be replaced by the debugger when it's connected. See
+ // external/oj-libjdwp/src/share/vmDebug.c for implementation when debugger is connected.
+ return false;
}
static jboolean VMDebug_isDebuggingEnabled(JNIEnv* env, jclass) {
@@ -181,7 +183,9 @@ static jboolean VMDebug_isDebuggingEnabled(JNIEnv* env, jclass) {
}
static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
- return Dbg::LastDebuggerActivity();
+ // This function will be replaced by the debugger when it's connected. See
+ // external/oj-libjdwp/src/share/vmDebug.c for implementation when debugger is connected.
+ return -1;
}
static void ThrowUnsupportedOperationException(JNIEnv* env) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index 028675d4481..d405735f19a 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -20,6 +20,7 @@
#include "base/file_utils.h"
#include "base/mutex.h"
+#include "base/endian_utils.h"
#include "debugger.h"
#include "gc/heap.h"
#include "jni/jni_internal.h"
@@ -101,6 +102,57 @@ static void ThreadCountCallback(Thread*, void* context) {
static const int kThstBytesPerEntry = 18;
static const int kThstHeaderLen = 4;
+static constexpr uint8_t ToJdwpThreadStatus(ThreadState state) {
+ /*
+ * ThreadStatus constants.
+ */
+ enum JdwpThreadStatus : uint8_t {
+ TS_ZOMBIE = 0,
+ TS_RUNNING = 1, // RUNNING
+ TS_SLEEPING = 2, // (in Thread.sleep())
+ TS_MONITOR = 3, // WAITING (monitor wait)
+ TS_WAIT = 4, // (in Object.wait())
+ };
+ switch (state) {
+ case kBlocked:
+ return TS_MONITOR;
+ case kNative:
+ case kRunnable:
+ case kSuspended:
+ return TS_RUNNING;
+ case kSleeping:
+ return TS_SLEEPING;
+ case kStarting:
+ case kTerminated:
+ return TS_ZOMBIE;
+ case kTimedWaiting:
+ case kWaitingForTaskProcessor:
+ case kWaitingForLockInflation:
+ case kWaitingForCheckPointsToRun:
+ case kWaitingForDebuggerSend:
+ case kWaitingForDebuggerSuspension:
+ case kWaitingForDebuggerToAttach:
+ case kWaitingForDeoptimization:
+ case kWaitingForGcToComplete:
+ case kWaitingForGetObjectsAllocated:
+ case kWaitingForJniOnLoad:
+ case kWaitingForMethodTracingStart:
+ case kWaitingForSignalCatcherOutput:
+ case kWaitingForVisitObjects:
+ case kWaitingInMainDebuggerLoop:
+ case kWaitingInMainSignalCatcherLoop:
+ case kWaitingPerformingGc:
+ case kWaitingWeakGcRootRead:
+ case kWaitingForGcThreadFlip:
+ case kNativeForAbort:
+ case kWaiting:
+ return TS_WAIT;
+ // Don't add a 'default' here so the compiler can spot incompatible enum changes.
+ }
+ LOG(FATAL) << "Unknown thread state: " << state;
+ UNREACHABLE();
+}
+
static void ThreadStatsGetterCallback(Thread* t, void* context) {
/*
* Generate the contents of a THST chunk. The data encompasses all known
@@ -130,12 +182,12 @@ static void ThreadStatsGetterCallback(Thread* t, void* context) {
GetTaskStats(t->GetTid(), &native_thread_state, &utime, &stime, &task_cpu);
std::vector<uint8_t>& bytes = *reinterpret_cast<std::vector<uint8_t>*>(context);
- JDWP::Append4BE(bytes, t->GetThreadId());
- JDWP::Append1BE(bytes, Dbg::ToJdwpThreadStatus(t->GetState()));
- JDWP::Append4BE(bytes, t->GetTid());
- JDWP::Append4BE(bytes, utime);
- JDWP::Append4BE(bytes, stime);
- JDWP::Append1BE(bytes, t->IsDaemon());
+ Append4BE(bytes, t->GetThreadId());
+ Append1BE(bytes, ToJdwpThreadStatus(t->GetState()));
+ Append4BE(bytes, t->GetTid());
+ Append4BE(bytes, utime);
+ Append4BE(bytes, stime);
+ Append1BE(bytes, t->IsDaemon());
}
static jbyteArray DdmVmInternal_getThreadStats(JNIEnv* env, jclass) {
@@ -148,9 +200,9 @@ static jbyteArray DdmVmInternal_getThreadStats(JNIEnv* env, jclass) {
uint16_t thread_count = 0;
thread_list->ForEach(ThreadCountCallback, &thread_count);
- JDWP::Append1BE(bytes, kThstHeaderLen);
- JDWP::Append1BE(bytes, kThstBytesPerEntry);
- JDWP::Append2BE(bytes, thread_count);
+ Append1BE(bytes, kThstHeaderLen);
+ Append1BE(bytes, kThstBytesPerEntry);
+ Append2BE(bytes, thread_count);
thread_list->ForEach(ThreadStatsGetterCallback, &bytes);
}
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index fc9a9757fc8..351d5e8ea09 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -113,7 +113,7 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize
.Define("-XjdwpProvider:_")
.WithType<JdwpProvider>()
.IntoKey(M::JdwpProvider)
- .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_", "-XjdwpOptions:_"})
+ .Define("-XjdwpOptions:_")
.WithType<std::string>()
.IntoKey(M::JdwpOptions)
// TODO Re-enable -agentlib: once I have a good way to transform the values.
@@ -713,7 +713,6 @@ void ParsedOptions::Usage(const char* fmt, ...) {
" 'third-party-jni', 'threads', 'verifier', 'verifier-debug')\n");
UsageMessage(stream, " -showversion\n");
UsageMessage(stream, " -help\n");
- UsageMessage(stream, " -agentlib:jdwp=options\n");
// TODO add back in once -agentlib actually does something.
// UsageMessage(stream, " -agentlib:library=options (Experimental feature, "
// "requires -Xexperimental:agent, some features might not be supported)\n");
@@ -722,7 +721,6 @@ void ParsedOptions::Usage(const char* fmt, ...) {
UsageMessage(stream, "\n");
UsageMessage(stream, "The following extended options are supported:\n");
- UsageMessage(stream, " -Xrunjdwp:<options>\n");
UsageMessage(stream, " -Xbootclasspath:bootclasspath\n");
UsageMessage(stream, " -Xcheck:tag (e.g. 'jni')\n");
UsageMessage(stream, " -XmsN (min heap, must be multiple of 1K, >= 1MB)\n");
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index fa1916aac1a..fcfe7970951 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1441,30 +1441,16 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
VLOG(jdwp) << "Disabling all JDWP support.";
if (!jdwp_options_.empty()) {
bool has_transport = jdwp_options_.find("transport") != std::string::npos;
- const char* transport_internal = !has_transport ? "transport=dt_android_adb," : "";
std::string adb_connection_args =
std::string(" -XjdwpProvider:adbconnection -XjdwpOptions:") + jdwp_options_;
LOG(WARNING) << "Jdwp options given when jdwp is disabled! You probably want to enable "
<< "jdwp with one of:" << std::endl
- << " -XjdwpProvider:internal "
- << "-XjdwpOptions:" << transport_internal << jdwp_options_ << std::endl
<< " -Xplugin:libopenjdkjvmti" << (kIsDebugBuild ? "d" : "") << ".so "
<< "-agentpath:libjdwp.so=" << jdwp_options_ << std::endl
<< (has_transport ? "" : adb_connection_args);
}
break;
}
- case JdwpProvider::kInternal: {
- if (runtime_options.Exists(Opt::JdwpOptions)) {
- JDWP::JdwpOptions ops;
- if (!JDWP::ParseJdwpOptions(runtime_options.GetOrDefault(Opt::JdwpOptions), &ops)) {
- LOG(ERROR) << "failed to parse jdwp options!";
- return false;
- }
- Dbg::ConfigureJdwp(ops);
- }
- break;
- }
case JdwpProvider::kAdbConnection: {
constexpr const char* plugin_name = kIsDebugBuild ? "libadbconnectiond.so"
: "libadbconnection.so";
@@ -2220,7 +2206,6 @@ void Runtime::VisitConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
// Guaranteed to have no new roots in the constant roots.
VisitConstantRoots(visitor);
}
- Dbg::VisitRoots(visitor);
}
void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
diff --git a/runtime/runtime_options.h b/runtime/runtime_options.h
index 19ec75ee905..abc1fc0d688 100644
--- a/runtime/runtime_options.h
+++ b/runtime/runtime_options.h
@@ -29,7 +29,6 @@
#include "gc/space/image_space_loading_order.h"
#include "gc/space/large_object_space.h"
#include "hidden_api.h"
-#include "jdwp/jdwp.h"
#include "jit/jit.h"
#include "jit/jit_code_cache.h"
#include "jit/profile_saver_options.h"
diff --git a/runtime/suspend_reason.h b/runtime/suspend_reason.h
index 9881b8851c5..7f377d50381 100644
--- a/runtime/suspend_reason.h
+++ b/runtime/suspend_reason.h
@@ -26,8 +26,6 @@ enum class SuspendReason : char {
// Suspending for internal reasons (e.g. GC, stack trace, etc.).
// TODO Split this into more descriptive sections.
kInternal,
- // Suspending for debugger (code in Dbg::*, runtime/jdwp/, etc.).
- kForDebugger,
// Suspending due to non-runtime, user controlled, code. (For example Thread#Suspend()).
kForUserCode,
};
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 2319f894012..a996bccf1c4 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1464,9 +1464,6 @@ bool Thread::ModifySuspendCountInternal(Thread* self,
tls32_.suspend_count += delta;
switch (reason) {
- case SuspendReason::kForDebugger:
- tls32_.debug_suspend_count += delta;
- break;
case SuspendReason::kForUserCode:
tls32_.user_code_suspend_count += delta;
break;
@@ -2501,9 +2498,6 @@ Thread::~Thread() {
CleanupCpu();
}
- if (tlsPtr_.single_step_control != nullptr) {
- delete tlsPtr_.single_step_control;
- }
delete tlsPtr_.instrumentation_stack;
delete tlsPtr_.name;
delete tlsPtr_.deps_or_stack_trace_sample.stack_trace_sample;
@@ -4030,9 +4024,6 @@ void Thread::VisitRoots(RootVisitor* visitor) {
tlsPtr_.jni_env->VisitJniLocalRoots(visitor, RootInfo(kRootJNILocal, thread_id));
tlsPtr_.jni_env->VisitMonitorRoots(visitor, RootInfo(kRootJNIMonitor, thread_id));
HandleScopeVisitRoots(visitor, thread_id);
- if (tlsPtr_.debug_invoke_req != nullptr) {
- tlsPtr_.debug_invoke_req->VisitRoots(visitor, RootInfo(kRootDebugger, thread_id));
- }
// Visit roots for deoptimization.
if (tlsPtr_.stacked_shadow_frame_record != nullptr) {
RootCallbackVisitor visitor_to_callback(visitor, thread_id);
@@ -4211,37 +4202,6 @@ bool Thread::UnprotectStack() {
return mprotect(pregion, kStackOverflowProtectedSize, PROT_READ|PROT_WRITE) == 0;
}
-void Thread::ActivateSingleStepControl(SingleStepControl* ssc) {
- CHECK(Dbg::IsDebuggerActive());
- CHECK(GetSingleStepControl() == nullptr) << "Single step already active in thread " << *this;
- CHECK(ssc != nullptr);
- tlsPtr_.single_step_control = ssc;
-}
-
-void Thread::DeactivateSingleStepControl() {
- CHECK(Dbg::IsDebuggerActive());
- CHECK(GetSingleStepControl() != nullptr) << "Single step not active in thread " << *this;
- SingleStepControl* ssc = GetSingleStepControl();
- tlsPtr_.single_step_control = nullptr;
- delete ssc;
-}
-
-void Thread::SetDebugInvokeReq(DebugInvokeReq* req) {
- CHECK(Dbg::IsDebuggerActive());
- CHECK(GetInvokeReq() == nullptr) << "Debug invoke req already active in thread " << *this;
- CHECK(Thread::Current() != this) << "Debug invoke can't be dispatched by the thread itself";
- CHECK(req != nullptr);
- tlsPtr_.debug_invoke_req = req;
-}
-
-void Thread::ClearDebugInvokeReq() {
- CHECK(GetInvokeReq() != nullptr) << "Debug invoke req not active in thread " << *this;
- CHECK(Thread::Current() == this) << "Debug invoke must be finished by the thread itself";
- DebugInvokeReq* req = tlsPtr_.debug_invoke_req;
- tlsPtr_.debug_invoke_req = nullptr;
- delete req;
-}
-
void Thread::PushVerifier(verifier::MethodVerifier* verifier) {
verifier->link_ = tlsPtr_.method_verifier;
tlsPtr_.method_verifier = verifier;
diff --git a/runtime/thread.h b/runtime/thread.h
index 34434cf68c1..483191eddd9 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -85,7 +85,6 @@ class BaseMutex;
class ClassLinker;
class Closure;
class Context;
-struct DebugInvokeReq;
class DeoptimizationContextRecord;
class DexFile;
class FrameIdToShadowFrame;
@@ -96,7 +95,6 @@ class Monitor;
class RootVisitor;
class ScopedObjectAccessAlreadyRunnable;
class ShadowFrame;
-class SingleStepControl;
class StackedShadowFrameRecord;
enum class SuspendReason : char;
class Thread;
@@ -941,14 +939,6 @@ class Thread {
return handle_scope;
}
- DebugInvokeReq* GetInvokeReq() const {
- return tlsPtr_.debug_invoke_req;
- }
-
- SingleStepControl* GetSingleStepControl() const {
- return tlsPtr_.single_step_control;
- }
-
// Indicates whether this thread is ready to invoke a method for debugging. This
// is only true if the thread has been suspended by a debug event.
bool IsReadyForDebugInvoke() const {
@@ -1024,25 +1014,6 @@ class Thread {
// Returns true if the thread is allowed to load java classes.
bool CanLoadClasses() const;
- // Activates single step control for debugging. The thread takes the
- // ownership of the given SingleStepControl*. It is deleted by a call
- // to DeactivateSingleStepControl or upon thread destruction.
- void ActivateSingleStepControl(SingleStepControl* ssc);
-
- // Deactivates single step control for debugging.
- void DeactivateSingleStepControl();
-
- // Sets debug invoke request for debugging. When the thread is resumed,
- // it executes the method described by this request then sends the reply
- // before suspending itself. The thread takes the ownership of the given
- // DebugInvokeReq*. It is deleted by a call to ClearDebugInvokeReq.
- void SetDebugInvokeReq(DebugInvokeReq* req);
-
- // Clears debug invoke request for debugging. When the thread completes
- // method invocation, it deletes its debug invoke request and suspends
- // itself.
- void ClearDebugInvokeReq();
-
// Returns the fake exception used to activate deoptimization.
static mirror::Throwable* GetDeoptimizationException() {
// Note that the mirror::Throwable must be aligned to kObjectAlignment or else it cannot be
@@ -1389,8 +1360,7 @@ class Thread {
jint thread_priority)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit, ~Thread and
- // Dbg::ManageDeoptimization.
+ // Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit and, ~Thread
ThreadState SetStateUnsafe(ThreadState new_state) {
ThreadState old_state = GetState();
if (old_state == kRunnable && new_state != kRunnable) {
@@ -1693,7 +1663,7 @@ class Thread {
self(nullptr), opeer(nullptr), jpeer(nullptr), stack_begin(nullptr), stack_size(0),
deps_or_stack_trace_sample(), wait_next(nullptr), monitor_enter_object(nullptr),
top_handle_scope(nullptr), class_loader_override(nullptr), long_jump_context(nullptr),
- instrumentation_stack(nullptr), debug_invoke_req(nullptr), single_step_control(nullptr),
+ instrumentation_stack(nullptr),
stacked_shadow_frame_record(nullptr), deoptimization_context_stack(nullptr),
frame_id_to_shadow_frame(nullptr), name(nullptr), pthread_self(0),
last_no_thread_suspension_cause(nullptr), checkpoint_function(nullptr),
@@ -1779,12 +1749,6 @@ class Thread {
// Stored as a pointer since std::deque is not PACKED.
std::deque<instrumentation::InstrumentationStackFrame>* instrumentation_stack;
- // JDWP invoke-during-breakpoint support.
- DebugInvokeReq* debug_invoke_req;
-
- // JDWP single-stepping support.
- SingleStepControl* single_step_control;
-
// For gc purpose, a shadow frame record stack that keeps track of:
// 1) shadow frames under construction.
// 2) deoptimization shadow frames.
@@ -1906,7 +1870,6 @@ class Thread {
// the caller is allowed to access all fields and methods in the Core Platform API.
uint32_t core_platform_api_cookie_ = 0;
- friend class Dbg; // For SetStateUnsafe.
friend class gc::collector::SemiSpace; // For getting stack traces.
friend class Runtime; // For CreatePeer.
friend class QuickExceptionHandler; // For dumping the stack.
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index b2bb84681a4..ed28e74ea86 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -73,7 +73,6 @@ static constexpr bool kDumpUnattachedThreadNativeStackForSigQuit = true;
ThreadList::ThreadList(uint64_t thread_suspend_timeout_ns)
: suspend_all_count_(0),
- debug_suspend_all_count_(0),
unregistering_count_(0),
suspend_all_historam_("suspend all histogram", 16, 64),
long_suspend_(false),
@@ -669,9 +668,6 @@ void ThreadList::SuspendAll(const char* cause, bool long_suspend) {
}
// Ensures all threads running Java suspend and that those not running Java don't start.
-// Debugger thread might be set to kRunnable for a short period of time after the
-// SuspendAllInternal. This is safe because it will be set back to suspended state before
-// the SuspendAll returns.
void ThreadList::SuspendAllInternal(Thread* self,
Thread* ignore1,
Thread* ignore2,
@@ -705,9 +701,6 @@ void ThreadList::SuspendAllInternal(Thread* self,
MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
// Update global suspend all state for attaching threads.
++suspend_all_count_;
- if (reason == SuspendReason::kForDebugger) {
- ++debug_suspend_all_count_;
- }
pending_threads.store(list_.size() - num_ignored, std::memory_order_relaxed);
// Increment everybody's suspend count (except those that should be ignored).
for (const auto& thread : list_) {
@@ -1117,186 +1110,6 @@ Thread* ThreadList::FindThreadByThreadId(uint32_t thread_id) {
return nullptr;
}
-void ThreadList::SuspendAllForDebugger() {
- Thread* self = Thread::Current();
- Thread* debug_thread = Dbg::GetDebugThread();
-
- VLOG(threads) << *self << " SuspendAllForDebugger starting...";
-
- SuspendAllInternal(self, self, debug_thread, SuspendReason::kForDebugger);
- // Block on the mutator lock until all Runnable threads release their share of access then
- // immediately unlock again.
-#if HAVE_TIMED_RWLOCK
- // Timeout if we wait more than 30 seconds.
- if (!Locks::mutator_lock_->ExclusiveLockWithTimeout(self, 30 * 1000, 0)) {
- UnsafeLogFatalForThreadSuspendAllTimeout();
- } else {
- Locks::mutator_lock_->ExclusiveUnlock(self);
- }
-#else
- Locks::mutator_lock_->ExclusiveLock(self);
- Locks::mutator_lock_->ExclusiveUnlock(self);
-#endif
- // Disabled for the following race condition:
- // Thread 1 calls SuspendAllForDebugger, gets preempted after pulsing the mutator lock.
- // Thread 2 calls SuspendAll and SetStateUnsafe (perhaps from Dbg::Disconnected).
- // Thread 1 fails assertion that all threads are suspended due to thread 2 being in a runnable
- // state (from SetStateUnsafe).
- // AssertThreadsAreSuspended(self, self, debug_thread);
-
- VLOG(threads) << *self << " SuspendAllForDebugger complete";
-}
-
-void ThreadList::SuspendSelfForDebugger() {
- Thread* const self = Thread::Current();
- self->SetReadyForDebugInvoke(true);
-
- // The debugger thread must not suspend itself due to debugger activity!
- Thread* debug_thread = Dbg::GetDebugThread();
- CHECK(self != debug_thread);
- CHECK_NE(self->GetState(), kRunnable);
- Locks::mutator_lock_->AssertNotHeld(self);
-
- // The debugger may have detached while we were executing an invoke request. In that case, we
- // must not suspend ourself.
- DebugInvokeReq* pReq = self->GetInvokeReq();
- const bool skip_thread_suspension = (pReq != nullptr && !Dbg::IsDebuggerActive());
- if (!skip_thread_suspension) {
- // Collisions with other suspends aren't really interesting. We want
- // to ensure that we're the only one fiddling with the suspend count
- // though.
- MutexLock mu(self, *Locks::thread_suspend_count_lock_);
- bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kForDebugger);
- DCHECK(updated);
- CHECK_GT(self->GetSuspendCount(), 0);
-
- VLOG(threads) << *self << " self-suspending (debugger)";
- } else {
- // We must no longer be subject to debugger suspension.
- MutexLock mu(self, *Locks::thread_suspend_count_lock_);
- CHECK_EQ(self->GetDebugSuspendCount(), 0) << "Debugger detached without resuming us";
-
- VLOG(threads) << *self << " not self-suspending because debugger detached during invoke";
- }
-
- // If the debugger requested an invoke, we need to send the reply and clear the request.
- if (pReq != nullptr) {
- Dbg::FinishInvokeMethod(pReq);
- self->ClearDebugInvokeReq();
- pReq = nullptr; // object has been deleted, clear it for safety.
- }
-
- // Tell JDWP that we've completed suspension. The JDWP thread can't
- // tell us to resume before we're fully asleep because we hold the
- // suspend count lock.
- Dbg::ClearWaitForEventThread();
-
- {
- MutexLock mu(self, *Locks::thread_suspend_count_lock_);
- while (self->GetSuspendCount() != 0) {
- Thread::resume_cond_->Wait(self);
- if (self->GetSuspendCount() != 0) {
- // The condition was signaled but we're still suspended. This
- // can happen when we suspend then resume all threads to
- // update instrumentation or compute monitor info. This can
- // also happen if the debugger lets go while a SIGQUIT thread
- // dump event is pending (assuming SignalCatcher was resumed for
- // just long enough to try to grab the thread-suspend lock).
- VLOG(jdwp) << *self << " still suspended after undo "
- << "(suspend count=" << self->GetSuspendCount() << ", "
- << "debug suspend count=" << self->GetDebugSuspendCount() << ")";
- }
- }
- CHECK_EQ(self->GetSuspendCount(), 0);
- }
-
- self->SetReadyForDebugInvoke(false);
- VLOG(threads) << *self << " self-reviving (debugger)";
-}
-
-void ThreadList::ResumeAllForDebugger() {
- Thread* self = Thread::Current();
- Thread* debug_thread = Dbg::GetDebugThread();
-
- VLOG(threads) << *self << " ResumeAllForDebugger starting...";
-
- // Threads can't resume if we exclusively hold the mutator lock.
- Locks::mutator_lock_->AssertNotExclusiveHeld(self);
-
- {
- MutexLock thread_list_mu(self, *Locks::thread_list_lock_);
- {
- MutexLock suspend_count_mu(self, *Locks::thread_suspend_count_lock_);
- // Update global suspend all state for attaching threads.
- DCHECK_GE(suspend_all_count_, debug_suspend_all_count_);
- if (debug_suspend_all_count_ > 0) {
- --suspend_all_count_;
- --debug_suspend_all_count_;
- } else {
- // We've been asked to resume all threads without being asked to
- // suspend them all before. That may happen if a debugger tries
- // to resume some suspended threads (with suspend count == 1)
- // at once with a VirtualMachine.Resume command. Let's print a
- // warning.
- LOG(WARNING) << "Debugger attempted to resume all threads without "
- << "having suspended them all before.";
- }
- // Decrement everybody's suspend count (except our own).
- for (const auto& thread : list_) {
- if (thread == self || thread == debug_thread) {
- continue;
- }
- if (thread->GetDebugSuspendCount() == 0) {
- // This thread may have been individually resumed with ThreadReference.Resume.
- continue;
- }
- VLOG(threads) << "requesting thread resume: " << *thread;
- bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kForDebugger);
- DCHECK(updated);
- }
- }
- }
-
- {
- MutexLock mu(self, *Locks::thread_suspend_count_lock_);
- Thread::resume_cond_->Broadcast(self);
- }
-
- VLOG(threads) << *self << " ResumeAllForDebugger complete";
-}
-
-void ThreadList::UndoDebuggerSuspensions() {
- Thread* self = Thread::Current();
-
- VLOG(threads) << *self << " UndoDebuggerSuspensions starting";
-
- {
- MutexLock mu(self, *Locks::thread_list_lock_);
- MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- // Update global suspend all state for attaching threads.
- suspend_all_count_ -= debug_suspend_all_count_;
- debug_suspend_all_count_ = 0;
- // Update running threads.
- for (const auto& thread : list_) {
- if (thread == self || thread->GetDebugSuspendCount() == 0) {
- continue;
- }
- bool suspended = thread->ModifySuspendCount(self,
- -thread->GetDebugSuspendCount(),
- nullptr,
- SuspendReason::kForDebugger);
- DCHECK(suspended);
- }
- }
-
- {
- MutexLock mu(self, *Locks::thread_suspend_count_lock_);
- Thread::resume_cond_->Broadcast(self);
- }
-
- VLOG(threads) << "UndoDebuggerSuspensions(" << *self << ") complete";
-}
-
void ThreadList::WaitForOtherNonDaemonThreadsToExit() {
ScopedTrace trace(__PRETTY_FUNCTION__);
Thread* self = Thread::Current();
@@ -1431,14 +1244,9 @@ void ThreadList::Register(Thread* self) {
// SuspendAll requests.
MutexLock mu(self, *Locks::thread_list_lock_);
MutexLock mu2(self, *Locks::thread_suspend_count_lock_);
- CHECK_GE(suspend_all_count_, debug_suspend_all_count_);
// Modify suspend count in increments of 1 to maintain invariants in ModifySuspendCount. While
// this isn't particularly efficient the suspend counts are most commonly 0 or 1.
- for (int delta = debug_suspend_all_count_; delta > 0; delta--) {
- bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kForDebugger);
- DCHECK(updated);
- }
- for (int delta = suspend_all_count_ - debug_suspend_all_count_; delta > 0; delta--) {
+ for (int delta = suspend_all_count_; delta > 0; delta--) {
bool updated = self->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal);
DCHECK(updated);
}
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index 7a3c26b00f7..ce564fda916 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -134,22 +134,6 @@ class ThreadList {
!Locks::thread_list_lock_,
!Locks::thread_suspend_count_lock_);
- // Suspends all threads
- void SuspendAllForDebugger()
- REQUIRES(!Locks::mutator_lock_,
- !Locks::thread_list_lock_,
- !Locks::thread_suspend_count_lock_);
-
- void SuspendSelfForDebugger()
- REQUIRES(!Locks::thread_suspend_count_lock_);
-
- // Resume all threads
- void ResumeAllForDebugger()
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
-
- void UndoDebuggerSuspensions()
- REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_);
-
// Iterates over all the threads.
void ForEach(void (*callback)(Thread*, void*), void* context)
REQUIRES(Locks::thread_list_lock_);
@@ -229,7 +213,6 @@ class ThreadList {
// Ongoing suspend all requests, used to ensure threads added to list_ respect SuspendAll.
int suspend_all_count_ GUARDED_BY(Locks::thread_suspend_count_lock_);
- int debug_suspend_all_count_ GUARDED_BY(Locks::thread_suspend_count_lock_);
// Number of threads unregistering, ~ThreadList blocks until this hits 0.
int unregistering_count_ GUARDED_BY(Locks::thread_list_lock_);
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index f4ce1590781..d593677a84c 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -295,6 +295,7 @@ while true; do
TIME_OUT="n"
shift
elif [ "x$1" = "x--debug" ]; then
+ USE_JVMTI="y"
DEBUGGER="y"
TIME_OUT="n"
shift
@@ -533,7 +534,7 @@ msg "------------------------------"
if [ "$DEBUGGER" = "y" ]; then
# Use this instead for ddms and connect by running 'ddms':
- # DEBUGGER_OPTS="-agentlib:jdwp=transport=dt_android_adb,server=y,suspend=y"
+ # DEBUGGER_OPTS="-XjdwpOptions=server=y,suspend=y -XjdwpProvider:adbconnection"
# TODO: add a separate --ddms option?
PORT=12345
@@ -543,9 +544,8 @@ if [ "$DEBUGGER" = "y" ]; then
fi
msg " jdb -attach localhost:$PORT"
if [ "$USE_JVM" = "n" ]; then
- # TODO We should switch over to using the jvmti agent by default.
- # Need to tell the runtime to enable the internal jdwp implementation.
- DEBUGGER_OPTS="-XjdwpOptions:transport=dt_socket,address=$PORT,server=y,suspend=y -XjdwpProvider:internal"
+ # Use the default libjdwp agent. Use --debug-agent to use a custom one.
+ DEBUGGER_OPTS="-agentpath:libjdwp.so=transport=dt_socket,address=$PORT,server=y,suspend=y -XjdwpProvider:internal"
else
DEBUGGER_OPTS="-agentlib:jdwp=transport=dt_socket,address=$PORT,server=y,suspend=y"
fi