From 46d48e247e7b0a23c62596cbbb76f0913a7f20b9 Mon Sep 17 00:00:00 2001 From: mmorozov Date: Fri, 23 Jun 2023 14:49:26 +0300 Subject: [PATCH] Add heap dumper core Change-Id: I1f31de7423d8cc5de0ca6c40a3bf6c5736a7851d Signed-off-by: mmorozov --- platforms/unix/libpandabase/file.h | 5 + runtime/BUILD.gn | 4 + runtime/CMakeLists.txt | 5 + runtime/exceptions.cpp | 3 + runtime/mem/gc/gc.h | 2 + runtime/mem/gc/lang/gc_lang.cpp | 14 + runtime/mem/gc/lang/gc_lang.h | 2 + runtime/mem/heapdumper/heap_dumper.cpp | 99 ++ runtime/mem/heapdumper/heap_dumper.h | 38 + .../mem/heapdumper/hprof/hprof_verifier.cpp | 320 +++++++ runtime/mem/heapdumper/hprof/hprof_verifier.h | 56 ++ runtime/mem/heapdumper/hprof/hprof_writer.cpp | 869 ++++++++++++++++++ runtime/mem/heapdumper/hprof/hprof_writer.h | 90 ++ .../heapdumper/hprof/hprof_writer_buffer.cpp | 218 +++++ .../heapdumper/hprof/hprof_writer_buffer.h | 164 ++++ runtime/options.yaml | 17 +- runtime/runtime.cpp | 7 + runtime/signal_handler.cpp | 14 + runtime/signal_handler.h | 12 + runtime/tests/heap_dumper_test.cpp | 332 +++++++ .../interpreter/test_runtime_interface.h | 4 + 21 files changed, 2274 insertions(+), 1 deletion(-) create mode 100644 runtime/mem/heapdumper/heap_dumper.cpp create mode 100644 runtime/mem/heapdumper/heap_dumper.h create mode 100644 runtime/mem/heapdumper/hprof/hprof_verifier.cpp create mode 100644 runtime/mem/heapdumper/hprof/hprof_verifier.h create mode 100644 runtime/mem/heapdumper/hprof/hprof_writer.cpp create mode 100644 runtime/mem/heapdumper/hprof/hprof_writer.h create mode 100644 runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp create mode 100644 runtime/mem/heapdumper/hprof/hprof_writer_buffer.h create mode 100644 runtime/tests/heap_dumper_test.cpp diff --git a/platforms/unix/libpandabase/file.h b/platforms/unix/libpandabase/file.h index 6e1c99021..70ef84241 100644 --- a/platforms/unix/libpandabase/file.h +++ b/platforms/unix/libpandabase/file.h @@ -212,6 +212,11 @@ public: return lseek(fd_, 0, SEEK_END) == 0; } + bool SetSeekFromCurrent(off_t offset) + { + return lseek(fd_, offset, SEEK_CUR) >= 0; + } + private: int fd_; diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index a7cb03dae..40526327c 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -176,6 +176,10 @@ source_set("libarkruntime_set_static") { "mem/heap_manager.cpp", "mem/heap_space.cpp", "mem/heap_verifier.cpp", + "mem/heapdumper/heap_dumper.cpp", + "mem/heapdumper/hprof/hprof_verifier.cpp", + "mem/heapdumper/hprof/hprof_writer.cpp", + "mem/heapdumper/hprof/hprof_writer_buffer.cpp", "mem/internal_allocator.cpp", "mem/mem_stats.cpp", "mem/mem_stats_additional_info.cpp", diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 50e93ae68..a97224d35 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -93,6 +93,10 @@ set(SOURCES mem/panda_string.cpp mem/memory_manager.cpp mem/heap_space.cpp + mem/heapdumper/hprof/hprof_writer_buffer.cpp + mem/heapdumper/hprof/hprof_writer.cpp + mem/heapdumper/hprof/hprof_verifier.cpp + mem/heapdumper/heap_dumper.cpp methodtrace/trace.cpp mark_word.cpp method.cpp @@ -945,6 +949,7 @@ add_gtests( tests/math_helpers_test.cpp tests/stack_walker_test.cpp tests/time_utils_test.cpp + tests/heap_dumper_test.cpp ${INVOKE_HELPER} ${PANDA_ROOT}/compiler/tests/panda_runner.cpp ) diff --git a/runtime/exceptions.cpp b/runtime/exceptions.cpp index 7993d5ef8..4c5026898 100644 --- a/runtime/exceptions.cpp +++ b/runtime/exceptions.cpp @@ -366,6 +366,9 @@ void ThrowOutOfMemoryError(ManagedThread *thread, const PandaString &msg) void ThrowOutOfMemoryError(const PandaString &msg) { auto *thread = ManagedThread::GetCurrent(); + if (Runtime::GetCurrent()->GetOptions().WasSetHeapDumpOnOutOfMemory()) { + thread->GetVM()->GetGC()->DumpHeap(); + } ThrowOutOfMemoryError(thread, msg); } diff --git a/runtime/mem/gc/gc.h b/runtime/mem/gc/gc.h index f39ee8643..65e7489b4 100644 --- a/runtime/mem/gc/gc.h +++ b/runtime/mem/gc/gc.h @@ -405,6 +405,8 @@ public: return false; } + virtual bool DumpHeap() = 0; + /** * Return true of ref is an instance of reference or it's ancestor, false otherwise */ diff --git a/runtime/mem/gc/lang/gc_lang.cpp b/runtime/mem/gc/lang/gc_lang.cpp index 8f01a5900..a7a708ed1 100644 --- a/runtime/mem/gc/lang/gc_lang.cpp +++ b/runtime/mem/gc/lang/gc_lang.cpp @@ -13,6 +13,7 @@ * limitations under the License. */ +#include "runtime/mem/heapdumper/heap_dumper.h" #include "runtime/include/panda_vm.h" #include "runtime/mem/gc/lang/gc_lang.h" #include "runtime/mem/object_helpers-inl.h" @@ -60,6 +61,19 @@ size_t GCLang::VerifyHeap() return HeapVerifier(GetPandaVm()->GetHeapManager()).VerifyAll(); } +template +bool GCLang::DumpHeap() +{ + auto file_name = PandaString(Runtime::GetCurrent()->GetOptions().GetHeapDumpOutputFile()); + LOG(INFO, RUNTIME) << "Saving heap dump in: " << file_name << " ..."; + HeapDumper dumper; + if (!dumper.HprofDumpHeap(nullptr, file_name)) { + return false; + } + LOG(INFO, RUNTIME) << "Saved."; + return true; +} + template void GCLang::UpdateRefsToMovedObjectsInPygoteSpace() { diff --git a/runtime/mem/gc/lang/gc_lang.h b/runtime/mem/gc/lang/gc_lang.h index 37f0239f6..525b355e0 100644 --- a/runtime/mem/gc/lang/gc_lang.h +++ b/runtime/mem/gc/lang/gc_lang.h @@ -46,6 +46,8 @@ public: bool IsMutatorAllowed() override; + bool DumpHeap() override; + protected: ~GCLang() override; void UpdateRefsToMovedObjectsInPygoteSpace() override; diff --git a/runtime/mem/heapdumper/heap_dumper.cpp b/runtime/mem/heapdumper/heap_dumper.cpp new file mode 100644 index 000000000..66f9d8b03 --- /dev/null +++ b/runtime/mem/heapdumper/heap_dumper.cpp @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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 "runtime/mem/heapdumper/heap_dumper.h" +#include "runtime/include/coretypes/dyn_objects.h" +#include "runtime/include/language_config.h" +#include "runtime/include/managed_thread.h" +#include "runtime/include/thread_scopes.h" +#include "libpandabase/macros.h" +#include "runtime/mem/gc/gc_root.h" +#include "runtime/mem/heapdumper/hprof/hprof_verifier.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer.h" +#include "runtime/mem/rendezvous.h" + +namespace panda::mem { +template +bool HeapDumper::HprofDumpHeap(PandaVM *core_vm, const PandaString &file_name) +{ + constexpr bool IS_STATIC = LanguageConfig::LANG_TYPE == LANG_TYPE_STATIC; + + auto *thread = ManagedThread::GetCurrent(); + auto *vm = (core_vm == nullptr) ? thread->GetVM() : core_vm; + HprofWriter writer {}; + ASSERT(thread != nullptr); + ScopedChangeThreadStatus sts(thread, ThreadStatus::RUNNING); + ScopedSuspendAllThreadsRunning ssat(vm->GetRendezvous()); + + if (!writer.SetFileName(file_name)) { + return false; + } + writer.DumpHeader(); + + writer.DumpUnknownStackTrace(); + + auto dump_all = [&](ObjectHeader *object) { + // NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation) + if constexpr (IS_STATIC) { + auto *cls = object->ClassAddr(); + ASSERT(cls != nullptr); + if (cls->IsClassClass()) { + auto object_cls = Class::FromClassObject(object); + writer.DumpClassString(object_cls); + if (object_cls->IsInitialized()) { + writer.DumpClass(object_cls); + } + } else { + writer.DumpObject(object); + } + // NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation) + } else { + [[maybe_unused]] auto *cls = object->ClassAddr(); + ASSERT(cls != nullptr && cls->IsDynamicClass()); + auto dyn_class = coretypes::DynClass::Cast(object); + writer.DumpClassString(object, dyn_class); + writer.DumpClass(object, dyn_class); + if (!cls->IsHClass()) { + writer.DumpObject(object, cls); + } + } + }; + vm->GetHeapManager()->GetObjectAllocator().AsObjectAllocator()->IterateOverObjects(dump_all); + + // Dump roots + // TODO(sshadrin): define for dynamic language and PandaAssemblyLanguageConfig + // NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation) + if constexpr (IS_STATIC && !std::is_same_v) { + RootManager root_manager; + root_manager.SetPandaVM(vm); + auto root_visitor = [&writer](const GCRoot &gc_root) { writer.DumpRoot(gc_root); }; + root_manager.VisitNonHeapRoots(root_visitor); + } + + // TODO(sshadrin): add StringTable->VisitStrings for adding string objects without live refs + + writer.DumpHeapDumpEnd(); + writer.PrintToFile(); + + if (!HprofVerifier::VerifyFormat(file_name)) { + LOG(ERROR, RUNTIME) << "Wrong hprof format"; + return false; + } + + return true; +} + +TEMPLATE_CLASS_LANGUAGE_CONFIG(HeapDumper); +} // namespace panda::mem diff --git a/runtime/mem/heapdumper/heap_dumper.h b/runtime/mem/heapdumper/heap_dumper.h new file mode 100644 index 000000000..ce2308d08 --- /dev/null +++ b/runtime/mem/heapdumper/heap_dumper.h @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_MEM_HEAPDUMPER_HEAP_DUMPER_H +#define PANDA_RUNTIME_MEM_HEAPDUMPER_HEAP_DUMPER_H + +#include "runtime/include/mem/panda_string.h" +#include "runtime/include/panda_vm.h" + +namespace panda::mem { +template +class HeapDumper { +public: + HeapDumper() = default; + + ~HeapDumper() = default; + + bool HprofDumpHeap(PandaVM *core_vm, const PandaString &file_name); + + NO_COPY_SEMANTIC(HeapDumper); + + NO_MOVE_SEMANTIC(HeapDumper); +}; +} // namespace panda::mem + +#endif // PANDA_RUNTIME_MEM_HEAPDUMPER_HEAP_DUMPER_H diff --git a/runtime/mem/heapdumper/hprof/hprof_verifier.cpp b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp new file mode 100644 index 000000000..173bcae1b --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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 "runtime/mem/heapdumper/hprof/hprof_verifier.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer_buffer.h" + +namespace panda::mem { +uint32_t HprofVerifier::GetU4FromFile(os::file::File file) +{ + uint32_t res = 0; + uint8_t num = 0; + for (size_t i = 0; i < U4_LENGTH; i++) { + file.ReadAll(&num, U1_LENGTH); + res += num << (BITS_IN_BYTE * (U4_LENGTH - i - 1)); + } + return res; +} + +uint16_t HprofVerifier::GetU2FromFile(os::file::File file) +{ + uint16_t res = 0; + uint8_t num = 0; + for (size_t i = 0; i < U2_LENGTH; i++) { + file.ReadAll(&num, U1_LENGTH); + res += num << (BITS_IN_BYTE * (U2_LENGTH - i - 1)); + } + return res; +} + +uint8_t HprofVerifier::GetU1FromFile(os::file::File file) +{ + uint8_t res; + file.ReadAll(&res, U1_LENGTH); + return res; +} + +void HprofVerifier::SkipFileContent(os::file::File file, size_t size) +{ + file.SetSeekFromCurrent(size); +} + +size_t HprofVerifier::GetValueLength(uint8_t value_type) +{ + size_t value_length = 0; + switch (static_cast(value_type)) { + case HprofBasicType::BOOLEAN: + case HprofBasicType::BYTE: { + value_length = U1_LENGTH; + break; + } + case HprofBasicType::CHAR: + case HprofBasicType::SHORT: { + value_length = U2_LENGTH; + break; + } + case HprofBasicType::FLOAT: + case HprofBasicType::INT: { + value_length = U4_LENGTH; + break; + } + case HprofBasicType::DOUBLE: + case HprofBasicType::LONG: { + value_length = U8_LENGTH; + break; + } + case HprofBasicType::OBJECT: { + value_length = DEFAULT_ID_LENGTH; + break; + } + default: { + value_length = 0; + break; + } + } + return value_length; +} + +bool HprofVerifier::IsUnknowTag(uint8_t tag_num) +{ + auto tag = static_cast(tag_num); + if (HprofTag::HEAP_SUMMARY >= tag && tag >= HprofTag::STRING_IN_UTF8) { + return false; + } + if (HprofTag::CONTROL_SETTINGS >= tag && tag >= HprofTag::START_THREAD) { + return false; + } + if (tag == HprofTag::HEAP_DUMP_SEGMENT || tag == HprofTag::HEAP_DUMP_END) { + return false; + } + return true; +} + +bool HprofVerifier::IsUnknowSubTag(uint8_t sub_tag_num) +{ + auto sub_tag = static_cast(sub_tag_num); + if (HprofSubTag::ROOT_THREAD_OBJECT >= sub_tag && sub_tag >= HprofSubTag::ROOT_NAPI_GLOBAL) { + return false; + } + if (HprofSubTag::PRIMITTVE_ARRAY_DUMP >= sub_tag && sub_tag >= HprofSubTag::CLASS_DUMP) { + return false; + } + if (sub_tag == HprofSubTag::ROOT_UNKOWN) { + return false; + } + return true; +} + +bool HprofVerifier::VerifyFormat(const PandaString &file_name) +{ + auto file = os::file::Open(file_name, os::file::Mode::READONLY); + if (!file.IsValid()) { + LOG(ERROR, RUNTIME) << "Cannot open the hprof file"; + return false; + } + auto res = VerifyFormatByFile(file); + file.Close(); + return res; +} + +bool HprofVerifier::VerifyFormatByFile(os::file::File file) +{ + auto target_file_length = file.GetFileSize().Value(); + size_t file_length = 0; + std::array magic {}; + file.ReadAll(magic.data(), HprofWriter::MAGIC_SIZE); + if (std::strcmp(&*magic.data(), "JAVA PROFILE 1.0.2") != 0) { + LOG(ERROR, RUNTIME) << "Unknown magic in hprof"; + return false; + } + file_length += HprofWriter::MAGIC_SIZE; + uint32_t id_length = GetU4FromFile(file); + if (id_length != ID_LENGTH_4B && id_length != ID_LENGTH_8B) { + LOG(ERROR, RUNTIME) << "Unknown ID length in hprof"; + return false; + } + // high and low word of time + GetU4FromFile(file); + GetU4FromFile(file); + // size of id and high/low work of time + file_length += U4_LENGTH * 3; + uint8_t tag; + while (file.ReadAll(&tag, 1)) { + // size of tag, time and length + file_length += U1_LENGTH + U4_LENGTH * 2; + if (IsUnknowTag(tag)) { + LOG(ERROR, RUNTIME) << "Unknown Tag in hprof"; + return false; + } + GetU4FromFile(file); // time + uint32_t length = GetU4FromFile(file); + file_length += length; + if (static_cast(tag) != HprofTag::HEAP_DUMP_SEGMENT) { + SkipFileContent(file, length); + continue; + } + uint8_t sub_tag = GetU1FromFile(file); + uint32_t target_length = 1; + if (IsUnknowSubTag(sub_tag)) { + LOG(ERROR, RUNTIME) << "Unknown SubTag in hprof"; + return false; + } + uint16_t size; + uint8_t value_type; + size_t value_length; + uint32_t number; + switch (static_cast(sub_tag)) { + case HprofSubTag::ROOT_UNKOWN: + case HprofSubTag::ROOT_STICKY_CLASS: + case HprofSubTag::ROOT_MONITOR_USED: { + // size of object id + target_length += id_length; + SkipFileContent(file, id_length); + break; + } + case HprofSubTag::ROOT_NAPI_GLOBAL: { + // size of object id and JNI global ref id + target_length += id_length * 2; + SkipFileContent(file, id_length * 2); + break; + } + case HprofSubTag::ROOT_NATIVE_STACK: + case HprofSubTag::ROOT_THREAD_BLOCK: { + // size of object id and thread serial + target_length += id_length + U4_LENGTH; + SkipFileContent(file, id_length + U4_LENGTH); + break; + } + case HprofSubTag::ROOT_NAPI_LOCAL: + case HprofSubTag::ROOT_FRAME: + case HprofSubTag::ROOT_THREAD_OBJECT: { + // size of object id, thread serial and frame number + target_length += id_length + U4_LENGTH * 2; + SkipFileContent(file, id_length + U4_LENGTH * 2); + break; + } + case HprofSubTag::CLASS_DUMP: { + // class id and stack trace serial number + target_length += id_length + U4_LENGTH; + SkipFileContent(file, id_length + U4_LENGTH); + // super class and class loader object id + target_length += id_length * 2; + SkipFileContent(file, id_length * 2); + // signers object id and protection domain object id + target_length += id_length * 2; + SkipFileContent(file, id_length * 2); + // two reserved + target_length += id_length * 2; + SkipFileContent(file, id_length * 2); + // size of instance + target_length += U4_LENGTH; + SkipFileContent(file, U4_LENGTH); + // size of constance pool + target_length += U2_LENGTH; + size = GetU2FromFile(file); + for (size_t i = 0; i < size; i++) { + target_length += U2_LENGTH + U1_LENGTH; + GetU2FromFile(file); + value_type = GetU1FromFile(file); + value_length = GetValueLength(value_type); + if (value_length == 0) { + LOG(ERROR, RUNTIME) << "Unknown value type in hprof file"; + return false; + } + SkipFileContent(file, value_length); + target_length += value_length; + } + // number of static fields + target_length += U2_LENGTH; + size = GetU2FromFile(file); + for (size_t i = 0; i < size; i++) { + target_length += id_length + U1_LENGTH; + SkipFileContent(file, id_length); + value_type = GetU1FromFile(file); + value_length = GetValueLength(value_type); + if (value_length == 0) { + LOG(ERROR, RUNTIME) << "Unknown value type in hprof file"; + return false; + } + SkipFileContent(file, value_length); + target_length += value_length; + } + // number of instance fields + target_length += U2_LENGTH; + size = GetU2FromFile(file); + for (size_t i = 0; i < size; i++) { + target_length += id_length + U1_LENGTH; + SkipFileContent(file, id_length + U1_LENGTH); + } + break; + } + case HprofSubTag::INSTANCE_DUMP: { + // object id, stack trace serial number and class id + target_length += id_length * 2 + U4_LENGTH; + SkipFileContent(file, id_length * 2 + U4_LENGTH); + number = GetU4FromFile(file); + target_length += U4_LENGTH; + SkipFileContent(file, number); + target_length += number; + break; + } + case HprofSubTag::OBJECT_ARRAY_DUMP: { + // object id and stack trace serial number + target_length += id_length + U4_LENGTH; + SkipFileContent(file, id_length + U4_LENGTH); + number = GetU4FromFile(file); + target_length += U4_LENGTH; + SkipFileContent(file, id_length); + target_length += id_length; + SkipFileContent(file, id_length * number); + target_length += id_length * number; + break; + } + case HprofSubTag::PRIMITTVE_ARRAY_DUMP: { + // object id and stack trace serial number + target_length += id_length + U4_LENGTH; + SkipFileContent(file, id_length + U4_LENGTH); + number = GetU4FromFile(file); + target_length += U4_LENGTH; + value_type = GetU1FromFile(file); + target_length += U1_LENGTH; + value_length = GetValueLength(value_type); + if (value_length == 0) { + LOG(ERROR, RUNTIME) << "Unknown value type in hprof file"; + return false; + } + SkipFileContent(file, value_length * number); + target_length += value_length * number; + break; + } + default: { + LOG(ERROR, RUNTIME) << "Unknown SubTag in hprof"; + return false; + } + } + if (length != target_length) { + LOG(ERROR, RUNTIME) << "Mismatched body length in hprof"; + return false; + } + } + if (target_file_length != file_length) { + LOG(ERROR, RUNTIME) << "Mismatched file size in hprof"; + return false; + } + return true; +} + +} // namespace panda::mem diff --git a/runtime/mem/heapdumper/hprof/hprof_verifier.h b/runtime/mem/heapdumper/hprof/hprof_verifier.h new file mode 100644 index 000000000..2db78f393 --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_verifier.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_VERIFIER_H +#define PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_VERIFIER_H + +#include "libpandabase/macros.h" +#include "runtime/include/mem/panda_string.h" + +namespace panda::mem { + +class HprofVerifier { +public: + HprofVerifier() = default; + + ~HprofVerifier() = default; + + NO_COPY_SEMANTIC(HprofVerifier); + + NO_MOVE_SEMANTIC(HprofVerifier); + + static bool VerifyFormat(const PandaString &file_name); + + static uint32_t GetU4FromFile(os::file::File file); + + static uint16_t GetU2FromFile(os::file::File file); + + static uint8_t GetU1FromFile(os::file::File file); + + static size_t GetValueLength(uint8_t value_type); + + static void SkipFileContent(os::file::File file, size_t size); + +private: + static bool VerifyFormatByFile(os::file::File file); + + static bool IsUnknowTag(uint8_t tag_num); + + static bool IsUnknowSubTag(uint8_t sub_tag_num); +}; + +} // namespace panda::mem + +#endif // PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_VERIFIER_H diff --git a/runtime/mem/heapdumper/hprof/hprof_writer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer.cpp new file mode 100644 index 000000000..e654615a2 --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_writer.cpp @@ -0,0 +1,869 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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 "runtime/mem/heapdumper/hprof/hprof_writer.h" +#include "runtime/include/coretypes/dyn_objects.h" +#include "runtime/include/coretypes/string-inl.h" + +namespace panda::mem { + +bool HprofWriter::SetFileName(const PandaString &file_name) +{ + file_name_ = file_name; + auto file = os::file::Open(file_name, os::file::Mode::READWRITECREATE); + if (!file.IsValid()) { + LOG(ERROR, DEBUGGER) << "Cannot open the hprof file"; + return false; + } + file.ClearData(); + file.Close(); + return true; +} + +void HprofWriter::CheckSize() +{ + if (writer_buffer_.GetLength() >= BUFFER_SIZE_WATER_LINE) { + PrintToFile(); + } +} + +void HprofWriter::PrintToFile() +{ + auto file = os::file::Open(file_name_, os::file::Mode::READWRITE); + if (!file.IsValid()) { + LOG(ERROR, DEBUGGER) << "Cannot open the hprof file"; + return; + } + file.SetSeekEnd(); + file.WriteAll(writer_buffer_.GetData(), writer_buffer_.GetLength()); + writer_buffer_.Clear(); + file.Close(); +} + +void HprofWriter::DumpHeader() +{ + writer_buffer_.WriteArrayU1("JAVA PROFILE 1.0.2", MAGIC_SIZE); + writer_buffer_.WriteU4(writer_buffer_.GetIdLength()); + writer_buffer_.AddStartTime(); + CheckSize(); +} + +size_t HprofWriter::DumpTopInfo(HprofTag tag, size_t length) +{ + writer_buffer_.WriteU1(tag); + writer_buffer_.AddTime(); + auto old_pos = writer_buffer_.GetPos(); + writer_buffer_.WriteU4(length); + return old_pos; +} + +size_t HprofWriter::FindOrAddString(const PandaString &cls_name) +{ + size_t string_id = string_storage_.FindString(cls_name); + if (string_id == 0) { + string_id = string_storage_.AddString(cls_name); + } else { + return string_id; + } + size_t cls_name_size = cls_name.length(); + DumpTopInfo(HprofTag::STRING_IN_UTF8, writer_buffer_.GetIdLength() + cls_name_size); + writer_buffer_.WriteId(string_id); + writer_buffer_.WriteArrayU1(cls_name, cls_name_size); + CheckSize(); + return string_id; +} + +void HprofWriter::DumpClassString(Class *cls) +{ + if (!cls->IsLoaded()) { + // TODO(sshadrin): dump unloadclass after class serial number support + return; + } + + PandaString cls_name = PandaString(cls->GetName()); + size_t string_id = FindOrAddString(cls_name); + + if (cls->IsStringClass()) { + FindOrAddString("value"); + } + + // class serial number, stack trace serial number, class id and string id of class name + DumpTopInfo(HprofTag::LOAD_CLASS, U4_LENGTH * 2 + writer_buffer_.GetIdLength() * 2); // 2 multiply + writer_buffer_.AddUnknownClassSerialNumber(); + writer_buffer_.WriteId(cls); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(string_id); + + const Span &static_fields = cls->GetStaticFields(); + for (const Field &field : static_fields) { + auto field_name = GetFieldName(field); + FindOrAddString(field_name); + } + + const Span &instance_fields = cls->GetInstanceFields(); + for (const Field &field : instance_fields) { + auto field_name = GetFieldName(field); + FindOrAddString(field_name); + } + + CheckSize(); +} + +void HprofWriter::DumpClassString(ObjectHeader *obj, coretypes::DynClass *hcls) +{ + DumpTopInfo(HprofTag::LOAD_CLASS, U4_LENGTH * 2 + writer_buffer_.GetIdLength() * 2); // 2 multiply + writer_buffer_.AddUnknownClassSerialNumber(); + writer_buffer_.WriteId(obj); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(hcls); // unknown name + + CheckSize(); +} + +void HprofWriter::DumpHeapDumpEnd() +{ + writer_buffer_.WriteU1(HprofTag::HEAP_DUMP_END); + writer_buffer_.AddTime(); + // HEAP DUMP END don't have body + writer_buffer_.WriteU4(0U); + + CheckSize(); +} + +void HprofWriter::DumpClass(Class *cls) +{ + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::CLASS_DUMP); + writer_buffer_.WriteId(cls); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(cls->GetBase()); + writer_buffer_.WriteId(nullptr); + // Don't have signers object + writer_buffer_.WriteId(0U); + // Don't have protection domain object + writer_buffer_.WriteId(0U); + // Reserved + writer_buffer_.WriteId(0U); + // Reserved + writer_buffer_.WriteId(0U); + // Set instance size if 0 for array class + uint32_t instance_size = 0; + if (!cls->IsVariableSize()) { + instance_size = cls->GetObjectSize(); + } + if (cls->IsStringClass()) { + // Spcial processing for the string type. The instance include object header, 'length' and 'hash' + // The type of 'length' and 'hash' is int + instance_size = ObjectHeader::ObjectHeaderSize() + U4_LENGTH * 2; // 2 multiply + } + writer_buffer_.WriteU4(instance_size); + // don't have constant pool, the size of it is zero + writer_buffer_.WriteU2(0U); + + DumpStaticFields(cls); + + DumpInstanceFields(cls); + + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +void HprofWriter::DumpClass(ObjectHeader *obj, coretypes::DynClass *dyn_class) +{ + auto hcls = dyn_class->ClassAddr(); + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::CLASS_DUMP); + writer_buffer_.WriteId(obj); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(nullptr); // BaseCls + writer_buffer_.WriteId(nullptr); // Classloader + writer_buffer_.WriteId(nullptr); // Don't have signers object + writer_buffer_.WriteId(nullptr); // Don't have protection domain object + writer_buffer_.WriteId(0U); // Reserved + writer_buffer_.WriteId(0U); // Reserved + + uint32_t instance_size = hcls->GetObjectSize(); + writer_buffer_.WriteU4(instance_size); + // don't have constant pool, the size of it is zero + writer_buffer_.WriteU2(0U); + + // iterate "static" fields + size_t hklass_size = instance_size - sizeof(coretypes::DynClass); + size_t body_size = hklass_size - sizeof(HClass); + size_t num_of_fields = body_size / TaggedValue::TaggedTypeSize(); + + writer_buffer_.WriteU2(num_of_fields); // number static fields + + for (size_t i = 0; i < num_of_fields; i++) { + size_t field_offset = sizeof(ObjectHeader) + sizeof(HClass) + i * TaggedValue::TaggedTypeSize(); + writer_buffer_.WriteId(field_offset); // unknown field name + auto tagged_value = ObjectAccessor::GetDynValue(dyn_class, field_offset); + DumpField(&tagged_value); + } + + writer_buffer_.WriteU2(0U); // instance fields + + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +void HprofWriter::DumpField(TaggedValue *tagged_value) +{ + if (tagged_value->IsInt()) { + writer_buffer_.WriteU1(HprofBasicType::INT); + writer_buffer_.WriteU4(tagged_value->GetInt()); + } else if (tagged_value->IsDouble()) { + writer_buffer_.WriteU1(HprofBasicType::DOUBLE); + writer_buffer_.WriteU8(static_cast(tagged_value->GetDouble())); + } else if (tagged_value->IsHeapObject() || tagged_value->IsWeak()) { + writer_buffer_.WriteU1(HprofBasicType::OBJECT); + writer_buffer_.WriteId(tagged_value->GetHeapObject()); + } else if (tagged_value->IsSpecial()) { + if (tagged_value->IsBoolean()) { + writer_buffer_.WriteU1(HprofBasicType::BOOLEAN); + if (tagged_value->IsTrue()) { + writer_buffer_.WriteU1(1U); + } else { + ASSERT(tagged_value->IsFalse()); + writer_buffer_.WriteU1(0U); + } + } else { + // TODO(sshadrin): make dump special values proper way + // "Hole", "Null", "Undefined", "Exception" + writer_buffer_.WriteU1(HprofBasicType::OBJECT); + writer_buffer_.WriteId(nullptr); + } + } else { + LOG(INFO, RUNTIME) << "Unknown tagged_value type"; + } +} + +void HprofWriter::DumpStaticFields(Class *cls) +{ + const Span &static_fields = cls->GetStaticFields(); + writer_buffer_.WriteU2(static_fields.size()); + for (const Field &field : static_fields) { + auto string_id = FindOrAddString(GetFieldName(field)); + writer_buffer_.WriteId(string_id); + size_t offset = field.GetOffset(); + panda_file::Type::TypeId type_id = field.GetTypeId(); + if (type_id == panda_file::Type::TypeId::REFERENCE) { + writer_buffer_.WriteU1(HprofBasicType::OBJECT); + ObjectHeader *field_object = cls->GetFieldObject(offset); + writer_buffer_.WriteId(field_object); + } else { + if (type_id != panda_file::Type::TypeId::VOID) { + switch (type_id) { + case panda_file::Type::TypeId::U1: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::BOOLEAN); + writer_buffer_.WriteU1(val); + break; + } + case panda_file::Type::TypeId::I8: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::BYTE); + writer_buffer_.WriteU1(val); + break; + } + case panda_file::Type::TypeId::U8: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::BYTE); + writer_buffer_.WriteU1(val); + break; + } + case panda_file::Type::TypeId::I16: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::SHORT); + writer_buffer_.WriteU2(val); + break; + } + case panda_file::Type::TypeId::U16: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::CHAR); + writer_buffer_.WriteU2(val); + break; + } + case panda_file::Type::TypeId::I32: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::INT); + writer_buffer_.WriteU4(val); + break; + } + case panda_file::Type::TypeId::U32: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::INT); + writer_buffer_.WriteU4(val); + break; + } + case panda_file::Type::TypeId::F32: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::FLOAT); + writer_buffer_.WriteU4(val); + break; + } + case panda_file::Type::TypeId::U64: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::LONG); + writer_buffer_.WriteU8(val); + break; + } + case panda_file::Type::TypeId::I64: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::LONG); + writer_buffer_.WriteU8(val); + break; + } + case panda_file::Type::TypeId::F64: { + auto val = cls->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(HprofBasicType::DOUBLE); + writer_buffer_.WriteU8(val); + break; + } + default: { + LOG(FATAL, DEBUGGER) << "Unknown type id"; + } + } + } else { + LOG(ERROR, DEBUGGER) << "Have type void"; + } + } + } +} + +void HprofWriter::DumpInstanceFields(Class *cls) +{ + const Span &instance_fields = cls->GetInstanceFields(); + if (cls->IsStringClass()) { + writer_buffer_.WriteU2(instance_fields.size() + 1); + } else { + writer_buffer_.WriteU2(instance_fields.size()); + } + for (const Field &field : instance_fields) { + auto string_id = FindOrAddString(GetFieldName(field)); + writer_buffer_.WriteId(string_id); + panda_file::Type::TypeId type_id = field.GetTypeId(); + if (type_id == panda_file::Type::TypeId::REFERENCE) { + writer_buffer_.WriteU1(HprofBasicType::OBJECT); + } else { + if (type_id != panda_file::Type::TypeId::VOID) { + switch (type_id) { + case panda_file::Type::TypeId::U1: { + writer_buffer_.WriteU1(HprofBasicType::BOOLEAN); + break; + } + case panda_file::Type::TypeId::I8: + case panda_file::Type::TypeId::U8: { + writer_buffer_.WriteU1(HprofBasicType::BYTE); + break; + } + case panda_file::Type::TypeId::I16: { + writer_buffer_.WriteU1(HprofBasicType::SHORT); + break; + } + case panda_file::Type::TypeId::U16: { + writer_buffer_.WriteU1(HprofBasicType::CHAR); + break; + } + case panda_file::Type::TypeId::I32: + case panda_file::Type::TypeId::U32: { + writer_buffer_.WriteU1(HprofBasicType::INT); + break; + } + case panda_file::Type::TypeId::F32: { + writer_buffer_.WriteU1(HprofBasicType::FLOAT); + break; + } + case panda_file::Type::TypeId::U64: + case panda_file::Type::TypeId::I64: { + writer_buffer_.WriteU1(HprofBasicType::LONG); + break; + } + case panda_file::Type::TypeId::F64: { + writer_buffer_.WriteU1(HprofBasicType::DOUBLE); + break; + } + default: { + LOG(FATAL, DEBUGGER) << "Have type void"; + } + } + } else { + LOG(FATAL, DEBUGGER) << "Unknown type id"; + } + } + } + + if (cls->IsStringClass()) { + auto string_id = FindOrAddString("value"); + writer_buffer_.WriteId(string_id); + writer_buffer_.WriteU1(HprofBasicType::OBJECT); + } +} + +void HprofWriter::DumpObject(ObjectHeader *object, HClass *cls) +{ + // dump as class with static fields + if (cls->IsString()) { + // assume structure: 30 bit length + 2 bit flags + 32 bit hash + utf8 str + size_t length_offset = sizeof(ObjectHeader); + size_t data_offset = length_offset + 2 * TaggedValue::TaggedTypeSize(); + + auto messy_length = ObjectAccessor::GetPrimitive(object, length_offset); + auto length = messy_length >> 2U; + auto data = Span(reinterpret_cast(ToUintPtr(object) + data_offset), length); + + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::PRIMITTVE_ARRAY_DUMP); + writer_buffer_.WriteId(object); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteU4(length); + writer_buffer_.WriteU1(HprofBasicType::CHAR); + for (const auto &ch : data) { + writer_buffer_.WriteU2(static_cast(ch)); + } + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + } else if (cls->IsNativePointer()) { + // pass + } else if (cls->IsArray()) { + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::CLASS_DUMP); + writer_buffer_.WriteId(object); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(cls); + writer_buffer_.WriteId(nullptr); + writer_buffer_.WriteId(nullptr); + writer_buffer_.WriteId(nullptr); + writer_buffer_.WriteId(0U); + writer_buffer_.WriteId(0U); + writer_buffer_.WriteU4(0U); + writer_buffer_.WriteU2(0U); + coretypes::Array *array = coretypes::Array::Cast(object); + auto length = array->GetLength(); + writer_buffer_.WriteU2(length); + for (coretypes::ArraySizeT i = 0; i < length; ++i) { + writer_buffer_.WriteId(ObjectHeader::ObjectHeaderSize() + i * TaggedValue::TaggedTypeSize()); + TaggedValue array_element(array->Get(i)); + DumpField(&array_element); + } + writer_buffer_.WriteU2(0U); + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + } else { + // just object + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::CLASS_DUMP); + writer_buffer_.WriteId(object); // class id + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(cls); // super class id + writer_buffer_.WriteId(nullptr); // class loader object id + writer_buffer_.WriteId(nullptr); // signers + writer_buffer_.WriteId(nullptr); // protection domain object id + writer_buffer_.WriteId(0U); // reserved + writer_buffer_.WriteId(0U); // reserved + uint32_t instance_size = cls->GetObjectSize(); + writer_buffer_.WriteU4(instance_size); // size of instance + writer_buffer_.WriteU2(0U); // size of constant pool + + uint32_t num_of_fields = (instance_size - ObjectHeader::ObjectHeaderSize()) / TaggedValue::TaggedTypeSize(); + size_t data_offset = ObjectHeader::ObjectHeaderSize(); + writer_buffer_.WriteU2(num_of_fields); + for (uint32_t i = 0; i < num_of_fields; i++) { + size_t field_offset = data_offset + i * TaggedValue::TaggedTypeSize(); + writer_buffer_.WriteId(field_offset); + auto tagged_value = ObjectAccessor::GetDynValue(object, field_offset); + DumpField(&tagged_value); + } + writer_buffer_.WriteU2(0U); + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + } + CheckSize(); +} + +void HprofWriter::DumpObject(ObjectHeader *object_header) +{ + auto cls = object_header->ClassAddr(); + if (cls->IsArrayClass()) { + if (cls->IsObjectArrayClass()) { + DumpObjectArray(object_header); + } else { + DumpPrimitiveArray(object_header); + } + return; + } + + bool is_string_class = false; + if (cls->IsStringClass()) { + is_string_class = true; + } + + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::INSTANCE_DUMP); + writer_buffer_.WriteId(object_header); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.WriteId(cls); + + auto old_num_pos = writer_buffer_.GetPos(); + // Following bytes number. Will rewrite after counting + writer_buffer_.WriteU4(0U); + while (cls != nullptr) { + const Span &instance_fields = cls->GetInstanceFields(); + for (const Field &field : instance_fields) { + size_t offset = field.GetOffset(); + panda_file::Type::TypeId type_id = field.GetTypeId(); + if (type_id == panda_file::Type::TypeId::REFERENCE) { + ObjectHeader *field_object = object_header->GetFieldObject(offset); + writer_buffer_.WriteId(field_object); + } else { + if (type_id != panda_file::Type::TypeId::VOID) { + switch (type_id) { + case panda_file::Type::TypeId::U1: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(val); + break; + } + case panda_file::Type::TypeId::I8: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(val); + break; + } + case panda_file::Type::TypeId::U8: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU1(val); + break; + } + + case panda_file::Type::TypeId::I16: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU2(val); + break; + } + + case panda_file::Type::TypeId::U16: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU2(val); + break; + } + + case panda_file::Type::TypeId::I32: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU4(val); + break; + } + + case panda_file::Type::TypeId::U32: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU4(val); + break; + } + + case panda_file::Type::TypeId::F32: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU4(val); + break; + } + + case panda_file::Type::TypeId::U64: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU8(val); + break; + } + + case panda_file::Type::TypeId::I64: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU8(val); + break; + } + + case panda_file::Type::TypeId::F64: { + auto val = object_header->GetFieldPrimitive(offset); + writer_buffer_.WriteU8(val); + break; + } + default: { + LOG(FATAL, DEBUGGER) << "Wrong type id"; + } + } + } else { + LOG(ERROR, DEBUGGER) << "Have type void"; + } + } + } + if (cls->IsStringClass()) { + auto data_offset = coretypes::String::GetDataOffset(); + writer_buffer_.WriteId(reinterpret_cast(object_header) + data_offset); + } + cls = cls->GetBase(); + } + + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_num_pos, now_pos - old_num_pos - LENGTH_LENGTH); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + if (is_string_class) { + old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::PRIMITTVE_ARRAY_DUMP); + auto data_offset = coretypes::String::GetDataOffset(); + writer_buffer_.WriteId(reinterpret_cast(object_header) + data_offset); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + auto str = coretypes::String::Cast(object_header); + auto element_num = str->GetLength(); + writer_buffer_.WriteU4(element_num); + writer_buffer_.WriteU1(HprofBasicType::CHAR); + for (size_t i = 0; i < element_num; i++) { + writer_buffer_.WriteU2(str->At(i)); // SUPPRESS_CSA(alpha.core.WasteObjHeader) + } + now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + } + + CheckSize(); +} + +void HprofWriter::DumpObjectArray(ObjectHeader *object_header) +{ + auto cls = object_header->ClassAddr(); + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::OBJECT_ARRAY_DUMP); + writer_buffer_.WriteId(object_header); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + auto arr = coretypes::Array::Cast(object_header); + auto element_num = arr->GetLength(); + writer_buffer_.WriteU4(element_num); + writer_buffer_.WriteId(cls); + for (size_t i = 0; i < element_num; i++) { + writer_buffer_.WriteId(arr->template Get(i)); + } + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +void HprofWriter::DumpPrimitiveArray(ObjectHeader *object_header) +{ + auto cls = object_header->ClassAddr(); + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(HprofSubTag::PRIMITTVE_ARRAY_DUMP); + writer_buffer_.WriteId(object_header); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + auto length_offset = coretypes::Array::GetLengthOffset(); + auto element_num = object_header->GetFieldPrimitive(length_offset); + writer_buffer_.WriteU4(element_num); + + auto type_size = cls->GetComponentSize(); + auto data_offset = coretypes::Array::GetDataOffset(); + panda_file::Type::TypeId type_id = cls->GetComponentType()->GetType().GetId(); + if (type_id == panda_file::Type::TypeId::REFERENCE) { + LOG(ERROR, DEBUGGER) << "Is not primitive array"; + } else { + if (type_id != panda_file::Type::TypeId::VOID) { + switch (type_id) { + case panda_file::Type::TypeId::U1: { + writer_buffer_.WriteU1(HprofBasicType::BOOLEAN); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU1(val); + } + break; + } + case panda_file::Type::TypeId::I8: { + writer_buffer_.WriteU1(HprofBasicType::BYTE); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU1(val); + } + break; + } + case panda_file::Type::TypeId::U8: { + writer_buffer_.WriteU1(HprofBasicType::BYTE); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU1(val); + } + break; + } + case panda_file::Type::TypeId::I16: { + writer_buffer_.WriteU1(HprofBasicType::SHORT); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU2(val); + } + break; + } + case panda_file::Type::TypeId::U16: { + writer_buffer_.WriteU1(HprofBasicType::CHAR); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU2(val); + } + break; + } + case panda_file::Type::TypeId::I32: { + writer_buffer_.WriteU1(HprofBasicType::INT); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU4(val); + } + break; + } + case panda_file::Type::TypeId::U32: { + writer_buffer_.WriteU1(HprofBasicType::INT); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU4(val); + } + break; + } + case panda_file::Type::TypeId::F32: { + writer_buffer_.WriteU1(HprofBasicType::FLOAT); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU4(val); + } + break; + } + case panda_file::Type::TypeId::U64: { + writer_buffer_.WriteU1(HprofBasicType::LONG); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU8(val); + } + break; + } + case panda_file::Type::TypeId::I64: { + writer_buffer_.WriteU1(HprofBasicType::LONG); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU8(val); + } + break; + } + case panda_file::Type::TypeId::F64: { + writer_buffer_.WriteU1(HprofBasicType::DOUBLE); + for (size_t i = 0; i < element_num; i++) { + auto val = object_header->GetFieldPrimitive(data_offset + i * type_size); + writer_buffer_.WriteU8(val); + } + break; + } + default: { + LOG(FATAL, DEBUGGER) << "Unknown type id"; + } + } + } else { + LOG(ERROR, DEBUGGER) << "Have type void"; + } + } + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +void HprofWriter::DumpUnknownStackTrace() +{ + auto old_pos = DumpTopInfo(HprofTag::STACK_TRACE, 0); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + writer_buffer_.AddUnknownThreadSerialNumber(); + // unknown stack don't have frame + writer_buffer_.WriteU4(0U); + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +void HprofWriter::DumpRoot(const GCRoot &gc_root) +{ + auto root_type = ChangeRootType(gc_root.GetType()); + auto object_header = gc_root.GetObjectHeader(); + auto old_pos = DumpTopInfo(HprofTag::HEAP_DUMP_SEGMENT, 0); + writer_buffer_.WriteU1(root_type); + writer_buffer_.WriteId(object_header); + switch (root_type) { + case HprofSubTag::ROOT_NAPI_GLOBAL: { + // TODO(sshadrin): add global reference + writer_buffer_.WriteId(nullptr); + break; + } + case HprofSubTag::ROOT_NAPI_LOCAL: + case HprofSubTag::ROOT_FRAME: { + writer_buffer_.AddUnknownThreadSerialNumber(); + writer_buffer_.AddUnknownFrameNumberInStackTrace(); + break; + } + case HprofSubTag::ROOT_NATIVE_STACK: + case HprofSubTag::ROOT_THREAD_BLOCK: { + writer_buffer_.AddUnknownThreadSerialNumber(); + break; + } + case HprofSubTag::ROOT_THREAD_OBJECT: { + writer_buffer_.AddUnknownThreadSerialNumber(); + writer_buffer_.AddUnknownStackTraceSerialNumber(); + break; + } + // NOLINTNEXTLINE(bugprone-branch-clone) + case HprofSubTag::ROOT_STICKY_CLASS: + case HprofSubTag::ROOT_MONITOR_USED: + case HprofSubTag::ROOT_UNKOWN: + default: { + break; + } + } + auto now_pos = writer_buffer_.GetPos(); + writer_buffer_.ChangeU4(old_pos, now_pos - old_pos - LENGTH_LENGTH); + + CheckSize(); +} + +HprofSubTag HprofWriter::ChangeRootType(RootType type) +{ + HprofSubTag root_type = HprofSubTag::ROOT_UNKOWN; + switch (type) { + case RootType::ROOT_CLASS: { + root_type = HprofSubTag::ROOT_STICKY_CLASS; + break; + } + case RootType::ROOT_FRAME: { + root_type = HprofSubTag::ROOT_FRAME; + break; + } + case RootType::ROOT_THREAD: { + root_type = HprofSubTag::ROOT_THREAD_OBJECT; + break; + } + case RootType::ROOT_NATIVE_GLOBAL: { + root_type = HprofSubTag::ROOT_NAPI_GLOBAL; + break; + } + case RootType::ROOT_NATIVE_LOCAL: { + root_type = HprofSubTag::ROOT_NAPI_LOCAL; + break; + } + case RootType::ROOT_UNKNOWN: + default: { + break; + } + } + return root_type; +} + +} // namespace panda::mem diff --git a/runtime/mem/heapdumper/hprof/hprof_writer.h b/runtime/mem/heapdumper/hprof/hprof_writer.h new file mode 100644 index 000000000..4dd3f7a30 --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_writer.h @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_H +#define PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_H + +#include "runtime/include/class.h" +#include "runtime/mem/vm_handle.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer_buffer.h" + +namespace panda::mem { + +class HprofWriter { +public: + HprofWriter() = default; + + bool SetFileName(const PandaString &file_name); + + void DumpHeader(); + + void DumpHeapDumpEnd(); + + void PrintToFile(); + + static constexpr size_t MAGIC_SIZE = 19; + + static constexpr size_t BUFFER_SIZE_WATER_LINE = 4000; + + size_t FindOrAddString(const PandaString &cls_name); + + size_t DumpTopInfo(HprofTag tag, size_t length); + + void DumpStaticFields(Class *cls); + + void DumpInstanceFields(Class *cls); + + void DumpField(TaggedValue *tagged_value); + + void DumpObjectArray(ObjectHeader *obj); + + void DumpPrimitiveArray(ObjectHeader *obj); + + void DumpClassString(Class *cls); + + void DumpClassString(ObjectHeader *obj, coretypes::DynClass *hcls); + + void DumpClass(Class *cls); + + void DumpClass(ObjectHeader *obj, coretypes::DynClass *dyn_class); + + void DumpObject(ObjectHeader *obj); + + void DumpObject(ObjectHeader *obj, HClass *cls); + + void DumpRoot(const GCRoot &gc_root); + + HprofSubTag ChangeRootType(panda::mem::RootType type); + + void DumpUnknownStackTrace(); + + size_t GetLength() + { + return writer_buffer_.GetLength(); + } + +private: + void CheckSize(); + + PandaString file_name_; + HprofWriterBuffer writer_buffer_; + StringStorage string_storage_; + + static constexpr size_t LENGTH_LENGTH = U4_LENGTH; +}; + +} // namespace panda::mem + +#endif // PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_H diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp new file mode 100644 index 000000000..2176257fd --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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 "runtime/mem/heapdumper/hprof/hprof_writer_buffer.h" +#include "libpandabase/utils/time.h" + +namespace panda::mem { + +static constexpr size_t MASK_INT8 = 0xFF; +static constexpr size_t MASK_INT32 = 0xFFFFFFFF; + +size_t StringStorage::AddString(const PandaString &str) +{ + list_.push_back(str); + size_++; + return list_.size(); +} + +void StringStorage::Clear() +{ + list_.clear(); + size_ = 0; +} + +size_t StringStorage::FindString(const PandaString &str) +{ + auto it = find(list_.begin(), list_.end(), str); + if (it != list_.end()) { + return it - list_.begin() + 1; + } + return 0; +} + +void HprofWriterBuffer::WriteU1(uint8_t input) +{ + buffer_.push_back(input); + length_ += U1_LENGTH; +} + +void HprofWriterBuffer::WriteU1(HprofTag tag) +{ + WriteU1(static_cast(tag)); +} + +void HprofWriterBuffer::WriteU1(HprofSubTag sub_tag) +{ + WriteU1(static_cast(sub_tag)); +} + +void HprofWriterBuffer::WriteU1(HprofBasicType base_type) +{ + WriteU1(static_cast(base_type)); +} + +void HprofWriterBuffer::WriteU2(uint16_t input) +{ + for (size_t i = U2_LENGTH; i > 0; i--) { + auto mov = BITS_IN_BYTE * (i - 1); + buffer_.push_back(static_cast(input >> mov) & MASK_INT8); + } + length_ += U2_LENGTH; +} + +void HprofWriterBuffer::WriteU4(uint32_t input) +{ + for (size_t i = U4_LENGTH; i > 0; i--) { + auto mov = BITS_IN_BYTE * (i - 1); + buffer_.push_back(static_cast(input >> mov) & MASK_INT8); + } + length_ += U4_LENGTH; +} + +void HprofWriterBuffer::WriteU8(uint64_t input) +{ + for (size_t i = U8_LENGTH; i > 0; i--) { + auto mov = BITS_IN_BYTE * (i - 1); + buffer_.push_back(static_cast(input >> mov) & MASK_INT8); + } + length_ += U8_LENGTH; +} + +void HprofWriterBuffer::WriteId(uint32_t input) +{ + if (id_length_ == ID_LENGTH_4B) { + WriteU4(input); + } else if (id_length_ == ID_LENGTH_8B) { + WriteU8(input); + } else { + LOG(ERROR, DEBUGGER) << "Unknown id length"; + } +} + +void HprofWriterBuffer::WriteId(uint64_t input) +{ + if (id_length_ == ID_LENGTH_4B) { + WriteU4(input); + } else if (id_length_ == ID_LENGTH_8B) { + WriteU8(input); + } else { + LOG(ERROR, DEBUGGER) << "Unknown id length"; + } +} + +void HprofWriterBuffer::WriteId(void *input) +{ + auto id = ToUintPtr(input); + if (id_length_ == ID_LENGTH_4B) { + WriteU4(id); + } else if (id_length_ == ID_LENGTH_8B) { + WriteU8(id); + } else { + LOG(ERROR, DEBUGGER) << "Unknown id length"; + } +} + +void HprofWriterBuffer::WriteArrayU1(const PandaString &input, size_t size) +{ + for (size_t i = 0; i < size; i++) { + buffer_.push_back(input[i]); + } + length_ += size; +} + +void HprofWriterBuffer::WriteArrayU1(const Span &sz) +{ + for (uint8_t ch : sz) { + buffer_.push_back(ch); + } + length_ += sz.size(); +} + +size_t HprofWriterBuffer::GetLength() +{ + return length_; +} + +size_t HprofWriterBuffer::GetPos() +{ + return buffer_.size(); +} + +void HprofWriterBuffer::ChangeU4(size_t pos, uint32_t input) +{ + for (size_t i = 0; i < U4_LENGTH; i++) { + auto mov = BITS_IN_BYTE * (U4_LENGTH - i - 1); + buffer_[pos + i] = (static_cast(input >> mov) & MASK_INT8); + } +} + +void HprofWriterBuffer::AddTime() +{ + static constexpr size_t NANOS_IN_MICROS = 1000; + uint64_t now_nanos = time::GetCurrentTimeInNanos(); + uint64_t microseconds = (now_nanos - start_time_nanos_) / NANOS_IN_MICROS; + WriteU4(microseconds); +} + +void HprofWriterBuffer::AddStartTime() +{ + auto start_time = time::GetCurrentTimeInMillis(); + WriteU4(static_cast(start_time >> (BITS_IN_BYTE * U4_LENGTH)) & MASK_INT32); + WriteU4(start_time & MASK_INT32); + start_time_nanos_ = time::GetCurrentTimeInNanos(); +} + +void HprofWriterBuffer::AddUnknownStackTraceSerialNumber() +{ + // TODO(sshadrin): add corresponding serial number + WriteU4(0U); +} + +void HprofWriterBuffer::AddUnknownThreadSerialNumber() +{ + // TODO(sshadrin): add corresponding serial number + WriteU4(0U); +} + +void HprofWriterBuffer::AddUnknownClassSerialNumber() +{ + // TODO(sshadrin): add corresponding serial number + WriteU4(0U); +} + +void HprofWriterBuffer::AddUnknownFrameNumberInStackTrace() +{ + // TODO(sshadrin): add corresponding frame number + WriteU4(-1); +} + +size_t HprofWriterBuffer::GetIdLength() +{ + return id_length_; +} + +uint8_t *HprofWriterBuffer::GetData() +{ + return buffer_.data(); +} + +void HprofWriterBuffer::Clear() +{ + buffer_.clear(); + length_ = 0; +} + +} // namespace panda::mem diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h new file mode 100644 index 000000000..249e902e1 --- /dev/null +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_BUFFER_H +#define PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_BUFFER_H + +#include "runtime/include/mem/panda_containers.h" +#include "runtime/include/mem/panda_string.h" + +namespace panda::mem { + +enum class HprofTag : uint8_t { + STRING_IN_UTF8 = 0x01, + LOAD_CLASS = 0x02, + UNLOAD_CLASS = 0x03, + STACK_FRAME = 0x04, + STACK_TRACE = 0x05, + ALLOC_SITES = 0x06, + HEAP_SUMMARY = 0x07, + START_THREAD = 0x0A, + END_THREAD = 0x0B, + HEAP_DUMP = 0x0C, + HEAP_DUMP_SEGMENT = 0x1C, + HEAP_DUMP_END = 0x2C, + CPU_SAMPLE = 0x0D, + CONTROL_SETTINGS = 0x0E +}; + +enum class HprofSubTag : uint8_t { + ROOT_UNKOWN = 0xFF, + ROOT_NAPI_GLOBAL = 0x01, + ROOT_NAPI_LOCAL = 0x02, + ROOT_FRAME = 0x03, + ROOT_NATIVE_STACK = 0x04, + ROOT_STICKY_CLASS = 0x05, + ROOT_THREAD_BLOCK = 0x06, + ROOT_MONITOR_USED = 0x07, + ROOT_THREAD_OBJECT = 0x08, + CLASS_DUMP = 0x20, + INSTANCE_DUMP = 0x21, + OBJECT_ARRAY_DUMP = 0x22, + PRIMITTVE_ARRAY_DUMP = 0x23 +}; + +enum class HprofBasicType : uint8_t { + OBJECT = 2, + BOOLEAN = 4, + CHAR = 5, + FLOAT = 6, + DOUBLE = 7, + BYTE = 8, + SHORT = 9, + INT = 10, + LONG = 11 +}; + +static constexpr size_t DEFAULT_ID_LENGTH = 4; +static constexpr size_t BITS_IN_BYTE = 8; +static constexpr size_t U1_LENGTH = 1; +static constexpr size_t U2_LENGTH = 2; +static constexpr size_t U4_LENGTH = 4; +static constexpr size_t U8_LENGTH = 8; +static constexpr size_t ID_LENGTH_4B = 4; +static constexpr size_t ID_LENGTH_8B = 8; + +class StringStorage { +public: + StringStorage() = default; + + size_t AddString(const PandaString &str); + + size_t FindString(const PandaString &str); + + void Clear(); + + ~StringStorage() = default; + + DEFAULT_COPY_SEMANTIC(StringStorage); + DEFAULT_MOVE_SEMANTIC(StringStorage); + +private: + size_t size_ = 0; + PandaVector list_; +}; + +class HprofWriterBuffer { +public: + HprofWriterBuffer() = default; + + void WriteU1(uint8_t input); + + void WriteU1(HprofTag tag); + + void WriteU1(HprofSubTag sub_tag); + + void WriteU1(HprofBasicType base_type); + + void WriteU2(uint16_t input); + + void WriteU4(uint32_t input); + + void WriteU8(uint64_t input); + + void WriteId(uint32_t input); + + void WriteId(uint64_t input); + + void WriteId(void *input); + + void WriteArrayU1(const PandaString &input, size_t size); + + void WriteArrayU1(const Span &sz); + + size_t GetPos(); + + void ChangeU4(size_t pos, uint32_t input); + + size_t GetLength(); + + void AddTime(); + + void AddStartTime(); + + void AddUnknownStackTraceSerialNumber(); + + void AddUnknownThreadSerialNumber(); + + void AddUnknownClassSerialNumber(); + + void AddUnknownFrameNumberInStackTrace(); + + size_t GetIdLength(); + + uint8_t *GetData(); + + void Clear(); + + ~HprofWriterBuffer() = default; + + DEFAULT_COPY_SEMANTIC(HprofWriterBuffer); + DEFAULT_MOVE_SEMANTIC(HprofWriterBuffer); + +private: + size_t id_length_ = DEFAULT_ID_LENGTH; + uint64_t start_time_nanos_ = 0; + size_t length_ = 0; + PandaVector buffer_; +}; + +} // namespace panda::mem + +#endif // PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_HPROF_WRITER_BUFFER_H diff --git a/runtime/options.yaml b/runtime/options.yaml index 34c531c3e..bb0fb8225 100644 --- a/runtime/options.yaml +++ b/runtime/options.yaml @@ -347,6 +347,21 @@ options: default: false description: Dump heap before and after GC +- name: heap-dump-on-out-of-memory + type: bool + default: false + description: Dump heap in hprof format on OOM error + +- name: heap-dump-on-sigint + type: bool + default: false + description: Dump heap in hprof format on SIGINT + +- name: heap-dump-output-file + type: std::string + default: heap_dump.hprof + description: Path to heap dump hprof output file + - name: gc-workers-count type: uint32_t default: 2 @@ -847,4 +862,4 @@ options: - name: coroutines-stack-mem-limit type: uint64_t default: 134217728 - description: defines the total amount of memory that can be used for stackful coroutine stacks allocation (in bytes) \ No newline at end of file + description: defines the total amount of memory that can be used for stackful coroutine stacks allocation (in bytes) diff --git a/runtime/runtime.cpp b/runtime/runtime.cpp index 206abd09e..9522244f3 100644 --- a/runtime/runtime.cpp +++ b/runtime/runtime.cpp @@ -884,6 +884,13 @@ bool Runtime::Initialize() StartMemAllocDumper(ConvertToString(options_.GetMemAllocDumpFile())); } +#ifndef PANDA_TARGET_WINDOWS + if (options_.WasSetHeapDumpOnSigint()) { + auto *handler = signal_manager_->GetAllocator()->New(); + signal_manager_->AddHandler(handler, true); + } +#endif // PANDA_TARGET_WINDOWS + #ifdef PANDA_TARGET_MOBILE mem::GcHung::InitPreFork(true); #else diff --git a/runtime/signal_handler.cpp b/runtime/signal_handler.cpp index 4301f9d56..b8be7a9be 100644 --- a/runtime/signal_handler.cpp +++ b/runtime/signal_handler.cpp @@ -143,6 +143,7 @@ void SignalManager::InitSignals() sigdelset(&mask, SIGFPE); sigdelset(&mask, SIGILL); sigdelset(&mask, SIGSEGV); + sigdelset(&mask, SIGINT); ClearSignalHooksHandlersArray(); @@ -153,6 +154,7 @@ void SignalManager::InitSignals() SA_SIGINFO, }; AddSpecialSignalHandlerFn(SIGSEGV, &sigchain_action); + AddSpecialSignalHandlerFn(SIGINT, &sigchain_action); is_init_ = true; @@ -494,4 +496,16 @@ bool StackOverflowHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, return true; } + +bool CtrlBreakHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, [[maybe_unused]] void *context) +{ + if (sig != SIGINT) { + return false; + } + + auto *thread = ManagedThread::GetCurrent(); + bool is_dump_heap_success = thread->GetVM()->GetGC()->DumpHeap(); + + _exit(is_dump_heap_success ? 0 : 1); +} } // namespace panda diff --git a/runtime/signal_handler.h b/runtime/signal_handler.h index b0f329ce3..57b00b1ec 100644 --- a/runtime/signal_handler.h +++ b/runtime/signal_handler.h @@ -177,6 +177,18 @@ public: bool Action(int sig, siginfo_t *siginfo, void *context) override; }; +class CtrlBreakHandler final : public SignalHandler { +public: + CtrlBreakHandler() = default; + + ~CtrlBreakHandler() override = default; + + NO_COPY_SEMANTIC(CtrlBreakHandler); + + NO_MOVE_SEMANTIC(CtrlBreakHandler); + + bool Action(int sig, siginfo_t *siginfo, void *context) override; +}; } // namespace panda #endif // PANDA_RUNTIME_SIGNAL_HANDLER_H diff --git a/runtime/tests/heap_dumper_test.cpp b/runtime/tests/heap_dumper_test.cpp new file mode 100644 index 000000000..6d708dfdd --- /dev/null +++ b/runtime/tests/heap_dumper_test.cpp @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * 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 + +#include "assembler/assembly-parser.h" +#include "runtime/include/mem/panda_containers.h" +#include "runtime/include/mem/panda_string.h" +#include "libpandabase/macros.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer_buffer.h" +#include "runtime/include/method.h" +#include "runtime/include/runtime.h" +#include "generated/base_options.h" +#include "runtime/mem/heapdumper/hprof/hprof_verifier.h" +#include "runtime/mem/heapdumper/hprof/hprof_writer.h" + +namespace panda::test { + +namespace { +inline std::string Separator() +{ +#ifdef _WIN32 + return "\\"; +#else + return "/"; +#endif +} +} // namespace + +// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions, hicpp-special-member-functions) +class HeapDumperTest : public testing::Test { +public: + HeapDumperTest() + { + base_options::Options base_options(""); + base_options.SetLogComponents({"runtime"}); + base_options.SetLogLevel("info"); + Logger::Initialize(base_options); + + options_.SetHeapSizeLimit(DEFAULT_TEST_HEAP_SIZE); + options_.SetInternalMemorySizeLimit(DEFAULT_TEST_HEAP_SIZE); + options_.SetShouldInitializeIntrinsics(false); + options_.SetBootClassSpaces({"core"}); + options_.SetCompilerEnableJit(false); + options_.SetRuntimeType("core"); + options_.SetHeapDumpOnOutOfMemory(true); + options_.SetGcType("gen-gc"); + options_.SetHeapDumpOutputFile(path_to_heap_dump_); + + auto exec_path = panda::os::file::File::GetExecutablePath(); + auto panda_std_lib = + exec_path.Value() + Separator() + ".." + Separator() + "pandastdlib" + Separator() + "pandastdlib.bin"; + options_.SetBootPandaFiles({panda_std_lib}); + + Runtime::Create(options_); + + thread_ = panda::MTManagedThread::GetCurrent(); + thread_->ManagedCodeBegin(); + } + + ~HeapDumperTest() // NOLINT(hicpp-use-override, modernize-use-override) + { + thread_->ManagedCodeEnd(); + Runtime::Destroy(); + } + +protected: + static constexpr size_t DEFAULT_TEST_HEAP_SIZE = 64_MB; + panda::MTManagedThread *thread_; // NOLINT(misc-non-private-member-variables-in-classes) + RuntimeOptions options_; // NOLINT(misc-non-private-member-variables-in-classes) + std::string path_to_heap_dump_ = "heap_dump.hprof"; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +namespace { +PandaString DecodeValueType(uint8_t value_type) +{ + switch (static_cast(value_type)) { + case mem::HprofBasicType::BOOLEAN: { + return "u1"; + } + case mem::HprofBasicType::BYTE: { + return "i8"; + } + case mem::HprofBasicType::CHAR: { + return "u8"; + } + case mem::HprofBasicType::SHORT: { + return "i16"; + } + case mem::HprofBasicType::FLOAT: { + return "f32"; + } + case mem::HprofBasicType::INT: { + return "i32"; + } + case mem::HprofBasicType::DOUBLE: { + return "f64"; + } + case mem::HprofBasicType::LONG: { + return "i64"; + } + case mem::HprofBasicType::OBJECT: { + return "any"; + } + } + UNREACHABLE(); +} +} // namespace + +TEST_F(HeapDumperTest, SimpleTest) +{ + auto source = R"( + .record panda.String + + .record Foo { + i32 member1 + f32 member2 + panda.String f_str + } + + .record Bar { + Foo foo + f64 member1 + f64 member2 + } + + .record Test {} + .record A {} + .record B {} + + .function i32 Test.main() { + try_begin: + newobj v2, A + newobj v3, B + newobj v4, Foo + newobj v5, Bar + inf_loop: + # cause OOM to dump heap + movi v1, 1999999999 + newarr v0, v1, i64[] + jmp inf_loop + try_end: + catch_begin: + movi v0, 0x0 + lda v0 + return + no_exceptions: + movi v0, 0xffffffffffffffff + lda v0 + return + .catchall try_begin, try_end, catch_begin + } + )"; + + pandasm::Parser parser; + auto res = parser.Parse(source); + ASSERT_TRUE(res) << res.Error().message; + + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + ASSERT_NE(pf, nullptr); + + ClassLinker *class_linker = Runtime::GetCurrent()->GetClassLinker(); + class_linker->AddPandaFile(std::move(pf)); + PandaString descriptor; + Class *klass = class_linker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY) + ->GetClass(ClassHelper::GetDescriptor(utf::CStringAsMutf8("Test"), &descriptor)); + ASSERT_NE(klass, nullptr); + + Method *method = klass->GetDirectMethod(utf::CStringAsMutf8("main")); + ASSERT_NE(method, nullptr); + + std::vector args; + Value result = method->Invoke(ManagedThread::GetCurrent(), args.data()); + ASSERT_EQ(result.GetAs(), 0) << "Unexpected error: no OOM exception happend"; + + auto file = os::file::Open(path_to_heap_dump_, os::file::Mode::READONLY); + ASSERT_TRUE(file.IsValid()) << "Can't open heap_dump" << path_to_heap_dump_; + + // Field: id, value_type, value, is_static_field + using Field = std::tuple; + // ClassDump: class_id, fields + using ClassDump = std::tuple>; + // Utf8String: string, class_name_id + using Utf8String = std::tuple; + // LoadClass: class_name_id, class_id + using LoadClass = std::tuple; + PandaVector class_dumps; + PandaVector utf8_strings; + PandaVector load_classes; + // class_id, counter + PandaUnorderedMap class_id_to_instance_count; + mem::HprofVerifier verifier; + + // skip header + std::array buf = {}; + file.ReadAll(buf.data(), mem::HprofWriter::MAGIC_SIZE); + size_t id_length = verifier.GetU4FromFile(file); + auto read_id = [&]() { + uint64_t id = 0; + file.ReadAll(&id, id_length); + return id; + }; + verifier.GetU4FromFile(file); // high word of time + verifier.GetU4FromFile(file); // low word of time + + mem::HprofTag tag; + while (file.ReadAll(&tag, 1)) { + verifier.GetU4FromFile(file); // time + size_t length = verifier.GetU4FromFile(file); + if (tag == mem::HprofTag::STRING_IN_UTF8) { + uint64_t id = read_id(); + PandaString sz(length - id_length, '.'); + file.ReadAll(sz.data(), length - id_length); + utf8_strings.emplace_back(sz, id); + } else if (tag == mem::HprofTag::HEAP_DUMP_SEGMENT) { + auto sub_tag = static_cast(verifier.GetU1FromFile(file)); + if (sub_tag == mem::HprofSubTag::CLASS_DUMP) { + uint64_t id = read_id(); + verifier.GetU4FromFile(file); // stack trace serial number + read_id(); // super class object + read_id(); // class loader object + read_id(); // signers object + read_id(); // protection domain object + read_id(); // reserved + read_id(); // reserved + verifier.GetU4FromFile(file); // instance size + verifier.GetU2FromFile(file); // size of constant pool + uint16_t num_static_fields = verifier.GetU2FromFile(file); + PandaVector fields; + for (uint16_t i = 0; i < num_static_fields; ++i) { + uint64_t field_id = read_id(); // static field name + uint8_t value_type = verifier.GetU1FromFile(file); + size_t value_length = verifier.GetValueLength(value_type); + uint64_t value; + file.ReadAll(&value, value_length); + fields.emplace_back(field_id, value_type, value, true); + } + uint16_t num_inst_fields = verifier.GetU2FromFile(file); + for (uint16_t i = 0; i < num_inst_fields; ++i) { + uint64_t field_id = read_id(); // instance field name + uint8_t value_type = verifier.GetU1FromFile(file); + fields.emplace_back(field_id, value_type, 0, false); + } + + class_dumps.push_back({id, fields}); + } else if (sub_tag == mem::HprofSubTag::INSTANCE_DUMP) { + read_id(); // object id + verifier.GetU4FromFile(file); + uint64_t class_id = read_id(); + if (auto [iter, is_ins] = class_id_to_instance_count.try_emplace(class_id, 1); !is_ins) { + iter->second += 1; + } + verifier.SkipFileContent(file, length - 2 * id_length - 5); + } else { + verifier.SkipFileContent(file, length - 1); + } + } else if (tag == mem::HprofTag::LOAD_CLASS) { + verifier.GetU4FromFile(file); // class serial number + uint64_t class_id = read_id(); + verifier.GetU4FromFile(file); // stack trace serial number + uint64_t class_name_id = read_id(); + load_classes.emplace_back(class_name_id, class_id); + } else { + // skip + verifier.SkipFileContent(file, length); + } + } + + using ExpectedFields = std::tuple; + using ExpectedClass = std::tuple, int>; + PandaVector expected_classes = { + {"Foo", {{"member1", "i32"}, {"member2", "f32"}, {"f_str", "any"}}, 1}, + {"Bar", {{"foo", "any"}, {"member1", "f64"}, {"member2", "f64"}}, 1}, + {"A", {}, 1}, + {"Test", {}, 0}, + {"B", {}, 1}}; + + for (auto const &[class_name, fields, count_of_instances] : expected_classes) { + auto utf8_string_row = + std::find_if(utf8_strings.begin(), utf8_strings.end(), + [class_name = class_name](const Utf8String &x) { return std::get<0>(x) == class_name; }); + ASSERT_NE(utf8_string_row, utf8_strings.end()); + + auto load_class_row = + std::find_if(load_classes.begin(), load_classes.end(), [&utf8_string_row](const LoadClass &x) { + return std::get<0>(x) == std::get<1>(*utf8_string_row); + }); + ASSERT_NE(load_class_row, load_classes.end()); + + auto class_row = std::find_if(class_dumps.begin(), class_dumps.end(), [&load_class_row](const ClassDump &x) { + return std::get<0>(x) == std::get<1>(*load_class_row); + }); + + if (class_row == class_dumps.end()) { + continue; + } + + auto class_fields = std::get<1>(*class_row); + + for (auto const &[name, value_type] : fields) { + auto field_string_row = + std::find_if(utf8_strings.begin(), utf8_strings.end(), + [name = name](const Utf8String &x) { return std::get<0>(x) == name; }); + ASSERT_NE(field_string_row, utf8_strings.end()); + + auto class_field_row = + std::find_if(class_fields.begin(), class_fields.end(), [&field_string_row](const Field &x) { + return std::get<0>(x) == std::get<1>(*field_string_row); + }); + ASSERT_NE(class_field_row, class_fields.end()); + + ASSERT_EQ(DecodeValueType(std::get<1>(*class_field_row)), value_type); + } + + ASSERT_EQ(class_id_to_instance_count[std::get<1>(*load_class_row)], count_of_instances); + } + file.Close(); +} + +} // namespace panda::test diff --git a/runtime/tests/interpreter/test_runtime_interface.h b/runtime/tests/interpreter/test_runtime_interface.h index 573998f06..48eb03ca8 100644 --- a/runtime/tests/interpreter/test_runtime_interface.h +++ b/runtime/tests/interpreter/test_runtime_interface.h @@ -103,6 +103,10 @@ private: void UpdateClassLinkerContextRoots() override {} void UpdateThreadLocals() override {} void ClearLocalInternalAllocatorPools() override {} + bool DumpHeap() override + { + return true; + }; }; template -- Gitee