Merge "Add JVMTI DDMS extension method and event."
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"
 }
 ]