From 08223082229749d3bec9186cfe30f402999f9aaf Mon Sep 17 00:00:00 2001 From: mmorozov Date: Fri, 23 Jun 2023 14:49:26 +0300 Subject: [PATCH 1/3] 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 095da044f..6957cb62b 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -177,6 +177,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 1e4f0ac67..c8c32e677 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 @@ -972,6 +976,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 1dc48bd2d..c9bda34d8 100644 --- a/runtime/mem/gc/gc.h +++ b/runtime/mem/gc/gc.h @@ -410,6 +410,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 3adb4bec4..4f0f60d6d 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 @@ -854,4 +869,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 9359a3727..8d46bf037 100644 --- a/runtime/runtime.cpp +++ b/runtime/runtime.cpp @@ -904,6 +904,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 From 9b676baa0dff37e2ed937c62028d6637cbcbeb54 Mon Sep 17 00:00:00 2001 From: kropacheva Date: Thu, 6 Jul 2023 19:12:27 +0300 Subject: [PATCH 2/3] Fix heap dumper Change-Id: I17f09ee4b3fd923eb86e0b1024825bfbe478b09e Signed-off-by: kropacheva --- runtime/CMakeLists.txt | 9 +- runtime/mem/gc/lang/gc_lang.cpp | 2 +- runtime/mem/heapdumper/heap_dumper.cpp | 12 +-- runtime/mem/heapdumper/heap_dumper.h | 2 +- .../mem/heapdumper/hprof/hprof_verifier.cpp | 33 ++++--- runtime/mem/heapdumper/hprof/hprof_verifier.h | 22 +++-- runtime/mem/heapdumper/hprof/hprof_writer.cpp | 9 +- runtime/mem/heapdumper/hprof/hprof_writer.h | 3 +- .../heapdumper/hprof/hprof_writer_buffer.cpp | 10 +-- .../heapdumper/hprof/hprof_writer_buffer.h | 2 +- runtime/tests/heap_dumper_test.cpp | 90 ++++++++++++------- 11 files changed, 115 insertions(+), 79 deletions(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index c8c32e677..ac2f91af9 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -976,11 +976,18 @@ 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 ) +# This test expects OOM +if (NOT PANDA_ENABLE_ADDRESS_SANITIZER) +add_gtests( + arkruntime_heap_dumper_test + tests/heap_dumper_test.cpp +) +endif() + include(intrinsics.cmake) include(icu.cmake) diff --git a/runtime/mem/gc/lang/gc_lang.cpp b/runtime/mem/gc/lang/gc_lang.cpp index a7a708ed1..e82b1c5a9 100644 --- a/runtime/mem/gc/lang/gc_lang.cpp +++ b/runtime/mem/gc/lang/gc_lang.cpp @@ -67,7 +67,7 @@ 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)) { + if (!dumper.HprofDumpHeap(GetPandaVm(), file_name)) { return false; } LOG(INFO, RUNTIME) << "Saved."; diff --git a/runtime/mem/heapdumper/heap_dumper.cpp b/runtime/mem/heapdumper/heap_dumper.cpp index 66f9d8b03..0c6895304 100644 --- a/runtime/mem/heapdumper/heap_dumper.cpp +++ b/runtime/mem/heapdumper/heap_dumper.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -26,14 +26,14 @@ namespace panda::mem { template -bool HeapDumper::HprofDumpHeap(PandaVM *core_vm, const PandaString &file_name) +bool HeapDumper::HprofDumpHeap(PandaVM *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); + ASSERT(vm != nullptr); + HprofWriter writer {}; ScopedChangeThreadStatus sts(thread, ThreadStatus::RUNNING); ScopedSuspendAllThreadsRunning ssat(vm->GetRendezvous()); @@ -73,7 +73,7 @@ bool HeapDumper::HprofDumpHeap(PandaVM *core_vm, const PandaStri vm->GetHeapManager()->GetObjectAllocator().AsObjectAllocator()->IterateOverObjects(dump_all); // Dump roots - // TODO(sshadrin): define for dynamic language and PandaAssemblyLanguageConfig + // TODO(kropacheva): define for dynamic language and PandaAssemblyLanguageConfig, #8621 // NOLINTNEXTLINE(readability-braces-around-statements, readability-misleading-indentation) if constexpr (IS_STATIC && !std::is_same_v) { RootManager root_manager; @@ -82,7 +82,7 @@ bool HeapDumper::HprofDumpHeap(PandaVM *core_vm, const PandaStri root_manager.VisitNonHeapRoots(root_visitor); } - // TODO(sshadrin): add StringTable->VisitStrings for adding string objects without live refs + // TODO(kropacheva): add StringTable->VisitStrings for adding string objects without live refs, #8621 writer.DumpHeapDumpEnd(); writer.PrintToFile(); diff --git a/runtime/mem/heapdumper/heap_dumper.h b/runtime/mem/heapdumper/heap_dumper.h index ce2308d08..f2714943b 100644 --- a/runtime/mem/heapdumper/heap_dumper.h +++ b/runtime/mem/heapdumper/heap_dumper.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 diff --git a/runtime/mem/heapdumper/hprof/hprof_verifier.cpp b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp index 173bcae1b..66f1301e6 100644 --- a/runtime/mem/heapdumper/hprof/hprof_verifier.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -18,33 +18,31 @@ #include "runtime/mem/heapdumper/hprof/hprof_writer_buffer.h" namespace panda::mem { -uint32_t HprofVerifier::GetU4FromFile(os::file::File file) +template +uint32_t GetFromFile(os::file::File file) { uint32_t res = 0; uint8_t num = 0; - for (size_t i = 0; i < U4_LENGTH; i++) { + for (size_t i = 0; i < LENGTH; i++) { file.ReadAll(&num, U1_LENGTH); - res += num << (BITS_IN_BYTE * (U4_LENGTH - i - 1)); + res += num << (BITS_IN_BYTE * (LENGTH - i - 1)); } return res; } +uint32_t HprofVerifier::GetU4FromFile(os::file::File file) +{ + return GetFromFile(file); +} + 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; + return GetFromFile(file); } uint8_t HprofVerifier::GetU1FromFile(os::file::File file) { - uint8_t res; - file.ReadAll(&res, U1_LENGTH); - return res; + return GetFromFile(file); } void HprofVerifier::SkipFileContent(os::file::File file, size_t size) @@ -81,6 +79,7 @@ size_t HprofVerifier::GetValueLength(uint8_t value_type) break; } default: { + LOG(ERROR, RUNTIME) << "Unknown value type in hprof file"; value_length = 0; break; } @@ -136,6 +135,7 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File file) size_t file_length = 0; std::array magic {}; file.ReadAll(magic.data(), HprofWriter::MAGIC_SIZE); + // CHECKER_IGNORE_NEXTLINE(AF0001) if (std::strcmp(&*magic.data(), "JAVA PROFILE 1.0.2") != 0) { LOG(ERROR, RUNTIME) << "Unknown magic in hprof"; return false; @@ -186,7 +186,7 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File file) break; } case HprofSubTag::ROOT_NAPI_GLOBAL: { - // size of object id and JNI global ref id + // size of object id and NAPI global ref id target_length += id_length * 2; SkipFileContent(file, id_length * 2); break; @@ -231,7 +231,6 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File 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); @@ -246,7 +245,6 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File 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); @@ -293,7 +291,6 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File 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); diff --git a/runtime/mem/heapdumper/hprof/hprof_verifier.h b/runtime/mem/heapdumper/hprof/hprof_verifier.h index 2db78f393..41f301ba7 100644 --- a/runtime/mem/heapdumper/hprof/hprof_verifier.h +++ b/runtime/mem/heapdumper/hprof/hprof_verifier.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -19,6 +19,10 @@ #include "libpandabase/macros.h" #include "runtime/include/mem/panda_string.h" +namespace panda::test { +class HeapDumperTest; +} // namespace panda::test + namespace panda::mem { class HprofVerifier { @@ -33,22 +37,24 @@ public: static bool VerifyFormat(const PandaString &file_name); +private: + static bool VerifyFormatByFile(os::file::File file); + + static bool IsUnknowTag(uint8_t tag_num); + + static bool IsUnknowSubTag(uint8_t sub_tag_num); + 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 size_t GetValueLength(uint8_t value_type); - static bool IsUnknowSubTag(uint8_t sub_tag_num); + friend class panda::test::HeapDumperTest; }; } // namespace panda::mem diff --git a/runtime/mem/heapdumper/hprof/hprof_writer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer.cpp index e654615a2..06117b5c2 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_writer.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -54,6 +54,7 @@ void HprofWriter::PrintToFile() void HprofWriter::DumpHeader() { + // CHECKER_IGNORE_NEXTLINE(AF0001) writer_buffer_.WriteArrayU1("JAVA PROFILE 1.0.2", MAGIC_SIZE); writer_buffer_.WriteU4(writer_buffer_.GetIdLength()); writer_buffer_.AddStartTime(); @@ -88,7 +89,7 @@ size_t HprofWriter::FindOrAddString(const PandaString &cls_name) void HprofWriter::DumpClassString(Class *cls) { if (!cls->IsLoaded()) { - // TODO(sshadrin): dump unloadclass after class serial number support + // TODO(kropacheva): dump unloadclass after class serial number support, #8621 return; } @@ -244,7 +245,7 @@ void HprofWriter::DumpField(TaggedValue *tagged_value) writer_buffer_.WriteU1(0U); } } else { - // TODO(sshadrin): make dump special values proper way + // TODO(kropacheva): make dump special values proper way, #8621 // "Hole", "Null", "Undefined", "Exception" writer_buffer_.WriteU1(HprofBasicType::OBJECT); writer_buffer_.WriteId(nullptr); @@ -800,7 +801,7 @@ void HprofWriter::DumpRoot(const GCRoot &gc_root) writer_buffer_.WriteId(object_header); switch (root_type) { case HprofSubTag::ROOT_NAPI_GLOBAL: { - // TODO(sshadrin): add global reference + // TODO(kropacheva): add global reference, #8621 writer_buffer_.WriteId(nullptr); break; } diff --git a/runtime/mem/heapdumper/hprof/hprof_writer.h b/runtime/mem/heapdumper/hprof/hprof_writer.h index 4dd3f7a30..91b14ead3 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer.h +++ b/runtime/mem/heapdumper/hprof/hprof_writer.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -22,6 +22,7 @@ namespace panda::mem { +// This class creates .hprof file and write dump info corresponding to the format class HprofWriter { public: HprofWriter() = default; diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp index 2176257fd..d61f95988 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -177,25 +177,25 @@ void HprofWriterBuffer::AddStartTime() void HprofWriterBuffer::AddUnknownStackTraceSerialNumber() { - // TODO(sshadrin): add corresponding serial number + // TODO(kropacheva): add corresponding serial number, #8621 WriteU4(0U); } void HprofWriterBuffer::AddUnknownThreadSerialNumber() { - // TODO(sshadrin): add corresponding serial number + // TODO(kropacheva): add corresponding serial number, #8621 WriteU4(0U); } void HprofWriterBuffer::AddUnknownClassSerialNumber() { - // TODO(sshadrin): add corresponding serial number + // TODO(kropacheva): add corresponding serial number, #8621 WriteU4(0U); } void HprofWriterBuffer::AddUnknownFrameNumberInStackTrace() { - // TODO(sshadrin): add corresponding frame number + // TODO(kropacheva): add corresponding frame number, #8621 WriteU4(-1); } diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h index 249e902e1..111eac196 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 diff --git a/runtime/tests/heap_dumper_test.cpp b/runtime/tests/heap_dumper_test.cpp index 6d708dfdd..6b4f509d0 100644 --- a/runtime/tests/heap_dumper_test.cpp +++ b/runtime/tests/heap_dumper_test.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2023 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 @@ -76,10 +76,35 @@ public: Runtime::Destroy(); } + uint32_t GetU4FromFile(os::file::File file) + { + return mem::HprofVerifier::GetU4FromFile(file); + } + + uint16_t GetU2FromFile(os::file::File file) + { + return mem::HprofVerifier::GetU2FromFile(file); + } + + uint8_t GetU1FromFile(os::file::File file) + { + return mem::HprofVerifier::GetU1FromFile(file); + } + + void SkipFileContent(os::file::File file, size_t size) + { + mem::HprofVerifier::SkipFileContent(file, size); + } + + size_t GetValueLength(uint8_t value_type) + { + return mem::HprofVerifier::GetValueLength(value_type); + } + 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) + 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) }; @@ -201,80 +226,80 @@ TEST_F(HeapDumperTest, SimpleTest) 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 id_length = 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 + + GetU4FromFile(file); // high word of time + GetU4FromFile(file); // low word of time mem::HprofTag tag; while (file.ReadAll(&tag, 1)) { - verifier.GetU4FromFile(file); // time - size_t length = verifier.GetU4FromFile(file); + GetU4FromFile(file); // time + size_t length = 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)); + auto sub_tag = static_cast(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); + 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 + GetU4FromFile(file); // instance size + GetU2FromFile(file); // size of constant pool + uint16_t num_static_fields = 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); + uint8_t value_type = GetU1FromFile(file); + size_t value_length = 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); + uint16_t num_inst_fields = 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); + uint8_t value_type = 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); + 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); + SkipFileContent(file, length - 2 * id_length - 5); } else { - verifier.SkipFileContent(file, length - 1); + SkipFileContent(file, length - 1); } } else if (tag == mem::HprofTag::LOAD_CLASS) { - verifier.GetU4FromFile(file); // class serial number + GetU4FromFile(file); // class serial number uint64_t class_id = read_id(); - verifier.GetU4FromFile(file); // stack trace serial number + 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); + SkipFileContent(file, length); } } @@ -310,9 +335,8 @@ TEST_F(HeapDumperTest, SimpleTest) 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; }); + 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 = -- Gitee From aae5232333573f78872ee0f78140e8f1f52dbafa Mon Sep 17 00:00:00 2001 From: kropacheva Date: Tue, 8 Aug 2023 13:28:25 +0300 Subject: [PATCH 3/3] Move heap dump to separate thread Change-Id: If757eed41fcf649035450e2a886b2768bcd4c4b7 Signed-off-by: kropacheva --- runtime/CMakeLists.txt | 10 +-- .../mem/heapdumper/hprof/hprof_verifier.cpp | 2 +- runtime/mem/heapdumper/hprof/hprof_writer.cpp | 21 +---- .../heapdumper/hprof/hprof_writer_buffer.cpp | 56 +++++------- .../heapdumper/hprof/hprof_writer_buffer.h | 7 ++ runtime/runtime.cpp | 13 ++- runtime/signal_catcher.cpp | 88 +++++++++++++++++++ runtime/signal_catcher.h | 50 +++++++++++ runtime/signal_handler.cpp | 4 +- 9 files changed, 183 insertions(+), 68 deletions(-) create mode 100644 runtime/signal_catcher.cpp create mode 100644 runtime/signal_catcher.h diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index ac2f91af9..d6e7acaaa 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -152,6 +152,7 @@ set(SOURCES threaded_coroutine_manager.cpp stackful_coroutine_manager.cpp fibers/fiber_context.cpp + signal_catcher.cpp ) if(PANDA_TARGET_ARM32_ABI_SOFT OR PANDA_TARGET_ARM32_ABI_SOFTFP) @@ -976,18 +977,11 @@ 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 ) -# This test expects OOM -if (NOT PANDA_ENABLE_ADDRESS_SANITIZER) -add_gtests( - arkruntime_heap_dumper_test - tests/heap_dumper_test.cpp -) -endif() - include(intrinsics.cmake) include(icu.cmake) diff --git a/runtime/mem/heapdumper/hprof/hprof_verifier.cpp b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp index 66f1301e6..cc68c9601 100644 --- a/runtime/mem/heapdumper/hprof/hprof_verifier.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_verifier.cpp @@ -136,7 +136,7 @@ bool HprofVerifier::VerifyFormatByFile(os::file::File file) std::array magic {}; file.ReadAll(magic.data(), HprofWriter::MAGIC_SIZE); // CHECKER_IGNORE_NEXTLINE(AF0001) - if (std::strcmp(&*magic.data(), "JAVA PROFILE 1.0.2") != 0) { + if (std::strcmp(magic.data(), "JAVA PROFILE 1.0.2") != 0) { LOG(ERROR, RUNTIME) << "Unknown magic in hprof"; return false; } diff --git a/runtime/mem/heapdumper/hprof/hprof_writer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer.cpp index 06117b5c2..e904c4fd6 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_writer.cpp @@ -277,12 +277,7 @@ void HprofWriter::DumpStaticFields(Class *cls) 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::I8: case panda_file::Type::TypeId::U8: { auto val = cls->GetFieldPrimitive(offset); writer_buffer_.WriteU1(HprofBasicType::BYTE); @@ -301,12 +296,7 @@ void HprofWriter::DumpStaticFields(Class *cls) 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::I32: case panda_file::Type::TypeId::U32: { auto val = cls->GetFieldPrimitive(offset); writer_buffer_.WriteU1(HprofBasicType::INT); @@ -319,18 +309,13 @@ void HprofWriter::DumpStaticFields(Class *cls) writer_buffer_.WriteU4(val); break; } + case panda_file::Type::TypeId::I64: 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); diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp index d61f95988..47034c43b 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.cpp @@ -51,47 +51,46 @@ void HprofWriterBuffer::WriteU1(uint8_t input) void HprofWriterBuffer::WriteU1(HprofTag tag) { - WriteU1(static_cast(tag)); + WriteU1(helpers::ToUnderlying(tag)); } void HprofWriterBuffer::WriteU1(HprofSubTag sub_tag) { - WriteU1(static_cast(sub_tag)); + WriteU1(helpers::ToUnderlying(sub_tag)); } void HprofWriterBuffer::WriteU1(HprofBasicType base_type) { - WriteU1(static_cast(base_type)); + WriteU1(helpers::ToUnderlying(base_type)); } -void HprofWriterBuffer::WriteU2(uint16_t input) +template +void HprofWriterBuffer::WriteU(T input) { - for (size_t i = U2_LENGTH; i > 0; i--) { + for (size_t i = LENGTH; i > 0; i--) { auto mov = BITS_IN_BYTE * (i - 1); - buffer_.push_back(static_cast(input >> mov) & MASK_INT8); + buffer_.push_back(static_cast(input >> mov) & MASK_INT8); } - length_ += U2_LENGTH; + length_ += LENGTH; +} + +void HprofWriterBuffer::WriteU2(uint16_t input) +{ + WriteU(input); } 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; + WriteU(input); } 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; + WriteU(input); } -void HprofWriterBuffer::WriteId(uint32_t input) +template +void HprofWriterBuffer::WriteIdU(T input) { if (id_length_ == ID_LENGTH_4B) { WriteU4(input); @@ -102,27 +101,20 @@ void HprofWriterBuffer::WriteId(uint32_t input) } } +void HprofWriterBuffer::WriteId(uint32_t input) +{ + WriteIdU(input); +} + 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"; - } + WriteIdU(input); } 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"; - } + WriteIdU(id); } void HprofWriterBuffer::WriteArrayU1(const PandaString &input, size_t size) diff --git a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h index 111eac196..0106ff644 100644 --- a/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h +++ b/runtime/mem/heapdumper/hprof/hprof_writer_buffer.h @@ -153,6 +153,13 @@ public: DEFAULT_MOVE_SEMANTIC(HprofWriterBuffer); private: + + template + void WriteU(T input); + + template + void WriteIdU(T input); + size_t id_length_ = DEFAULT_ID_LENGTH; uint64_t start_time_nanos_ = 0; size_t length_ = 0; diff --git a/runtime/runtime.cpp b/runtime/runtime.cpp index 8d46bf037..a4baabd62 100644 --- a/runtime/runtime.cpp +++ b/runtime/runtime.cpp @@ -371,6 +371,12 @@ bool Runtime::Create(const RuntimeOptions &options) options.GetSamplingProfilerInterval()); } + // add to tools + if (options.WasSetHeapDumpOnSigint()) { + instance_->GetTools().CreateHeapDump(); + instance_->GetTools().StartHeapDump(); + } + return true; } @@ -904,13 +910,6 @@ 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_catcher.cpp b/runtime/signal_catcher.cpp new file mode 100644 index 000000000..c4ab4b7df --- /dev/null +++ b/runtime/signal_catcher.cpp @@ -0,0 +1,88 @@ +/** + * 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 "signal_catcher.h" +#include "runtime/include/panda_vm.h" +#include "runtime/include/thread.h" +#include "libpandabase/os/native_stack.h" +#include "libpandabase/os/property.h" +#include "libpandabase/os/failure_retry.h" +#include "runtime/methodtrace/trace.h" +#include "runtime/mem/gc/gc-hung/gc_hung.h" + +namespace panda { + +static bool SigUsr1Handler([[maybe_unused]] PandaVM *vm) +{ + auto sigusr1_flag = DfxController::GetOptionValue(DfxOptionHandler::ARK_SIGUSR1); + if (sigusr1_flag == 0) { + LOG(ERROR, DFX) << "ARK_SIGUSR1 action disabled, setprop ark.dfx.options to restart"; + return false; + } + + LOG(ERROR, RUNTIME) << "SIGUSR1 action : dump"; + + auto res = vm->GetGC()->DumpHeap(); + + if (!res) + { + LOG(ERROR, RUNTIME) << "heap dump called by signal failed"; + return false; + } + + return true; +} + +bool SignalCatcher::DoSignalAction(int sig) +{ + switch (sig) { + case SIGQUIT: + return SigUsr1Handler(vm_); + default: + LOG(INFO, RUNTIME) << "Unexpected signal = " << sig; + return false; + } +} + +/* static */ +void SignalCatcher::SignalAction(int sig, SignalCatcher *self) +{ + self->DoSignalAction(sig); +} + +bool SignalCatcher::Initialize() +{ + Runtime::GetCurrent()->GetNotificationManager()->AddListener(this, RuntimeNotificationManager::Event::THREAD_EVENTS); + StartThread(); + return true; +} + +void SignalCatcher::Uninitialize() +{ + StopThread(); +} + +void SignalCatcher::StartThread() +{ + + signal_catcher_thread_.StartThread(&SignalCatcher::SignalAction, this); +} + +void SignalCatcher::StopThread() +{ + signal_catcher_thread_.StopThread(); +} + +} // namespace panda diff --git a/runtime/signal_catcher.h b/runtime/signal_catcher.h new file mode 100644 index 000000000..6f9e58a05 --- /dev/null +++ b/runtime/signal_catcher.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2023 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_SIGNAL_CATCHER_H +#define PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_SIGNAL_CATCHER_H + +#include +#include +#include +#include "utils/logger.h" +#include "platforms/unix/libpandabase/signal.h" +#include "runtime/include/runtime_notification.h" + +namespace panda { + +class SignalCatcher final : public RuntimeListener { +public: + SignalCatcher() = default; + ~SignalCatcher() = default; + NO_COPY_SEMANTIC(SignalCatcher); + NO_MOVE_SEMANTIC(SignalCatcher); + + bool Initialize(); + void Uninitialize(); + + void StartThread(); + void StopThread(); + +private: + static void SignalAction(int sig, SignalCatcher *self); + bool DoSignalAction(int sig); + + os::unix::SignalCatcherThread signal_catcher_thread_; +}; + +} // namespace panda + +#endif // PANDA_RUNTIME_MEM_HEAPDUMPER_HPROF_SIGNAL_CATCHER_H diff --git a/runtime/signal_handler.cpp b/runtime/signal_handler.cpp index b8be7a9be..465823ea5 100644 --- a/runtime/signal_handler.cpp +++ b/runtime/signal_handler.cpp @@ -154,7 +154,6 @@ void SignalManager::InitSignals() SA_SIGINFO, }; AddSpecialSignalHandlerFn(SIGSEGV, &sigchain_action); - AddSpecialSignalHandlerFn(SIGINT, &sigchain_action); is_init_ = true; @@ -496,7 +495,7 @@ bool StackOverflowHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, return true; } - +/* move to sepatate thread and use signal same as deveco bool CtrlBreakHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, [[maybe_unused]] void *context) { if (sig != SIGINT) { @@ -508,4 +507,5 @@ bool CtrlBreakHandler::Action(int sig, [[maybe_unused]] siginfo_t *siginfo, [[ma _exit(is_dump_heap_success ? 0 : 1); } +*/ } // namespace panda -- Gitee