Add JVMTI DDMS extension method and event.

Add a new jvmti extension method
'com.android.art.internal.ddm.process_chunk' that can be used to
request that the system process a DDMS chunk with a given type and
data payload. It returns the processed chunk type and data. Agents can
use this to interact with DDMS.

Also add a new jvmti extension event
'com.android.art.internal.ddm.publish_chunk' that will be called
whenever the system wishes to send an unrequested chunk of data to be
processed. This is triggered by code executing 'DdmServer#sendChunk'
or by other internal mechanisms.

Both of these extensions are provided mainly to aid in the maintenence
of backwards compatibility with existing DDMS applications.  Generally
agents should prefer to use the normal JVMTI events and controls over
interpreting DDMS data or calling DDMS functions.

Bug: 62821960
Test: ./test.py --host -j50
Test: ./art/tools/run-jdwp-tests.sh --mode=host \
            --test org.apache.harmony.jpda.tests.jdwp.DDM.DDMTest
Change-Id: I39f22d3d096d12b59713ec7b8b0c08d0d68ff422
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index aef4dfc..9ba7068 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -34,6 +34,7 @@
         "ti_class.cc",
         "ti_class_definition.cc",
         "ti_class_loader.cc",
+        "ti_ddms.cc",
         "ti_dump.cc",
         "ti_extension.cc",
         "ti_field.cc",
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 4814924..62f723d 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1014,14 +1014,21 @@
       return ERR(NONE);
     }
 
-    std::unique_ptr<jvmtiEventCallbacks> tmp(new jvmtiEventCallbacks());
-    memset(tmp.get(), 0, sizeof(jvmtiEventCallbacks));
+    // Lock the event_info_mutex_ while we replace the callbacks.
+    ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+    art::WriterMutexLock lk(art::Thread::Current(), art_env->event_info_mutex_);
+    std::unique_ptr<ArtJvmtiEventCallbacks> tmp(new ArtJvmtiEventCallbacks());
+    // Copy over the extension events.
+    tmp->CopyExtensionsFrom(art_env->event_callbacks.get());
+    // Never overwrite the extension events.
     size_t copy_size = std::min(sizeof(jvmtiEventCallbacks),
                                 static_cast<size_t>(size_of_callbacks));
     copy_size = art::RoundDown(copy_size, sizeof(void*));
+    // Copy non-extension events.
     memcpy(tmp.get(), callbacks, copy_size);
 
-    ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks = std::move(tmp);
+    // replace the event table.
+    art_env->event_callbacks = std::move(tmp);
 
     return ERR(NONE);
   }
@@ -1077,8 +1084,10 @@
                                               jint extension_event_index,
                                               jvmtiExtensionEvent callback) {
     ENSURE_VALID_ENV(env);
-    // We do not have any extension events, so any call is illegal.
-    return ExtensionUtil::SetExtensionEventCallback(env, extension_event_index, callback);
+    return ExtensionUtil::SetExtensionEventCallback(env,
+                                                    extension_event_index,
+                                                    callback,
+                                                    &gEventHandler);
   }
 
   static jvmtiError GetPotentialCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 97801e0..682b82b 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -68,7 +68,7 @@
   jvmtiCapabilities capabilities;
 
   EventMasks event_masks;
-  std::unique_ptr<jvmtiEventCallbacks> event_callbacks;
+  std::unique_ptr<ArtJvmtiEventCallbacks> event_callbacks;
 
   // Tagging is specific to the jvmtiEnv.
   std::unique_ptr<ObjectTagTable> object_tag_table;
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 7f77f90..5344e0f 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -80,16 +80,17 @@
   fn(GarbageCollectionStart,  ArtJvmtiEvent::kGarbageCollectionStart)                \
   fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish)               \
   fn(ObjectFree,              ArtJvmtiEvent::kObjectFree)                            \
-  fn(VMObjectAlloc,           ArtJvmtiEvent::kVmObjectAlloc)
+  fn(VMObjectAlloc,           ArtJvmtiEvent::kVmObjectAlloc)                         \
+  fn(DdmPublishChunk,         ArtJvmtiEvent::kDdmPublishChunk)
 
 template <ArtJvmtiEvent kEvent>
 struct EventFnType {
 };
 
-#define EVENT_FN_TYPE(name, enum_name)               \
-template <>                                          \
-struct EventFnType<enum_name> {                      \
-  using type = decltype(jvmtiEventCallbacks().name); \
+#define EVENT_FN_TYPE(name, enum_name)                    \
+template <>                                               \
+struct EventFnType<enum_name> {                           \
+  using type = decltype(ArtJvmtiEventCallbacks().name);   \
 };
 
 FORALL_EVENT_TYPES(EVENT_FN_TYPE)
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 6a64441..d1d606d 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -60,6 +60,45 @@
 
 namespace openjdkjvmti {
 
+void ArtJvmtiEventCallbacks::CopyExtensionsFrom(const ArtJvmtiEventCallbacks* cb) {
+  if (art::kIsDebugBuild) {
+    ArtJvmtiEventCallbacks clean;
+    DCHECK_EQ(memcmp(&clean, this, sizeof(clean)), 0)
+        << "CopyExtensionsFrom called with initialized eventsCallbacks!";
+  }
+  if (cb != nullptr) {
+    memcpy(this, cb, sizeof(*this));
+  } else {
+    memset(this, 0, sizeof(*this));
+  }
+}
+
+jvmtiError ArtJvmtiEventCallbacks::Set(jint index, jvmtiExtensionEvent cb) {
+  switch (index) {
+    case static_cast<jint>(ArtJvmtiEvent::kDdmPublishChunk):
+      DdmPublishChunk = reinterpret_cast<ArtJvmtiEventDdmPublishChunk>(cb);
+      return OK;
+    default:
+      return ERR(ILLEGAL_ARGUMENT);
+  }
+}
+
+
+bool IsExtensionEvent(jint e) {
+  return e >= static_cast<jint>(ArtJvmtiEvent::kMinEventTypeVal) &&
+      e <= static_cast<jint>(ArtJvmtiEvent::kMaxEventTypeVal) &&
+      IsExtensionEvent(static_cast<ArtJvmtiEvent>(e));
+}
+
+bool IsExtensionEvent(ArtJvmtiEvent e) {
+  switch (e) {
+    case ArtJvmtiEvent::kDdmPublishChunk:
+      return true;
+    default:
+      return false;
+  }
+}
+
 bool EventMasks::IsEnabledAnywhere(ArtJvmtiEvent event) {
   return global_event_mask.Test(event) || unioned_thread_event_mask.Test(event);
 }
@@ -213,6 +252,38 @@
                                  args...);
 }
 
