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"
}
]