diff options
author | 2017-12-14 11:52:04 -0800 | |
---|---|---|
committer | 2017-12-19 09:26:11 -0800 | |
commit | 403207107da7f11525c4d305184c56b35ec1c17a (patch) | |
tree | f97f080fcfa8051df18536e97a33582ee4bbbc6a | |
parent | ba3a790338725a37ecd4cb314c4a6147e29aef38 (diff) |
Add support for selecting alternate JDWP implementations
Change JDWP options parsing to take place later and add a
-XjdwpProvider:_ option that can be used by the runtime to select an
appropriate JDWP provider. The argument is a string.
If 'none' is given JDWP will be totally disabled.
If 'internal' is given the current internal JDWP implementation is
used.
If 'default' is given the 'internal' JDWP implementation will
currently be used.
Other values will be added in the future.
Also adds a runtime callback that will be invoked when the runtime
wants to start or stop the debugger (namely at the post-zygote fork
and just before exit) and check if a debugger is availible.
Also add '-XjdwpOptions:_' in preparation for the eventual removal of
the existing -Xrunjdwp=_ and -Xagentlib:jdwp=_ as top-level options.
All of these options now store their arguments as a std::string to be
interpreted by the JDWP implementation as it sees fit. Also change the
jdwpOptions to default to transport=dt_android_adb if there is not one
specified and it is available. This will make changing the default
transport based on the JDWP provider easier.
These new options are needed to allow us to support both the old,
internal, JDWP implementation as its replacement is tested and
verified. This lets us switch between them with little difficulty.
We will probably remove one or both of these options once we have
confidence that the new jdwp implementation has stuck.
Test: ./test.py --host -j50
Test: ./test/run-test --host --debug 001-HelloWorld
Test: Manual, flash walleye, debug app
Bug: 62821960
Change-Id: Ie31db6b6f7d76a03d4ab8e178fcf298ed0eec203
-rw-r--r-- | cmdline/cmdline_parser_test.cc | 60 | ||||
-rw-r--r-- | cmdline/cmdline_types.h | 127 | ||||
-rw-r--r-- | runtime/Android.bp | 2 | ||||
-rw-r--r-- | runtime/debugger.cc | 16 | ||||
-rw-r--r-- | runtime/debugger.h | 18 | ||||
-rw-r--r-- | runtime/jdwp/jdwp.h | 7 | ||||
-rw-r--r-- | runtime/jdwp/jdwp_main.cc | 113 | ||||
-rw-r--r-- | runtime/jdwp/jdwp_options_test.cc | 79 | ||||
-rw-r--r-- | runtime/jdwp_provider.h | 35 | ||||
-rw-r--r-- | runtime/native/dalvik_system_VMDebug.cc | 5 | ||||
-rw-r--r-- | runtime/parsed_options.cc | 7 | ||||
-rw-r--r-- | runtime/runtime.cc | 29 | ||||
-rw-r--r-- | runtime/runtime.h | 15 | ||||
-rw-r--r-- | runtime/runtime_callbacks.cc | 29 | ||||
-rw-r--r-- | runtime/runtime_callbacks.h | 26 | ||||
-rw-r--r-- | runtime/runtime_options.def | 3 |
16 files changed, 406 insertions, 165 deletions
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc index c438c54cea..802200013d 100644 --- a/cmdline/cmdline_parser_test.cc +++ b/cmdline/cmdline_parser_test.cc @@ -20,6 +20,7 @@ #include "gtest/gtest.h" +#include "jdwp_provider.h" #include "experimental_flags.h" #include "parsed_options.h" #include "runtime.h" @@ -364,48 +365,35 @@ TEST_F(CmdlineParserTest, DISABLED_TestXGcOption) { } // TEST_F /* - * {"-Xrunjdwp:_", "-agentlib:jdwp=_"} + * { "-XjdwpProvider:_" } */ -TEST_F(CmdlineParserTest, TestJdwpOptions) { - /* - * Test success - */ +TEST_F(CmdlineParserTest, TestJdwpProviderEmpty) { { - /* - * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" - */ - JDWP::JdwpOptions opt = JDWP::JdwpOptions(); - opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; - opt.port = 8000; - opt.server = true; + EXPECT_SINGLE_PARSE_DEFAULT_VALUE(JdwpProvider::kInternal, "", M::JdwpProvider); + } +} // TEST_F - const char *opt_args = "-Xrunjdwp:transport=dt_socket,address=8000,server=y"; +TEST_F(CmdlineParserTest, TestJdwpProviderDefault) { + const char* opt_args = "-XjdwpProvider:default"; + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider); +} // TEST_F - EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); - } +TEST_F(CmdlineParserTest, TestJdwpProviderInternal) { + const char* opt_args = "-XjdwpProvider:internal"; + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider); +} // TEST_F - { - /* - * "Example: -agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n\n"); - */ - JDWP::JdwpOptions opt = JDWP::JdwpOptions(); - opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; - opt.host = "localhost"; - opt.port = 6500; - opt.server = false; - - const char *opt_args = "-agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n"; - - EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); - } +TEST_F(CmdlineParserTest, TestJdwpProviderNone) { + const char* opt_args = "-XjdwpProvider:none"; + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kNone, opt_args, M::JdwpProvider); +} // TEST_F - /* - * Test failures - */ - EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:help", CmdlineResult::kUsage); // usage for help only - EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:blabla", CmdlineResult::kFailure); // invalid subarg - EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=help", CmdlineResult::kUsage); // usage for help only - EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=blabla", CmdlineResult::kFailure); // invalid subarg +TEST_F(CmdlineParserTest, TestJdwpProviderHelp) { + EXPECT_SINGLE_PARSE_FAIL("-XjdwpProvider:help", CmdlineResult::kUsage); +} // TEST_F + +TEST_F(CmdlineParserTest, TestJdwpProviderFail) { + EXPECT_SINGLE_PARSE_FAIL("-XjdwpProvider:blablabla", CmdlineResult::kFailure); } // TEST_F /* diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index f12ef971af..5c887f8a73 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -34,6 +34,7 @@ #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" #include "read_barrier_config.h" @@ -64,123 +65,27 @@ struct CmdlineType<Unit> : CmdlineTypeParser<Unit> { }; template <> -struct CmdlineType<JDWP::JdwpOptions> : CmdlineTypeParser<JDWP::JdwpOptions> { +struct CmdlineType<JdwpProvider> : CmdlineTypeParser<JdwpProvider> { /* - * Handle one of the JDWP name/value pairs. - * - * JDWP options are: - * help: if specified, show help message and bail - * transport: may be dt_socket or dt_shmem - * address: for dt_socket, "host:port", or just "port" when listening - * server: if "y", wait for debugger to attach; if "n", attach to debugger - * timeout: how long to wait for debugger to connect / listen - * - * Useful with server=n (these aren't supported yet): - * onthrow=<exception-name>: connect to debugger when exception thrown - * onuncaught=y|n: connect to debugger when uncaught exception thrown - * launch=<command-line>: launch the debugger itself - * - * The "transport" option is required, as is "address" if server=n. + * Handle a single JDWP provider name. Must be either 'internal', 'default', or the file name of + * an agent. A plugin will make use of this and the jdwpOptions to set up jdwp when appropriate. */ - Result Parse(const std::string& options) { - VLOG(jdwp) << "ParseJdwpOptions: " << options; - - if (options == "help") { + Result Parse(const std::string& option) { + if (option == "help") { return Result::Usage( - "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" - "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n"); - } - - const std::string s; - - std::vector<std::string> pairs; - Split(options, ',', &pairs); - - JDWP::JdwpOptions jdwp_options; - - for (const std::string& jdwp_option : pairs) { - std::string::size_type equals_pos = jdwp_option.find('='); - if (equals_pos == std::string::npos) { - return Result::Failure(s + - "Can't parse JDWP option '" + jdwp_option + "' in '" + options + "'"); - } - - Result parse_attempt = ParseJdwpOption(jdwp_option.substr(0, equals_pos), - jdwp_option.substr(equals_pos + 1), - &jdwp_options); - if (parse_attempt.IsError()) { - // We fail to parse this JDWP option. - return parse_attempt; - } - } - - if (jdwp_options.transport == JDWP::kJdwpTransportUnknown) { - return Result::Failure(s + "Must specify JDWP transport: " + options); - } - if (!jdwp_options.server && (jdwp_options.host.empty() || jdwp_options.port == 0)) { - return Result::Failure(s + "Must specify JDWP host and port when server=n: " + options); - } - - return Result::Success(std::move(jdwp_options)); - } - - Result ParseJdwpOption(const std::string& name, const std::string& value, - JDWP::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 { - return Result::Failure("JDWP transport not supported: " + value); - } - } else if (name == "server") { - if (value == "n") { - jdwp_options->server = false; - } else if (value == "y") { - jdwp_options->server = true; - } else { - return Result::Failure("JDWP option 'server' must be 'y' or 'n'"); - } - } else if (name == "suspend") { - if (value == "n") { - jdwp_options->suspend = false; - } else if (value == "y") { - jdwp_options->suspend = true; - } else { - return Result::Failure("JDWP option 'suspend' must be 'y' or 'n'"); - } - } 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()) { - return Result::Failure("JDWP address missing port: " + value); - } - char* end; - uint64_t port = strtoul(port_string.c_str(), &end, 10); - if (*end != '\0' || port > 0xffff) { - return Result::Failure("JDWP address has junk in port field: " + value); - } - jdwp_options->port = port; - } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") { - /* valid but unsupported */ - LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'"; + "Example: -XjdwpProvider:none to disable JDWP\n" + "Example: -XjdwpProvider:internal for internal jdwp implementation\n" + "Example: -XjdwpProvider:default for the default jdwp implementation" + " (currently internal)\n"); + } else if (option == "internal" || option == "default") { + return Result::Success(JdwpProvider::kInternal); + } else if (option == "none") { + return Result::Success(JdwpProvider::kNone); } else { - LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'"; + return Result::Failure(std::string("not a valid jdwp provider: ") + option); } - - return Result::SuccessNoValue(); } - - static const char* Name() { return "JdwpOptions"; } + static const char* Name() { return "JdwpProvider"; } }; template <size_t Divisor> diff --git a/runtime/Android.bp b/runtime/Android.bp index 6477347a6e..1e5fe16e19 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -470,6 +470,7 @@ gensrcs { "instrumentation.h", "indirect_reference_table.h", "invoke_type.h", + "jdwp_provider.h", "jdwp/jdwp.h", "jdwp/jdwp_constants.h", "lock_word.h", @@ -602,6 +603,7 @@ art_cc_test { "intern_table_test.cc", "interpreter/safe_math_test.cc", "interpreter/unstarted_runtime_test.cc", + "jdwp/jdwp_options_test.cc", "java_vm_ext_test.cc", "jit/profile_compilation_info_test.cc", "leb128_test.cc", diff --git a/runtime/debugger.cc b/runtime/debugger.cc index b5ae09f701..c85c2336b0 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -328,6 +328,7 @@ 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_; @@ -364,6 +365,20 @@ bool DebuggerActiveMethodInspectionCallback::IsMethodSafeToJit(ArtMethod* m) { return !Dbg::MethodHasAnyBreakpoints(m); } +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_); @@ -736,6 +751,7 @@ 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() { diff --git a/runtime/debugger.h b/runtime/debugger.h index d5bad8dc67..74018137a0 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -53,18 +53,22 @@ 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_); +}; + struct DebuggerDdmCallback : public DdmCallback { void DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); }; -struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback { - bool IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED) - OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); - bool IsMethodSafeToJit(ArtMethod* m) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); +struct InternalDebuggerControlCallback : public DebuggerControlCallback { + void StartDebugger() OVERRIDE; + void StopDebugger() OVERRIDE; + bool IsDebuggerConfigured() OVERRIDE; }; - /* * Invoke-during-breakpoint support. */ @@ -251,7 +255,8 @@ class Dbg { } // Configures JDWP with parsed command-line options. - static void ConfigureJdwp(const JDWP::JdwpOptions& jdwp_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(); @@ -789,6 +794,7 @@ class Dbg { 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. diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h index d712b10bc2..b491c3ee5c 100644 --- a/runtime/jdwp/jdwp.h +++ b/runtime/jdwp/jdwp.h @@ -98,14 +98,15 @@ bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs); * How we talk to the debugger. */ enum JdwpTransportType { - kJdwpTransportUnknown = 0, + 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 = kJdwpTransportUnknown; + JdwpTransportType transport = kJdwpTransportNone; bool server = false; bool suspend = false; std::string host = ""; @@ -114,6 +115,8 @@ struct JdwpOptions { bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs); +bool ParseJdwpOptions(const std::string& options, JdwpOptions* jdwp_options); + struct JdwpEvent; class JdwpNetStateBase; struct ModBasket; diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc index e275554721..63f5dc8b69 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -37,6 +37,119 @@ 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 */ diff --git a/runtime/jdwp/jdwp_options_test.cc b/runtime/jdwp/jdwp_options_test.cc new file mode 100644 index 0000000000..10c52e8cf8 --- /dev/null +++ b/runtime/jdwp/jdwp_options_test.cc @@ -0,0 +1,79 @@ +/* + * 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_provider.h b/runtime/jdwp_provider.h new file mode 100644 index 0000000000..849ba211fe --- /dev/null +++ b/runtime/jdwp_provider.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 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_PROVIDER_H_ +#define ART_RUNTIME_JDWP_PROVIDER_H_ + +#include <ios> + +#include "base/macros.h" +#include "base/logging.h" + +namespace art { + +enum class JdwpProvider { + kNone, + kInternal, +}; + +std::ostream& operator<<(std::ostream& os, const JdwpProvider& rhs); + +} // namespace art +#endif // ART_RUNTIME_JDWP_PROVIDER_H_ diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc index 88a78ab4be..787646dd21 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -157,8 +157,9 @@ static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) { return Dbg::IsDebuggerActive(); } -static jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) { - return Dbg::IsJdwpConfigured(); +static jboolean VMDebug_isDebuggingEnabled(JNIEnv* env, jclass) { + ScopedObjectAccess soa(env); + return Runtime::Current()->GetRuntimeCallbacks()->IsDebuggerConfigured(); } static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) { diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index a3c00364a1..47309edfd7 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -92,8 +92,11 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .IntoKey(M::CheckJni) .Define("-Xjniopts:forcecopy") .IntoKey(M::JniOptsForceCopy) - .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_"}) - .WithType<JDWP::JdwpOptions>() + .Define("-XjdwpProvider:_") + .WithType<JdwpProvider>() + .IntoKey(M::JdwpProvider) + .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_", "-XjdwpOptions:_"}) + .WithType<std::string>() .IntoKey(M::JdwpOptions) // TODO Re-enable -agentlib: once I have a good way to transform the values. // .Define("-agentlib:_") diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 1cdeb7c77c..9c527e7fcd 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -355,7 +355,7 @@ Runtime::~Runtime() { } // Make sure our internal threads are dead before we start tearing down things they're using. - Dbg::StopJdwp(); + GetRuntimeCallbacks()->StopDebugger(); delete signal_catcher_; // Make sure all other non-daemon threads have terminated, and all daemon threads are suspended. @@ -871,8 +871,10 @@ void Runtime::InitNonZygoteOrPostFork( StartSignalCatcher(); // Start the JDWP thread. If the command-line debugger flags specified "suspend=y", - // this will pause the runtime, so we probably want this to come last. - Dbg::StartJdwp(); + // this will pause the runtime (in the internal debugger implementation), so we probably want + // this to come last. + ScopedObjectAccess soa(Thread::Current()); + GetRuntimeCallbacks()->StartDebugger(); } void Runtime::StartSignalCatcher() { @@ -1221,8 +1223,24 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { dump_gc_performance_on_shutdown_ = runtime_options.Exists(Opt::DumpGCPerformanceOnShutdown); - if (runtime_options.Exists(Opt::JdwpOptions)) { - Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions)); + jdwp_options_ = runtime_options.GetOrDefault(Opt::JdwpOptions); + jdwp_provider_ = runtime_options.GetOrDefault(Opt::JdwpProvider); + switch (jdwp_provider_) { + case JdwpProvider::kNone: { + LOG(WARNING) << "Disabling all JDWP support."; + 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; + } } callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback()); callbacks_->AddClassLoadCallback(Dbg::GetClassLoadCallback()); @@ -1509,6 +1527,7 @@ static bool EnsureJvmtiPlugin(Runtime* runtime, } // Is the process debuggable? Otherwise, do not attempt to load the plugin. + // TODO Support a crimped jvmti for non-debuggable runtimes. if (!runtime->IsJavaDebuggable()) { *error_msg = "Process is not debuggable."; return false; diff --git a/runtime/runtime.h b/runtime/runtime.h index 476b71f169..89caac4f3c 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -35,6 +35,7 @@ #include "experimental_flags.h" #include "gc_root.h" #include "instrumentation.h" +#include "jdwp_provider.h" #include "obj_ptr.h" #include "offsets.h" #include "process_state.h" @@ -696,6 +697,14 @@ class Runtime { return madvise_random_access_; } + const std::string& GetJdwpOptions() { + return jdwp_options_; + } + + JdwpProvider GetJdwpProvider() const { + return jdwp_provider_; + } + private: static void InitPlatformSignalHandlers(); @@ -953,6 +962,12 @@ class Runtime { // Whether zygote code is in a section that should not start threads. bool zygote_no_threads_; + // The string containing requested jdwp options + std::string jdwp_options_; + + // The jdwp provider we were configured with. + JdwpProvider jdwp_provider_; + // Saved environment. class EnvSnapshot { public: diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc index 40d7889565..cd3c0b7c88 100644 --- a/runtime/runtime_callbacks.cc +++ b/runtime/runtime_callbacks.cc @@ -49,6 +49,35 @@ void RuntimeCallbacks::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8 } } +void RuntimeCallbacks::AddDebuggerControlCallback(DebuggerControlCallback* cb) { + debugger_control_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveDebuggerControlCallback(DebuggerControlCallback* cb) { + Remove(cb, &debugger_control_callbacks_); +} + +bool RuntimeCallbacks::IsDebuggerConfigured() { + for (DebuggerControlCallback* cb : debugger_control_callbacks_) { + if (cb->IsDebuggerConfigured()) { + return true; + } + } + return false; +} + +void RuntimeCallbacks::StartDebugger() { + for (DebuggerControlCallback* cb : debugger_control_callbacks_) { + cb->StartDebugger(); + } +} + +void RuntimeCallbacks::StopDebugger() { + for (DebuggerControlCallback* cb : debugger_control_callbacks_) { + cb->StopDebugger(); + } +} + void RuntimeCallbacks::AddMethodInspectionCallback(MethodInspectionCallback* cb) { method_inspection_callbacks_.push_back(cb); } diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h index baf941a8e1..f405c9fe34 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -62,6 +62,19 @@ class DdmCallback { REQUIRES_SHARED(Locks::mutator_lock_) = 0; }; +class DebuggerControlCallback { + public: + virtual ~DebuggerControlCallback() {} + + // Begin running the debugger. + virtual void StartDebugger() = 0; + // The debugger should begin shutting down since the runtime is ending. This is just advisory + virtual void StopDebugger() = 0; + + // This allows the debugger to tell the runtime if it is configured. + virtual bool IsDebuggerConfigured() = 0; +}; + class RuntimeSigQuitCallback { public: virtual ~RuntimeSigQuitCallback() {} @@ -197,6 +210,17 @@ class RuntimeCallbacks { void AddDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_); void RemoveDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_); + void StartDebugger() REQUIRES_SHARED(Locks::mutator_lock_); + // NO_THREAD_SAFETY_ANALYSIS since this is only called when we are in the middle of shutting down + // and the mutator_lock_ is no longer acquirable. + void StopDebugger() NO_THREAD_SAFETY_ANALYSIS; + bool IsDebuggerConfigured() REQUIRES_SHARED(Locks::mutator_lock_); + + void AddDebuggerControlCallback(DebuggerControlCallback* cb) + REQUIRES_SHARED(Locks::mutator_lock_); + void RemoveDebuggerControlCallback(DebuggerControlCallback* cb) + REQUIRES_SHARED(Locks::mutator_lock_); + private: std::vector<ThreadLifecycleCallback*> thread_callbacks_ GUARDED_BY(Locks::mutator_lock_); @@ -214,6 +238,8 @@ class RuntimeCallbacks { GUARDED_BY(Locks::mutator_lock_); std::vector<DdmCallback*> ddm_callbacks_ GUARDED_BY(Locks::mutator_lock_); + std::vector<DebuggerControlCallback*> debugger_control_callbacks_ + GUARDED_BY(Locks::mutator_lock_); }; } // namespace art diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 2e03562505..4bc824570a 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -43,7 +43,8 @@ RUNTIME_OPTIONS_KEY (std::string, ClassPath) RUNTIME_OPTIONS_KEY (std::string, Image) RUNTIME_OPTIONS_KEY (Unit, CheckJni) RUNTIME_OPTIONS_KEY (Unit, JniOptsForceCopy) -RUNTIME_OPTIONS_KEY (JDWP::JdwpOptions, JdwpOptions) +RUNTIME_OPTIONS_KEY (std::string, JdwpOptions, "") +RUNTIME_OPTIONS_KEY (JdwpProvider, JdwpProvider, JdwpProvider::kInternal) RUNTIME_OPTIONS_KEY (MemoryKiB, MemoryMaximumSize, gc::Heap::kDefaultMaximumSize) // -Xmx RUNTIME_OPTIONS_KEY (MemoryKiB, MemoryInitialSize, gc::Heap::kDefaultInitialSize) // -Xms RUNTIME_OPTIONS_KEY (MemoryKiB, HeapGrowthLimit) // Default is 0 for unlimited |