+static void SetupDdmTracking(art::DdmCallback* listener, bool enable) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (enable) {
+    art::Runtime::Current()->GetRuntimeCallbacks()->AddDdmCallback(listener);
+  } else {
+    art::Runtime::Current()->GetRuntimeCallbacks()->RemoveDdmCallback(listener);
+  }
+}
+
+class JvmtiDdmChunkListener : public art::DdmCallback {
+ public:
+  explicit JvmtiDdmChunkListener(EventHandler* handler) : handler_(handler) {}
+
+  void DdmPublishChunk(uint32_t type, const art::ArrayRef<const uint8_t>& data)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kDdmPublishChunk)) {
+      art::Thread* self = art::Thread::Current();
+      handler_->DispatchEvent<ArtJvmtiEvent::kDdmPublishChunk>(
+          self,
+          static_cast<JNIEnv*>(self->GetJniEnv()),
+          static_cast<jint>(type),
+          static_cast<jint>(data.size()),
+          reinterpret_cast<const jbyte*>(data.data()));
+    }
+  }
+
+ private:
+  EventHandler* handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(JvmtiDdmChunkListener);
+};
+
 class JvmtiAllocationListener : public art::gc::AllocationListener {
  public:
   explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {}
@@ -924,6 +995,9 @@
 // Handle special work for the given event type, if necessary.
 void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
   switch (event) {
+    case ArtJvmtiEvent::kDdmPublishChunk:
+      SetupDdmTracking(ddm_listener_.get(), enable);
+      return;
     case ArtJvmtiEvent::kVmObjectAlloc:
       SetupObjectAllocationTracking(alloc_listener_.get(), enable);
       return;
@@ -1104,6 +1178,7 @@
 
 EventHandler::EventHandler() {
   alloc_listener_.reset(new JvmtiAllocationListener(this));
+  ddm_listener_.reset(new JvmtiDdmChunkListener(this));
   gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
   method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
   monitor_listener_.reset(new JvmtiMonitorListener(this));
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index aed24e5..a99ed7b 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -28,13 +28,14 @@
 
 struct ArtJvmTiEnv;
 class JvmtiAllocationListener;
+class JvmtiDdmChunkListener;
 class JvmtiGcPauseListener;
 class JvmtiMethodTraceListener;
 class JvmtiMonitorListener;
 
 // an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
 // retransformation capable and incapable loading
-enum class ArtJvmtiEvent {
+enum class ArtJvmtiEvent : jint {
     kMinEventTypeVal = JVMTI_MIN_EVENT_TYPE_VAL,
     kVmInit = JVMTI_EVENT_VM_INIT,
     kVmDeath = JVMTI_EVENT_VM_DEATH,
@@ -68,9 +69,33 @@
     kObjectFree = JVMTI_EVENT_OBJECT_FREE,
     kVmObjectAlloc = JVMTI_EVENT_VM_OBJECT_ALLOC,
     kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1,
-    kMaxEventTypeVal = kClassFileLoadHookRetransformable,
+    kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2,
+    kMaxEventTypeVal = kDdmPublishChunk,
 };
 
+using ArtJvmtiEventDdmPublishChunk = void (*)(jvmtiEnv *jvmti_env,
+                                              JNIEnv* jni_env,
+                                              jint data_type,
+                                              jint data_len,
+                                              const jbyte* data);
+
+struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks {
+  ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr) {
+    memset(this, 0, sizeof(jvmtiEventCallbacks));
+  }
+
+  // Copies extension functions from other callback struct if it exists. There must not have been
+  // any modifications to this struct when it is called.
+  void CopyExtensionsFrom(const ArtJvmtiEventCallbacks* cb);
+
+  jvmtiError Set(jint index, jvmtiExtensionEvent cb);
+
+  ArtJvmtiEventDdmPublishChunk DdmPublishChunk;
+};
+
+bool IsExtensionEvent(jint e);
+bool IsExtensionEvent(ArtJvmtiEvent e);
+
 // Convert a jvmtiEvent into a ArtJvmtiEvent
 ALWAYS_INLINE static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e);
 
@@ -245,6 +270,7 @@
   EventMask global_mask;
 
   std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
+  std::unique_ptr<JvmtiDdmChunkListener> ddm_listener_;
   std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
   std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
   std::unique_ptr<JvmtiMonitorListener> monitor_listener_;
diff --git a/openjdkjvmti/ti_ddms.cc b/openjdkjvmti/ti_ddms.cc
new file mode 100644
index 0000000..be7e65d
--- /dev/null
+++ b/openjdkjvmti/ti_ddms.cc
@@ -0,0 +1,87 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <functional>
+#include <vector>
+
+#include "ti_ddms.h"
+
+#include <endian.h>
+
+#include "art_jvmti.h"
+#include "base/array_ref.h"
+#include "debugger.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError DDMSUtil::HandleChunk(jvmtiEnv* env,
+                                 jint type_in,
+                                 jint length_in,
+                                 const jbyte* data_in,
+                                 /*out*/jint* type_out,
+                                 /*out*/jint* data_length_out,
+                                 /*out*/jbyte** data_out) {
+  constexpr uint32_t kDdmHeaderSize = sizeof(uint32_t) * 2;
+  if (env == nullptr || data_in == nullptr || data_out == nullptr || data_length_out == nullptr) {
+    return ERR(NULL_POINTER);
+  } else if (length_in < static_cast<jint>(kDdmHeaderSize)) {
+    // need to get type and length at least.
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadStateChange(self, art::ThreadState::kNative);
+
+  art::ArrayRef<const jbyte> data_arr(data_in, length_in);
+  std::vector<uint8_t> out_data;
+  if (!art::Dbg::DdmHandleChunk(self->GetJniEnv(),
+                                type_in,
+                                data_arr,
+                                /*out*/reinterpret_cast<uint32_t*>(type_out),
+                                /*out*/&out_data)) {
+    LOG(WARNING) << "Something went wrong with handling the ddm chunk.";
+    return ERR(INTERNAL);
+  } else {
+    jvmtiError error = OK;
+    JvmtiUniquePtr<jbyte[]> ret = AllocJvmtiUniquePtr<jbyte[]>(env, out_data.size(), &error);
+    if (error != OK) {
+      return error;
+    }
+    memcpy(ret.get(), out_data.data(), out_data.size());
+    *data_out = ret.release();
+    *data_length_out = static_cast<jint>(out_data.size());
+    return OK;
+  }
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_ddms.h b/openjdkjvmti/ti_ddms.h
new file mode 100644
index 0000000..1ea7548
--- /dev/null
+++ b/openjdkjvmti/ti_ddms.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_DDMS_H_
+#define ART_OPENJDKJVMTI_TI_DDMS_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class DDMSUtil {
+ public:
+  static jvmtiError HandleChunk(jvmtiEnv* env,
+                                jint type_in,
+                                jint length_in,
+                                const jbyte* data_in,
+                                /*out*/ jint* type_out,
+                                /*out*/ jint* data_length_out,
+                                /*out*/ jbyte** data_out);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_DDMS_H_
diff --git a/openjdkjvmti/ti_extension.cc b/openjdkjvmti/ti_extension.cc
index fbed964..d3e0912 100644
--- a/openjdkjvmti/ti_extension.cc
+++ b/openjdkjvmti/ti_extension.cc
@@ -34,8 +34,11 @@
 #include "ti_extension.h"
 
 #include "art_jvmti.h"
+#include "events.h"
 #include "ti_allocator.h"
+#include "ti_ddms.h"
 #include "ti_heap.h"
+#include "thread-inl.h"
 
 namespace openjdkjvmti {
 
@@ -202,6 +205,27 @@
     return error;
   }
 
+  // DDMS extension
+  error = add_extension(
+      reinterpret_cast<jvmtiExtensionFunction>(DDMSUtil::HandleChunk),
+      "com.android.art.internal.ddm.process_chunk",
+      "Handles a single ddms chunk request and returns a response. The reply data is in the ddms"
+      " chunk format. It returns the processed chunk. This is provided for backwards compatibility"
+      " reasons only. Agents should avoid making use of this extension when possible and instead"
+      " use the other JVMTI entrypoints explicitly.",
+      {                                                           // NOLINT[whitespace/braces] [4]
+        { "type_in", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+        { "length_in", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+        { "data_in", JVMTI_KIND_IN_BUF, JVMTI_TYPE_JBYTE, false },
+        { "type_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
+        { "data_len_out", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false },
+        { "data_out", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_JBYTE, false }
+      },
+      { ERR(NULL_POINTER), ERR(ILLEGAL_ARGUMENT), ERR(OUT_OF_MEMORY) });
+  if (error != ERR(NONE)) {
+    return error;
+  }
+
   // Copy into output buffer.
 
   *extension_count_ptr = ext_vector.size();
@@ -230,20 +254,133 @@
 }
 
 
-jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError ExtensionUtil::GetExtensionEvents(jvmtiEnv* env,
                                              jint* extension_count_ptr,
                                              jvmtiExtensionEventInfo** extensions) {
-  // We don't have any extension events at the moment.
-  *extension_count_ptr = 0;
-  *extensions = nullptr;
+  std::vector<jvmtiExtensionEventInfo> ext_vector;
+
+  // Holders for allocated values.
+  std::vector<JvmtiUniquePtr<char[]>> char_buffers;
+  std::vector<JvmtiUniquePtr<jvmtiParamInfo[]>> param_buffers;
+
+  auto add_extension = [&](ArtJvmtiEvent extension_event_index,
+                           const char* id,
+                           const char* short_description,
+                           const std::vector<CParamInfo>& params) {
+    DCHECK(IsExtensionEvent(extension_event_index));
+    jvmtiExtensionEventInfo event_info;
+    jvmtiError error;
+
+    event_info.extension_event_index = static_cast<jint>(extension_event_index);
+
+    JvmtiUniquePtr<char[]> id_ptr = CopyString(env, id, &error);
+    if (id_ptr == nullptr) {
+      return error;
+    }
+    event_info.id = id_ptr.get();
+    char_buffers.push_back(std::move(id_ptr));
+
+    JvmtiUniquePtr<char[]> descr = CopyString(env, short_description, &error);
+    if (descr == nullptr) {
+      return error;
+    }
+    event_info.short_description = descr.get();
+    char_buffers.push_back(std::move(descr));
+
+    event_info.param_count = params.size();
+    if (!params.empty()) {
+      JvmtiUniquePtr<jvmtiParamInfo[]> params_ptr =
+          AllocJvmtiUniquePtr<jvmtiParamInfo[]>(env, params.size(), &error);
+      if (params_ptr == nullptr) {
+        return error;
+      }
+      event_info.params = params_ptr.get();
+      param_buffers.push_back(std::move(params_ptr));
+
+      for (jint i = 0; i != event_info.param_count; ++i) {
+        event_info.params[i] = params[i].ToParamInfo(env, &char_buffers, &error);
+        if (error != OK) {
+          return error;
+        }
+      }
+    } else {
+      event_info.params = nullptr;
+    }
+
+    ext_vector.push_back(event_info);
+
+    return ERR(NONE);
+  };
+
+  jvmtiError error;
+  error = add_extension(
+      ArtJvmtiEvent::kDdmPublishChunk,
+      "com.android.art.internal.ddm.publish_chunk",
+      "Called when there is new ddms information that the agent or other clients can use. The"
+      " agent is given the 'type' of the ddms chunk and a 'data_size' byte-buffer in 'data'."
+      " The 'data' pointer is only valid for the duration of the publish_chunk event. The agent"
+      " is responsible for interpreting the information present in the 'data' buffer. This is"
+      " provided for backwards-compatibility support only. Agents should prefer to use relevant"
+      " JVMTI events and functions above listening for this event.",
+      {                                                             // NOLINT[whitespace/braces] [4]
+        { "jni_env", JVMTI_KIND_IN_PTR, JVMTI_TYPE_JNIENV, false },
+        { "type", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+        { "data_size", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false },
+        { "data",  JVMTI_KIND_IN_BUF, JVMTI_TYPE_JBYTE, false },
+      });
+  if (error != OK) {
+    return error;
+  }
+
+  // Copy into output buffer.
+
+  *extension_count_ptr = ext_vector.size();
+  JvmtiUniquePtr<jvmtiExtensionEventInfo[]> out_data =
+      AllocJvmtiUniquePtr<jvmtiExtensionEventInfo[]>(env, ext_vector.size(), &error);
+  if (out_data == nullptr) {
+    return error;
+  }
+  memcpy(out_data.get(),
+         ext_vector.data(),
+         ext_vector.size() * sizeof(jvmtiExtensionEventInfo));
+  *extensions = out_data.release();
+
+  // Release all the buffer holders, we're OK now.
+  for (auto& holder : char_buffers) {
+    holder.release();
+  }
+  for (auto& holder : param_buffers) {
+    holder.release();
+  }
+
   return OK;
 }
 
-jvmtiError ExtensionUtil::SetExtensionEventCallback(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                                    jint extension_event_index ATTRIBUTE_UNUSED,
-                                                    jvmtiExtensionEvent callback ATTRIBUTE_UNUSED) {
-  // We do not have any extension events, so any call is illegal.
-  return ERR(ILLEGAL_ARGUMENT);
+jvmtiError ExtensionUtil::SetExtensionEventCallback(jvmtiEnv* env,
+                                                    jint extension_event_index,
+                                                    jvmtiExtensionEvent callback,
+                                                    EventHandler* event_handler) {
+  if (!IsExtensionEvent(extension_event_index)) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+  jvmtiEventMode mode = callback == nullptr ? JVMTI_DISABLE : JVMTI_ENABLE;
+  // Lock the event_info_mutex_ while we set the event to make sure it isn't lost by a concurrent
+  // change to the normal callbacks.
+  {
+    art::WriterMutexLock lk(art::Thread::Current(), art_env->event_info_mutex_);
+    if (art_env->event_callbacks.get() == nullptr) {
+      art_env->event_callbacks.reset(new ArtJvmtiEventCallbacks());
+    }
+    jvmtiError err = art_env->event_callbacks->Set(extension_event_index, callback);
+    if (err != OK) {
+      return err;
+    }
+  }
+  return event_handler->SetEvent(art_env,
+                                 /*event_thread*/nullptr,
+                                 static_cast<ArtJvmtiEvent>(extension_event_index),
+                                 mode);
 }
 
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_extension.h b/openjdkjvmti/ti_extension.h
index d705ba7..18133e9 100644
--- a/openjdkjvmti/ti_extension.h
+++ b/openjdkjvmti/ti_extension.h
@@ -37,6 +37,8 @@
 
 namespace openjdkjvmti {
 
+class EventHandler;
+
 class ExtensionUtil {
  public:
   static jvmtiError GetExtensionFunctions(jvmtiEnv* env,
@@ -49,7 +51,8 @@
 
   static jvmtiError SetExtensionEventCallback(jvmtiEnv* env,
                                               jint extension_event_index,
-                                              jvmtiExtensionEvent callback);
+                                              jvmtiExtensionEvent callback,
+                                              EventHandler* event_handler);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index c7f2453..613e4fe 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -18,6 +18,7 @@
 
 #include <sys/uio.h>
 
+#include <functional>
 #include <memory>
 #include <set>
 #include <vector>
@@ -325,6 +326,7 @@
 bool Dbg::gDisposed = false;
 ObjectRegistry* Dbg::gRegistry = nullptr;
 DebuggerActiveMethodInspectionCallback Dbg::gDebugActiveCallback;
+DebuggerDdmCallback Dbg::gDebugDdmCallback;
 
 // Deoptimization support.
 std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_;
@@ -342,6 +344,10 @@
 Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_;
 Dbg::DbgClassLoadCallback Dbg::class_load_callback_;
 
+void DebuggerDdmCallback::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
+  Dbg::DdmSendChunk(type, data);
+}
+
 bool DebuggerActiveMethodInspectionCallback::IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED) {
   return Dbg::IsDebuggerActive();
 }
@@ -531,6 +537,12 @@
   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.
@@ -4285,47 +4297,28 @@
   }
 }
 
-/*
- * "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();
-  ScopedLocalRef<jbyteArray> dataArray(env, env->NewByteArray(request_length));
+bool Dbg::DdmHandleChunk(JNIEnv* env,
+                         uint32_t type,
+                         const ArrayRef<const jbyte>& data,
+                         /*out*/uint32_t* out_type,
+                         /*out*/std::vector<uint8_t>* out_data) {
+  ScopedLocalRef<jbyteArray> dataArray(env, env->NewByteArray(data.size()));
   if (dataArray.get() == nullptr) {
-    LOG(WARNING) << "byte[] allocation failed: " << request_length;
+    LOG(WARNING) << "byte[] allocation failed: " << data.size();
     env->ExceptionClear();
     return false;
   }
-  env->SetByteArrayRegion(dataArray.get(), 0, request_length,
-                          reinterpret_cast<const jbyte*>(request->data()));
-  request->Skip(request_length);
-
-  // Run through and find all chunks.  [Currently just find the first.]
-  ScopedByteArrayRO contents(env, dataArray.get());
-  if (length != request_length) {
-    LOG(WARNING) << StringPrintf("bad chunk found (len=%u pktLen=%zd)", length, request_length);
-    return false;
-  }
-
+  env->SetByteArrayRegion(dataArray.get(),
+                          0,
+                          data.size(),
+                          reinterpret_cast<const jbyte*>(data.data()));
   // Call "private static Chunk dispatch(int type, byte[] data, int offset, int length)".
-  ScopedLocalRef<jobject> chunk(env, env->CallStaticObjectMethod(WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer,
-                                                                 WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch,
-                                                                 type, dataArray.get(), 0, length));
+  ScopedLocalRef<jobject> chunk(
+      env,
+      env->CallStaticObjectMethod(
+          WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer,
+          WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_dispatch,
+          type, dataArray.get(), 0, data.size()));
   if (env->ExceptionCheck()) {
     LOG(INFO) << StringPrintf("Exception thrown by dispatcher for 0x%08x", type);
     env->ExceptionDescribe();
@@ -4349,30 +4342,78 @@
    *
    * So we're pretty much stuck with copying data around multiple times.
    */
-  ScopedLocalRef<jbyteArray> replyData(env, reinterpret_cast<jbyteArray>(env->GetObjectField(chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data)));
-  jint offset = env->GetIntField(chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset);
-  length = env->GetIntField(chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length);
-  type = env->GetIntField(chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type);
+  ScopedLocalRef<jbyteArray> replyData(
+      env,
+      reinterpret_cast<jbyteArray>(
+          env->GetObjectField(
+              chunk.get(), WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data)));
+  jint offset = env->GetIntField(chunk.get(),
+                                 WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_offset);
+  jint length = env->GetIntField(chunk.get(),
+                                 WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length);
+  *out_type = env->GetIntField(chunk.get(),
+                               WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_type);
 
-  VLOG(jdwp) << StringPrintf("DDM reply: type=0x%08x data=%p offset=%d length=%d", type, replyData.get(), offset, length);
+  VLOG(jdwp) << StringPrintf("DDM reply: type=0x%08x data=%p offset=%d length=%d",
+                             type,
+                             replyData.get(),
+                             offset,
+                             length);
   if (length == 0 || replyData.get() == nullptr) {
     return false;
   }
 
-  const int kChunkHdrLen = 8;
-  uint8_t* reply = new uint8_t[length + kChunkHdrLen];
-  if (reply == nullptr) {
-    LOG(WARNING) << "malloc failed: " << (length + kChunkHdrLen);
+  out_data->resize(length);
+  env->GetByteArrayRegion(replyData.get(),
+                          offset,
+                          length,
+                          reinterpret_cast<jbyte*>(out_data->data()));
+  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;
   }
-  JDWP::Set4BE(reply + 0, type);
-  JDWP::Set4BE(reply + 4, length);
-  env->GetByteArrayRegion(replyData.get(), offset, length, reinterpret_cast<jbyte*>(reply + kChunkHdrLen));
 
-  *pReplyBuf = reply;
-  *pReplyLen = length + kChunkHdrLen;
-
-  VLOG(jdwp) << StringPrintf("dvmHandleDdm returning type=%.4s %p len=%d", reinterpret_cast<char*>(reply), reply, length);
+  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)) {
+    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;
 }
 
@@ -4482,6 +4523,10 @@
   Dbg::PostThreadStartOrStop(t, CHUNK_TYPE("THDE"));
 }
 
+void Dbg::DdmSendChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
+  DdmSendChunk(type, data.size(), data.data());
+}
+
 void Dbg::DdmSendChunk(uint32_t type, size_t byte_count, const uint8_t* buf) {
   CHECK(buf != nullptr);
   iovec vec[1];
diff --git a/runtime/debugger.h b/runtime/debugger.h
index ec37833..c3184e8 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -27,6 +27,7 @@
 #include <string>
 #include <vector>
 
+#include "base/array_ref.h"
 #include "class_linker.h"
 #include "gc_root.h"
 #include "handle.h"
@@ -52,6 +53,11 @@
 class StackVisitor;
 class Thread;
 
+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_);
@@ -647,9 +653,17 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   static void DdmSetThreadNotification(bool enable)
       REQUIRES(!Locks::thread_list_lock_);
+  static bool DdmHandleChunk(
+      JNIEnv* env,
+      uint32_t type,
+      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_);
+  static void DdmSendChunk(uint32_t type, const ArrayRef<const uint8_t>& bytes)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   static void DdmSendChunk(uint32_t type, const std::vector<uint8_t>& bytes)
       REQUIRES_SHARED(Locks::mutator_lock_);
   static void DdmSendChunk(uint32_t type, size_t len, const uint8_t* buf)
@@ -782,6 +796,7 @@
   static bool gDebuggerActive;
 
   static DebuggerActiveMethodInspectionCallback gDebugActiveCallback;
+  static DebuggerDdmCallback gDebugDdmCallback;
 
   // Indicates whether we should drop the JDWP connection because the runtime stops or the
   // debugger called VirtualMachine.Dispose.
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index f8f4b1f..c79f51b 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -16,6 +16,7 @@
 
 #include "org_apache_harmony_dalvik_ddmc_DdmServer.h"
 
+#include "base/array_ref.h"
 #include "base/logging.h"
 #include "debugger.h"
 #include "jni_internal.h"
@@ -31,7 +32,9 @@
   ScopedFastNativeObjectAccess soa(env);
   ScopedByteArrayRO data(env, javaData);
   DCHECK_LE(offset + length, static_cast<int32_t>(data.size()));
-  Dbg::DdmSendChunk(type, length, reinterpret_cast<const uint8_t*>(&data[offset]));
+  ArrayRef<const uint8_t> chunk(reinterpret_cast<const uint8_t*>(&data[offset]),
+                                static_cast<size_t>(length));
+  Runtime::Current()->GetRuntimeCallbacks()->DdmPublishChunk(static_cast<uint32_t>(type), chunk);
 }
 
 static JNINativeMethod gMethods[] = {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 339fe82..40d7889 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -35,6 +35,20 @@
   }
 }
 
