Basic JDWP option parsing...

...and just enough code to silence all the UNIMPLEMENTED warnings you get
during normal usage.

Change-Id: I56820ac971b88581c4cb1f462c13331d8fa04c43
diff --git a/src/debugger.cc b/src/debugger.cc
index 46644e7..c8f3cec 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -16,8 +16,131 @@
 
 #include "debugger.h"
 
+#include <sys/uio.h>
+
 namespace art {
 
+// Was there a -Xrunjdwp or -agent argument on the command-line?
+static bool gJdwpConfigured = false;
+
+// Broken-down JDWP options. (Only valid if gJdwpConfigured is true.)
+static JDWP::JdwpTransportType gJdwpTransport;
+static bool gJdwpServer;
+static bool gJdwpSuspend;
+static std::string gJdwpHost;
+static int gJdwpPort;
+
+// Runtime JDWP state.
+static JDWP::JdwpState* gJdwpState = NULL;
+static bool gDebuggerConnected;  // debugger or DDMS is connected.
+static bool gDebuggerActive;     // debugger is making requests.
+
+/*
+ * 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.
+ */
+static bool ParseJdwpOption(const std::string& name, const std::string& value) {
+  if (name == "transport") {
+    if (value == "dt_socket") {
+      gJdwpTransport = JDWP::kJdwpTransportSocket;
+    } else if (value == "dt_android_adb") {
+      gJdwpTransport = JDWP::kJdwpTransportAndroidAdb;
+    } else {
+      LOG(ERROR) << "JDWP transport not supported: " << value;
+      return false;
+    }
+  } else if (name == "server") {
+    if (value == "n") {
+      gJdwpServer = false;
+    } else if (value == "y") {
+      gJdwpServer = true;
+    } else {
+      LOG(ERROR) << "JDWP option 'server' must be 'y' or 'n'";
+      return false;
+    }
+  } else if (name == "suspend") {
+    if (value == "n") {
+      gJdwpSuspend = false;
+    } else if (value == "y") {
+      gJdwpSuspend = 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;
+    gJdwpHost.clear();
+    std::string::size_type colon = value.find(':');
+    if (colon != std::string::npos) {
+      gJdwpHost = 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;
+    long port = strtol(port_string.c_str(), &end, 10);
+    if (*end != '\0') {
+      LOG(ERROR) << "JDWP address has junk in port field: " << value;
+      return false;
+    }
+    gJdwpPort = 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;
+}
+
+/*
+ * Parse the latter half of a -Xrunjdwp/-agentlib:jdwp= string, e.g.:
+ * "transport=dt_socket,address=8000,server=y,suspend=n"
+ */
+bool Dbg::ParseJdwpOptions(const std::string& options) {
+  std::vector<std::string> pairs;
+  Split(options, ',', pairs);
+
+  for (size_t i = 0; i < pairs.size(); ++i) {
+    std::string::size_type equals = pairs[i].find('=');
+    if (equals == std::string::npos) {
+      LOG(ERROR) << "Can't parse JDWP option '" << pairs[i] << "' in '" << options << "'";
+      return false;
+    }
+    ParseJdwpOption(pairs[i].substr(0, equals), pairs[i].substr(equals + 1));
+  }
+
+  if (gJdwpTransport == JDWP::kJdwpTransportUnknown) {
+    LOG(ERROR) << "Must specify JDWP transport: " << options;
+  }
+  if (!gJdwpServer && (gJdwpHost.empty() || gJdwpPort == 0)) {
+    LOG(ERROR) << "Must specify JDWP host and port when server=n: " << options;
+    return false;
+  }
+
+  gJdwpConfigured = true;
+  return true;
+}
+
 bool Dbg::DebuggerStartup() {
   UNIMPLEMENTED(FATAL);
   return false;
@@ -33,7 +156,9 @@
 }
 
 void Dbg::Connected() {
-  UNIMPLEMENTED(FATAL);
+  CHECK(!gDebuggerConnected);
+  LOG(VERBOSE) << "JDWP has attached";
+  gDebuggerConnected = true;
 }
 
 void Dbg::Active() {
@@ -45,13 +170,11 @@
 }
 
 bool Dbg::IsDebuggerConnected() {
-  UNIMPLEMENTED(WARNING);
-  return false;
+  return gDebuggerActive;
 }
 
 bool Dbg::IsDebuggingEnabled() {
-  UNIMPLEMENTED(WARNING);
-  return false; //return gDvm.jdwpConfigured;
+  return gJdwpConfigured;
 }
 
 int64_t Dbg::LastDebuggerActivity() {
@@ -376,10 +499,16 @@
 }
 
 void Dbg::PostThreadStart(Thread* t) {
+  if (!gDebuggerConnected) {
+    return;
+  }
   UNIMPLEMENTED(WARNING);
 }
 
 void Dbg::PostThreadDeath(Thread* t) {
+  if (!gDebuggerConnected) {
+    return;
+  }
   UNIMPLEMENTED(WARNING);
 }
 
@@ -431,12 +560,20 @@
   UNIMPLEMENTED(FATAL);
 }
 
-void Dbg::DdmSendChunk(int type, size_t length, const uint8_t* buf) {
-  UNIMPLEMENTED(WARNING) << "DdmSendChunk(" << type << ", " << length << ", " << (void*) buf << ");";
+void Dbg::DdmSendChunk(int type, size_t byte_count, const uint8_t* buf) {
+  CHECK(buf != NULL);
+  iovec vec[1];
+  vec[0].iov_base = reinterpret_cast<void*>(const_cast<uint8_t*>(buf));
+  vec[0].iov_len = byte_count;
+  Dbg::DdmSendChunkV(type, vec, 1);
 }
 
 void Dbg::DdmSendChunkV(int type, const struct iovec* iov, int iovcnt) {
-  UNIMPLEMENTED(FATAL);
+  if (gJdwpState == NULL) {
+    LOG(VERBOSE) << "Debugger thread not active, ignoring DDM send: " << type;
+  } else {
+    JDWP::DdmSendChunkV(gJdwpState, type, iov, iovcnt);
+  }
 }
 
 }  // namespace art
diff --git a/src/debugger.h b/src/debugger.h
index b66fd11..388248b 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -23,8 +23,10 @@
 
 #include <pthread.h>
 
-#include "object.h"
+#include <string>
+
 #include "jdwp/jdwp.h"
+#include "object.h"
 
 namespace art {
 
@@ -62,6 +64,7 @@
 
 class Dbg {
 public:
+  static bool ParseJdwpOptions(const std::string& options);
   static bool DebuggerStartup();
   static void DebuggerShutdown();
 
diff --git a/src/runtime.cc b/src/runtime.cc
index 08a1d9f..46ec8fb 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -7,10 +7,9 @@
 #include <limits>
 #include <vector>
 
-#include "ScopedLocalRef.h"
-#include "UniquePtr.h"
 #include "class_linker.h"
 #include "class_loader.h"
+#include "debugger.h"
 #include "heap.h"
 #include "image.h"
 #include "intern_table.h"
@@ -22,6 +21,7 @@
 #include "space.h"
 #include "thread.h"
 #include "thread_list.h"
+#include "UniquePtr.h"
 
 // TODO: this drags in cutil/log.h, which conflicts with our logging.h.
 #include "JniConstants.h"
@@ -255,6 +255,13 @@
       parsed->images_.push_back(option.substr(strlen("-Ximage:")).data());
     } else if (option.starts_with("-Xcheck:jni")) {
       parsed->check_jni_ = true;
+    } else if (option.starts_with("-Xrunjdwp:") || option.starts_with("-agentlib:jdwp=")) {
+      std::string tail = option.substr(option[1] == 'X' ? 10 : 15).ToString();
+      if (tail == "help" || !Dbg::ParseJdwpOptions(tail)) {
+        LOG(FATAL) << "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
+                   << "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n";
+        return NULL;
+      }
     } else if (option.starts_with("-Xms")) {
       size_t size = ParseMemoryOption(option.substr(strlen("-Xms")).data(), 1024);
       if (size == 0) {
diff --git a/test/etc/host-run-test-jar b/test/etc/host-run-test-jar
index 1fba19d..3ac2688 100755
--- a/test/etc/host-run-test-jar
+++ b/test/etc/host-run-test-jar
@@ -116,5 +116,5 @@
 fi
 
 $INVOKE_WITH $gdb $exe $gdbargs "-Xbootclasspath:${bpath}" \
-    $DEX_VERIFY $DEX_OPTIMIZE $DEX_DEBUG -ea \
+    $DEX_VERIFY $DEX_OPTIMIZE $DEX_DEBUG ${DEBUG_OPTS} -ea \
     -cp test.jar Main "$@"
diff --git a/test/etc/push-and-run-test-jar b/test/etc/push-and-run-test-jar
index 858ba74..ec7bf46 100755
--- a/test/etc/push-and-run-test-jar
+++ b/test/etc/push-and-run-test-jar
@@ -102,13 +102,13 @@
 fi
 
 if [ "$DEBUG" = "y" ]; then
-    DEX_DEBUG="-agentlib:jdwp=transport=dt_android_adb,server=y,suspend=y"
+  DEX_DEBUG="-agentlib:jdwp=transport=dt_android_adb,server=y,suspend=y"
 fi
 
 if [ "$ZYGOTE" = "y" ]; then
   adb shell cd /data \; dvz -classpath $TEST_NAME.jar Main "$@"
 else
-  cmdline="cd /data; $INVOKE_WITH $OATEXEC -Xjnigreflimit:256 \
+  cmdline="cd /data; $INVOKE_WITH $OATEXEC ${DEX_DEBUG} -Xjnigreflimit:256 \
       -Ximage:/data/art-test/core.art \
       -cp /data/art-test/$TEST_NAME.jar \
       Main"