+void RuntimeCallbacks::AddDdmCallback(DdmCallback* cb) {
+  ddm_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveDdmCallback(DdmCallback* cb) {
+  Remove(cb, &ddm_callbacks_);
+}
+
+void RuntimeCallbacks::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
+  for (DdmCallback* cb : ddm_callbacks_) {
+    cb->DdmPublishChunk(type, data);
+  }
+}
+
 void RuntimeCallbacks::AddMethodInspectionCallback(MethodInspectionCallback* cb) {
   method_inspection_callbacks_.push_back(cb);
 }
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index c1ba964..baf941a 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -19,6 +19,7 @@
 
 #include <vector>
 
+#include "base/array_ref.h"
 #include "base/macros.h"
 #include "base/mutex.h"
 #include "dex_file.h"
@@ -54,6 +55,13 @@
 //       any state checking (is the listener enabled) in the listener itself. For an example, see
 //       Dbg.
 
+class DdmCallback {
+ public:
+  virtual ~DdmCallback() {}
+  virtual void DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
 class RuntimeSigQuitCallback {
  public:
   virtual ~RuntimeSigQuitCallback() {}
@@ -182,6 +190,13 @@
   void RemoveMethodInspectionCallback(MethodInspectionCallback* cb)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // DDMS callbacks
+  void DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+  void RemoveDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::vector<ThreadLifecycleCallback*> thread_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
@@ -197,6 +212,8 @@
       GUARDED_BY(Locks::mutator_lock_);
   std::vector<MethodInspectionCallback*> method_inspection_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
+  std::vector<DdmCallback*> ddm_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
 };
 
 }  // namespace art
diff --git a/test/1940-ddms-ext/ddm_ext.cc b/test/1940-ddms-ext/ddm_ext.cc
new file mode 100644
index 0000000..cc29df9
--- /dev/null
+++ b/test/1940-ddms-ext/ddm_ext.cc
@@ -0,0 +1,207 @@
+/*
+ * 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 "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "nativehelper/scoped_local_ref.h"
+#include "nativehelper/scoped_primitive_array.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1940DdmExt {
+
+typedef jvmtiError (*DdmHandleChunk)(jvmtiEnv* env,
+                                     jint type_in,
+                                     jint len_in,
+                                     const jbyte* data_in,
+                                     jint* type_out,
+                                     jint* len_data_out,
+                                     jbyte** data_out);
+
+struct DdmsTrackingData {
+  DdmHandleChunk send_ddm_chunk;
+  jclass test_klass;
+  jmethodID publish_method;
+};
+
+template <typename T>
+static void Dealloc(T* t) {
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(t));
+}
+
+template <typename T, typename ...Rest>
+static void Dealloc(T* t, Rest... rs) {
+  Dealloc(t);
+  Dealloc(rs...);
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Test1940_processChunk(JNIEnv* env,
+                                                                    jclass,
+                                                                    jobject chunk) {
+  DdmsTrackingData* data = nullptr;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return nullptr;
+  }
+  CHECK(chunk != nullptr);
+  CHECK(data != nullptr);
+  CHECK(data->send_ddm_chunk != nullptr);
+  ScopedLocalRef<jclass> chunk_class(env, env->FindClass("org/apache/harmony/dalvik/ddmc/Chunk"));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jfieldID type_field_id = env->GetFieldID(chunk_class.get(), "type", "I");
+  jfieldID offset_field_id = env->GetFieldID(chunk_class.get(), "offset", "I");
+  jfieldID length_field_id = env->GetFieldID(chunk_class.get(), "length", "I");
+  jfieldID data_field_id = env->GetFieldID(chunk_class.get(), "data", "[B");
+  jint type = env->GetIntField(chunk, type_field_id);
+  jint off = env->GetIntField(chunk, offset_field_id);
+  jint len = env->GetIntField(chunk, length_field_id);
+  ScopedLocalRef<jbyteArray> chunk_buf(
+      env, reinterpret_cast<jbyteArray>(env->GetObjectField(chunk, data_field_id)));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  ScopedByteArrayRO byte_data(env, chunk_buf.get());
+  jint out_type;
+  jint out_size;
+  jbyte* out_data;
+  if (JvmtiErrorToException(env, jvmti_env, data->send_ddm_chunk(jvmti_env,
+                                                                 type,
+                                                                 len,
+                                                                 &byte_data[off],
+                                                                 /*out*/&out_type,
+                                                                 /*out*/&out_size,
+                                                                 /*out*/&out_data))) {
+    return nullptr;
+  } else {
+    ScopedLocalRef<jbyteArray> chunk_data(env, env->NewByteArray(out_size));
+    env->SetByteArrayRegion(chunk_data.get(), 0, out_size, out_data);
+    Dealloc(out_data);
+    ScopedLocalRef<jobject> res(env, env->NewObject(chunk_class.get(),
+                                                    env->GetMethodID(chunk_class.get(),
+                                                                     "<init>",
+                                                                     "(I[BII)V"),
+                                                    out_type,
+                                                    chunk_data.get(),
+                                                    0,
+                                                    out_size));
+    return res.release();
+  }
+}
+
+static void DeallocParams(jvmtiParamInfo* params, jint n_params) {
+  for (jint i = 0; i < n_params; i++) {
+    Dealloc(params[i].name);
+  }
+}
+
+static void JNICALL PublishCB(jvmtiEnv* jvmti, JNIEnv* jnienv, jint type, jint size, jbyte* bytes) {
+  DdmsTrackingData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  ScopedLocalRef<jbyteArray> res(jnienv, jnienv->NewByteArray(size));
+  jnienv->SetByteArrayRegion(res.get(), 0, size, bytes);
+  jnienv->CallStaticVoidMethod(data->test_klass, data->publish_method, type, res.get());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1940_initializeTest(JNIEnv* env,
+                                                                   jclass,
+                                                                   jclass method_klass,
+                                                                   jobject publish_method) {
+  void* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+    return;
+  } else if (old_data != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
+  DdmsTrackingData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(DdmsTrackingData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  memset(data, 0, sizeof(DdmsTrackingData));
+  data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(method_klass));
+  data->publish_method = env->FromReflectedMethod(publish_method);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  // Get the extensions.
+  jint n_ext = 0;
+  jvmtiExtensionFunctionInfo* infos = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionFunctions(&n_ext, &infos))) {
+    return;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionFunctionInfo* cur_info = &infos[i];
+    if (strcmp("com.android.art.internal.ddm.process_chunk", cur_info->id) == 0) {
+      data->send_ddm_chunk = reinterpret_cast<DdmHandleChunk>(cur_info->func);
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params, cur_info->errors);
+  }
+  // Cleanup the array.
+  Dealloc(infos);
+  if (data->send_ddm_chunk == nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to find memory tracking extensions.");
+    return;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+    return;
+  }
+
+  jint event_index = -1;
+  bool found_event = false;
+  jvmtiExtensionEventInfo* events = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetExtensionEvents(&n_ext, &events))) {
+    return;
+  }
+  for (jint i = 0; i < n_ext; i++) {
+    jvmtiExtensionEventInfo* cur_info = &events[i];
+    if (strcmp("com.android.art.internal.ddm.publish_chunk", cur_info->id) == 0) {
+      found_event = true;
+      event_index = cur_info->extension_event_index;
+    }
+    // Cleanup the cur_info
+    DeallocParams(cur_info->params, cur_info->param_count);
+    Dealloc(cur_info->id, cur_info->short_description, cur_info->params);
+  }
+  // Cleanup the array.
+  Dealloc(events);
+  if (!found_event) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Unable to find ddms extension event.");
+    return;
+  }
+  JvmtiErrorToException(env,
+                        jvmti_env,
+                        jvmti_env->SetExtensionEventCallback(
+                            event_index, reinterpret_cast<jvmtiExtensionEvent>(PublishCB)));
+  return;
+}
+
+}  // namespace Test1940DdmExt
+}  // namespace art
diff --git a/test/1940-ddms-ext/expected.txt b/test/1940-ddms-ext/expected.txt
new file mode 100644
index 0000000..cf4ad50
--- /dev/null
+++ b/test/1940-ddms-ext/expected.txt
@@ -0,0 +1,7 @@
+Sending data [1, 2, 3, 4, 5, 6, 7, 8]
+MyDdmHandler: Chunk received: Chunk(Type: 0xDEADBEEF, Len: 8, data: [1, 2, 3, 4, 5, 6, 7, 8])
+MyDdmHandler: Putting value 0x800025
+MyDdmHandler: Chunk returned: Chunk(Type: 0xFADE7357, Len: 8, data: [0, 0, 0, 0, 0, -128, 0, 37])
+JVMTI returned chunk: Chunk(Type: 0xFADE7357, Len: 8, data: [0, 0, 0, 0, 0, -128, 0, 37])
+Sending chunk: Chunk(Type: 0xDEADBEEF, Len: 8, data: [9, 10, 11, 12, 13, 14, 15, 16])
+Chunk published: Chunk(Type: 0xDEADBEEF, Len: 8, data: [9, 10, 11, 12, 13, 14, 15, 16])
diff --git a/test/1940-ddms-ext/info.txt b/test/1940-ddms-ext/info.txt
new file mode 100644
index 0000000..e1d35ae
--- /dev/null
+++ b/test/1940-ddms-ext/info.txt
@@ -0,0 +1 @@
+Tests the jvmti-extension to get allocated memory snapshot.
diff --git a/test/1940-ddms-ext/run b/test/1940-ddms-ext/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/1940-ddms-ext/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1940-ddms-ext/src-art/art/Test1940.java b/test/1940-ddms-ext/src-art/art/Test1940.java
new file mode 100644
index 0000000..f0ee710
--- /dev/null
+++ b/test/1940-ddms-ext/src-art/art/Test1940.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package art;
+
+import org.apache.harmony.dalvik.ddmc.*;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.zip.Adler32;
+import java.nio.*;
+
+public class Test1940 {
+  public static final int DDMS_TYPE_INDEX = 0;
+  public static final int DDMS_LEN_INDEX = 4;
+  public static final int DDMS_HEADER_LENGTH = 8;
+  public static final int MY_DDMS_TYPE = 0xDEADBEEF;
+  public static final int MY_DDMS_RESPONSE_TYPE = 0xFADE7357;
+
+  public static final class TestError extends Error {
+    public TestError(String s) { super(s); }
+  }
+
+  private static void checkEq(Object a, Object b) {
+    if (!a.equals(b)) {
+      throw new TestError("Failure: " + a + " != " + b);
+    }
+  }
+
+  private static String printChunk(Chunk k) {
+    byte[] out = new byte[k.length];
+    System.arraycopy(k.data, k.offset, out, 0, k.length);
+    return String.format("Chunk(Type: 0x%X, Len: %d, data: %s)",
+        k.type, k.length, Arrays.toString(out));
+  }
+
+  private static final class MyDdmHandler extends ChunkHandler {
+    public void connected() {}
+    public void disconnected() {}
+    public Chunk handleChunk(Chunk req) {
+      // For this test we will simply calculate the checksum
+      checkEq(req.type, MY_DDMS_TYPE);
+      System.out.println("MyDdmHandler: Chunk received: " + printChunk(req));
+      ByteBuffer b = ByteBuffer.wrap(new byte[8]);
+      Adler32 a = new Adler32();
+      a.update(req.data, req.offset, req.length);
+      b.order(ByteOrder.BIG_ENDIAN);
+      long val = a.getValue();
+      b.putLong(val);
+      System.out.printf("MyDdmHandler: Putting value 0x%X\n", val);
+      Chunk ret = new Chunk(MY_DDMS_RESPONSE_TYPE, b.array(), 0, 8);
+      System.out.println("MyDdmHandler: Chunk returned: " + printChunk(ret));
+      return ret;
+    }
+  }
+
+  public static final ChunkHandler SINGLE_HANDLER = new MyDdmHandler();
+
+  public static void HandlePublish(int type, byte[] data) {
+    System.out.println("Chunk published: " + printChunk(new Chunk(type, data, 0, data.length)));
+  }
+
+  public static void run() throws Exception {
+    initializeTest(
+        Test1940.class,
+        Test1940.class.getDeclaredMethod("HandlePublish", Integer.TYPE, new byte[0].getClass()));
+    // Test sending chunk directly.
+    DdmServer.registerHandler(MY_DDMS_TYPE, SINGLE_HANDLER);
+    DdmServer.registrationComplete();
+    byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
+    System.out.println("Sending data " + Arrays.toString(data));
+    Chunk res = processChunk(data);
+    System.out.println("JVMTI returned chunk: " + printChunk(res));
+
+    // Test sending chunk through DdmServer#sendChunk
+    Chunk c = new Chunk(
+        MY_DDMS_TYPE, new byte[] { 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }, 0, 8);
+    System.out.println("Sending chunk: " + printChunk(c));
+    DdmServer.sendChunk(c);
+  }
+
+  private static Chunk processChunk(byte[] val) {
+    return processChunk(new Chunk(MY_DDMS_TYPE, val, 0, val.length));
+  }
+
+  private static native void initializeTest(Class<?> k, Method m);
+  private static native Chunk processChunk(Chunk val);
+}
diff --git a/test/1940-ddms-ext/src/Main.java b/test/1940-ddms-ext/src/Main.java
new file mode 100644
index 0000000..1fd4cd3
--- /dev/null
+++ b/test/1940-ddms-ext/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1940.run();
+  }
+}
diff --git a/test/1940-ddms-ext/src/art/Test1940.java b/test/1940-ddms-ext/src/art/Test1940.java
new file mode 100644
index 0000000..c8dc19c
--- /dev/null
+++ b/test/1940-ddms-ext/src/art/Test1940.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package art;
+
+public class Test1940 {
+  public static void run() throws Exception {
+    throw new RuntimeException("Should not be called. Should use src-art/art/Test1940.java");
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 17ef114..ba24119 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -263,7 +263,10 @@
     shared_libs: [
         "libbase",
     ],
-    header_libs: ["libopenjdkjvmti_headers"],
+    header_libs: [
+        "libnativehelper_header_only",
+        "libopenjdkjvmti_headers",
+    ],
     include_dirs: ["art/test/ti-agent"],
 }
 
@@ -282,6 +285,7 @@
         "912-classes/classes_art.cc",
         "936-search-onload/search_onload.cc",
         "983-source-transform-verify/source_transform.cc",
+        "1940-ddms-ext/ddm_ext.cc",
     ],
 }
 
diff --git a/tools/libjdwp_art_failures.txt b/tools/libjdwp_art_failures.txt
index 354bee8..fd711bb 100644
--- a/tools/libjdwp_art_failures.txt
+++ b/tools/libjdwp_art_failures.txt
@@ -97,5 +97,11 @@
   result: EXEC_FAILED,
   bug: 69121056,
   name: "org.apache.harmony.jpda.tests.jdwp.ObjectReference.IsCollectedTest#testIsCollected001"
+},
+{
+  description: "Test for ddms extensions that are not implemented for prebuilt-libjdwp",
+  result: EXEC_FAILED,
+  bug: 69169846,
+  name: "org.apache.harmony.jpda.tests.jdwp.DDM.DDMTest#testChunk001"
 }
 ]
diff --git a/tools/libjdwp_oj_art_failures.txt b/tools/libjdwp_oj_art_failures.txt
index 787c4d2..3d06bcf 100644
--- a/tools/libjdwp_oj_art_failures.txt
+++ b/tools/libjdwp_oj_art_failures.txt
@@ -67,5 +67,11 @@
   result: EXEC_FAILED,
   bug: 69121056,
   name: "org.apache.harmony.jpda.tests.jdwp.ObjectReference.IsCollectedTest#testIsCollected001"
+},
+{
+  description: "Test for ddms extensions that are not yet implemented",
+  result: EXEC_FAILED,
+  bug: 69169846,
+  name: "org.apache.harmony.jpda.tests.jdwp.DDM.DDMTest#testChunk001"
 }
 ]