From e9849c74cf956f38c3bb3ef2503f621e3a2dbf59 Mon Sep 17 00:00:00 2001 From: SciresM Date: Sat, 11 Sep 2021 19:32:14 -0700 Subject: [PATCH] LogManager: implement system module, client api, logging api (#1617) Some notes: * Unless `atmosphere!enable_log_manager` is true, Nintendo's log manager will be used instead. * This prevents paying memory costs for LM when not enabling logging. * To facilitate this, Atmosphere's log manager has a different program id from Nintendo's. * `atmosphere!enable_htc` implies `atmosphere!enable_log_manager`. * LogManager logs to tma, and the SD card (if `lm!enable_sd_card_logging` is true, which it is by default). * Binary logs are saved to `lm!sd_card_log_output_directory`, which is `atmosphere/binlogs` by default. --- Makefile | 2 + config_templates/system_settings.ini | 11 + .../libstratosphere/include/stratosphere.hpp | 4 + .../impl/ams_system_thread_definitions.hpp | 12 +- .../include/stratosphere/diag.hpp | 25 ++ .../include/stratosphere/diag/diag_log.hpp | 40 ++ .../stratosphere/diag/diag_log_observer.hpp | 41 ++ .../stratosphere/diag/diag_log_types.hpp | 57 +++ .../stratosphere/diag/diag_sdk_log.hpp | 32 ++ .../diag/impl/diag_impl_build_config.hpp | 37 ++ .../stratosphere/diag/impl/diag_impl_log.hpp | 26 ++ .../diag/impl/diag_impl_structured_log.hpp | 44 +++ .../impl/diag_impl_structured_sdk_log.hpp | 33 ++ .../stratosphere/diag/impl/diag_utf8_util.hpp | 23 ++ .../include/stratosphere/lm.hpp | 21 + .../include/stratosphere/lm/lm_api.hpp | 26 ++ .../include/stratosphere/lm/lm_log_getter.hpp | 26 ++ .../include/stratosphere/lm/lm_types.hpp | 29 ++ .../ncm/ncm_system_content_meta_id.hpp | 4 +- .../stratosphere/os/os_waitable_utils.hpp | 21 + .../include/stratosphere/psc/psc_types.hpp | 13 +- .../include/stratosphere/ro.hpp | 3 + .../ro/impl/ro_ro_exception_info.hpp | 31 ++ .../include/stratosphere/rocrt/rocrt.hpp | 68 ++++ .../include/stratosphere/time.hpp | 6 +- .../time/impl/util/time_impl_util_api.hpp | 11 + .../include/stratosphere/time/time_api.hpp | 2 + .../stratosphere/time/time_calendar_time.hpp | 67 ++++ .../stratosphere/time/time_timezone_api.hpp | 26 ++ .../include/stratosphere/util.hpp | 8 +- .../util/util_singleton_traits.hpp | 45 +++ .../source/boot2/boot2_api.cpp | 26 +- .../libstratosphere/source/diag/diag_log.cpp | 62 +++ .../source/diag/diag_log_impl.hpp | 27 ++ .../source/diag/diag_log_observer.cpp | 166 ++++++++ .../diag/impl/diag_module_impl.os.horizon.cpp | 61 +++ .../diag/impl/diag_observer_manager.hpp | 157 ++++++++ .../diag/impl/diag_print_debug_string.hpp | 24 ++ .../diag_print_debug_string.os.horizon.cpp | 35 ++ .../diag/impl/diag_process.os.horizon.cpp | 100 +++++ .../source/diag/impl/diag_utf8_util.cpp | 91 +++++ .../source/erpt/srv/erpt_srv_service.cpp | 8 +- .../htc/server/htc_power_state_control.cpp | 20 +- .../source/lm/impl/lm_log_data_chunk.hpp | 35 ++ .../source/lm/impl/lm_log_packet_header.hpp | 59 +++ .../lm/impl/lm_log_packet_transmitter.hpp | 74 ++++ .../impl/lm_log_packet_transmitter_base.cpp | 166 ++++++++ .../impl/lm_log_packet_transmitter_base.hpp | 60 +++ .../libstratosphere/source/lm/lm_api.cpp | 121 ++++++ .../source/lm/lm_remote_log_service.cpp | 61 +++ .../source/lm/lm_remote_log_service.hpp | 63 +++ .../source/lm/lm_service_name.hpp | 25 ++ .../source/lm/sf/lm_i_log_getter.hpp | 24 ++ .../source/lm/sf/lm_i_log_service.hpp | 28 ++ .../source/lm/srv/lm_custom_sink_buffer.cpp | 60 +++ .../source/lm/srv/lm_custom_sink_buffer.hpp | 42 ++ .../lm/srv/lm_event_log_transmitter.cpp | 116 ++++++ .../lm/srv/lm_event_log_transmitter.hpp | 45 +++ .../source/lm/srv/lm_flush_thread.cpp | 179 +++++++++ .../source/lm/srv/lm_ipc_server.cpp | 145 +++++++ .../source/lm/srv/lm_log_buffer.cpp | 207 ++++++++++ .../source/lm/srv/lm_log_buffer.hpp | 73 ++++ .../source/lm/srv/lm_log_getter.cpp | 46 +++ .../source/lm/srv/lm_log_getter.hpp | 30 ++ .../source/lm/srv/lm_log_getter_impl.cpp | 48 +++ .../source/lm/srv/lm_log_getter_impl.hpp | 44 +++ .../source/lm/srv/lm_log_packet_parser.cpp | 276 +++++++++++++ .../source/lm/srv/lm_log_packet_parser.hpp | 39 ++ .../source/lm/srv/lm_log_server_proxy.cpp | 218 +++++++++++ .../source/lm/srv/lm_log_server_proxy.hpp | 53 +++ .../source/lm/srv/lm_log_service_impl.cpp | 43 +++ .../source/lm/srv/lm_log_service_impl.hpp | 28 ++ .../source/lm/srv/lm_logger_impl.cpp | 147 +++++++ .../source/lm/srv/lm_logger_impl.hpp | 35 ++ .../source/lm/srv/lm_sd_card_logger.cpp | 361 ++++++++++++++++++ .../source/lm/srv/lm_sd_card_logger.hpp | 46 +++ .../source/lm/srv/lm_time_util.cpp | 78 ++++ .../source/lm/srv/lm_time_util.hpp | 23 ++ .../source/ro/impl/ro_ro_exception_info.cpp | 111 ++++++ .../time/impl/util/time_impl_util_api.cpp | 227 +++++++++++ .../libstratosphere/source/time/time_api.cpp | 17 +- .../source/time/time_calendar_time.cpp | 46 +++ .../source/time/time_timezone_api.cpp | 28 ++ libraries/libvapours/include/vapours/util.hpp | 1 + .../vapours/util/util_format_string.hpp | 4 +- .../vapours/util/util_utf8_string_util.hpp | 27 ++ .../source/util/util_format_string.cpp | 7 +- .../source/util/util_utf8_string_util.cpp | 121 ++++++ stratosphere/LogManager/LogManager.json | 88 +++++ stratosphere/LogManager/Makefile | 113 ++++++ stratosphere/LogManager/source/lm_main.cpp | 165 ++++++++ stratosphere/Makefile | 2 +- .../source/set_mitm/settings_sd_kvs.cpp | 13 + .../loader/source/ldr_process_creation.cpp | 1 + 94 files changed, 5595 insertions(+), 45 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/diag.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/lm.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp create mode 100644 libraries/libstratosphere/source/diag/diag_log.cpp create mode 100644 libraries/libstratosphere/source/diag/diag_log_impl.hpp create mode 100644 libraries/libstratosphere/source/diag/diag_log_observer.cpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp create mode 100644 libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp create mode 100644 libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp create mode 100644 libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp create mode 100644 libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp create mode 100644 libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp create mode 100644 libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp create mode 100644 libraries/libstratosphere/source/lm/lm_api.cpp create mode 100644 libraries/libstratosphere/source/lm/lm_remote_log_service.cpp create mode 100644 libraries/libstratosphere/source/lm/lm_remote_log_service.hpp create mode 100644 libraries/libstratosphere/source/lm/lm_service_name.hpp create mode 100644 libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp create mode 100644 libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_time_util.cpp create mode 100644 libraries/libstratosphere/source/lm/srv/lm_time_util.hpp create mode 100644 libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.cpp create mode 100644 libraries/libstratosphere/source/time/time_calendar_time.cpp create mode 100644 libraries/libstratosphere/source/time/time_timezone_api.cpp create mode 100644 libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp create mode 100644 libraries/libvapours/source/util/util_utf8_string_util.cpp create mode 100644 stratosphere/LogManager/LogManager.json create mode 100644 stratosphere/LogManager/Makefile create mode 100644 stratosphere/LogManager/source/lm_main.cpp diff --git a/Makefile b/Makefile index b49ac9789..48ea5a455 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ dist-no-debug: all mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000037 mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000003C mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042 + mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420 cp stratosphere/boot2/boot2.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000008/exefs.nsp cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000000D/exefs.nsp cp stratosphere/erpt/erpt.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000002B/exefs.nsp @@ -119,6 +120,7 @@ dist-no-debug: all cp stratosphere/ro/ro.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000037/exefs.nsp cp stratosphere/jpegdec/jpegdec.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000003C/exefs.nsp cp stratosphere/pgl/pgl.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042/exefs.nsp + cp stratosphere/LogManager/LogManager.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420/exefs.nsp @build_romfs atmosphere-$(AMSVER)/stratosphere_romfs atmosphere-$(AMSVER)/atmosphere/stratosphere.romfs rm -r atmosphere-$(AMSVER)/stratosphere_romfs cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro diff --git a/config_templates/system_settings.ini b/config_templates/system_settings.ini index f02491abb..58cb606b1 100644 --- a/config_templates/system_settings.ini +++ b/config_templates/system_settings.ini @@ -9,6 +9,13 @@ ; Control whether RO should ease its validation of NROs. ; (note: this is normally not necessary, and ips patches can be used.) ; ease_nro_restriction = u8!0x1 +[lm] +; Control whether lm should log to the SD card. +; Note that this setting does nothing when log manager is not enabled. +; enable_sd_card_logging = u8!0x1 +; Control the output directory for SD card logs. +; Note that this setting does nothing when log manager is not enabled/sd card logging is not enabled. +; sd_card_log_output_directory = str!atmosphere/binlogs ; Atmosphere custom settings [atmosphere] ; Reboot from fatal automatically after some number of milliseconds. @@ -53,6 +60,10 @@ ; Controls whether htc is enabled ; 0 = Disabled, 1 = Enabled ; enable_htc = u8!0x0 +; Controls whether atmosphere's log manager is enabled +; Note that this setting is ignored (and treated as 1) when htc is enabled. +; 0 = Disabled, 1 = Enabled +; enable_log_manager = u8!0x0 [hbloader] ; Controls the size of the homebrew heap when running as applet. ; If set to zero, all available applet memory is used as heap. diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp index b2b5cfff4..d63eeaf0e 100644 --- a/libraries/libstratosphere/include/stratosphere.hpp +++ b/libraries/libstratosphere/include/stratosphere.hpp @@ -19,6 +19,9 @@ /* libvapours (pulls in util, svc, results). */ #include +/* Libstratosphere diagnostics. */ +#include + /* Libstratosphere definitions. */ #include @@ -62,6 +65,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 3cec1f265..2af496816 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -24,7 +24,7 @@ namespace ams::impl { }; #define AMS_DEFINE_SYSTEM_THREAD(__AMS_THREAD_PRIORITY__, __AMS_MODULE__, __AMS_THREAD_NAME__) \ - constexpr inline const ::ams::impl::SystemThreadDefinition SystemThreadDefinition##__AMS_MODULE__##__AMS_THREAD_NAME__ = { __AMS_THREAD_PRIORITY__, "ams." # __AMS_MODULE__ "." #__AMS_THREAD_NAME__ } + constexpr inline const ::ams::impl::SystemThreadDefinition SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__ = { __AMS_THREAD_PRIORITY__, "ams." # __AMS_MODULE__ "." #__AMS_THREAD_NAME__ } /* sm. */ AMS_DEFINE_SYSTEM_THREAD(-1, sm, Main); @@ -69,6 +69,12 @@ namespace ams::impl { /* boot2. */ AMS_DEFINE_SYSTEM_THREAD(20, boot2, Main); + /* LogManager. */ + AMS_DEFINE_SYSTEM_THREAD(10, LogManager, MainThread); + AMS_DEFINE_SYSTEM_THREAD(10, lm, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(10, lm, Flush); + AMS_DEFINE_SYSTEM_THREAD(10, lm, HtcsConnection); + /* dmnt. */ AMS_DEFINE_SYSTEM_THREAD(-3, dmnt, MultiCoreEventManager); AMS_DEFINE_SYSTEM_THREAD(-1, dmnt, CheatDebugEvents); @@ -167,5 +173,5 @@ namespace ams::impl { } -#define AMS_GET_SYSTEM_THREAD_PRIORITY(__AMS_MODULE__, __AMS_THREAD_NAME__) ::ams::impl::SystemThreadDefinition##__AMS_MODULE__##__AMS_THREAD_NAME__.priority -#define AMS_GET_SYSTEM_THREAD_NAME(__AMS_MODULE__, __AMS_THREAD_NAME__) ::ams::impl::SystemThreadDefinition##__AMS_MODULE__##__AMS_THREAD_NAME__.name +#define AMS_GET_SYSTEM_THREAD_PRIORITY(__AMS_MODULE__, __AMS_THREAD_NAME__) ::ams::impl::SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__.priority +#define AMS_GET_SYSTEM_THREAD_NAME(__AMS_MODULE__, __AMS_THREAD_NAME__) ::ams::impl::SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__.name diff --git a/libraries/libstratosphere/include/stratosphere/diag.hpp b/libraries/libstratosphere/include/stratosphere/diag.hpp new file mode 100644 index 000000000..da721d0c4 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include + +#include + diff --git a/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp b/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp new file mode 100644 index 000000000..0b4f7c13d --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +#if defined(AMS_IMPL_ENABLE_LOG) + +#define AMS_LOG(...) AMS_IMPL_STRUCTURED_LOG_IMPL("", ::ams::diag::LogSeverity_Info, 0, __VA_ARGS__) +#define AMS_VLOG(_FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("", ::ams::diag::LogSeverity_Info, 0, _FMT_, _VL_) +#define AMS_PUT(_MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("", ::ams::diag::LogSeverity_Info, 0, _MSG_, _ML_) + +#define AMS_STRUCTURED_LOG(_MOD_, _SEV_, _VER_, ...) AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, __VA_ARGS__) +#define AMS_STRUCTURED_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) +#define AMS_STRUCTURED_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) + +#else + +#define AMS_LOG(...) do { /* ... */ } while (false) +#define AMS_VLOG(_FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_PUT(_MSG_, _ML_) do { /* ... */ } while (false) + +#define AMS_STRUCTURED_LOG(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_STRUCTURED_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_STRUCTURED_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp b/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp new file mode 100644 index 000000000..c49c78b48 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::diag { + + using LogObserver = void (*)(const LogMetaData &meta, const LogBody &body, void *arg); + + struct LogObserverHolder { + LogObserver log_observer; + LogObserverHolder *next; + bool is_registered; + void *arg; + }; + + constexpr inline void InitializeLogObserverHolder(LogObserverHolder *holder, LogObserver observer, void *arg) { + holder->log_observer = observer; + holder->next = nullptr; + holder->is_registered = false; + holder->arg = arg; + } + + void RegisterLogObserver(LogObserverHolder *holder); + void UnregisterLogObserver(LogObserverHolder *holder); + +} diff --git a/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp b/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp new file mode 100644 index 000000000..8b7178ecb --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::diag { + + enum LogSeverity { + LogSeverity_Trace = 0, + LogSeverity_Info = 1, + LogSeverity_Warn = 2, + LogSeverity_Error = 3, + LogSeverity_Fatal = 4, + }; + + struct SourceInfo { + int line_number; + const char *file_name; + const char *function_name; + }; + + struct LogMetaData { + SourceInfo source_info; + const char *module_name; + LogSeverity severity; + int verbosity; + bool use_default_locale_charset; + void *additional_data; + size_t additional_data_size; + }; + + struct LogBody { + const char *message; + size_t message_size; + bool is_head; + bool is_tail; + }; + + struct LogMessage { + const char *fmt; + std::va_list *vl; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp b/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp new file mode 100644 index 000000000..50cd1aecc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +#if defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_SDK_LOG(...) AMS_IMPL_STRUCTURED_LOG_IMPL("$", ::ams::diag::LogSeverity_Info, 0, __VA_ARGS__) +#define AMS_SDK_VLOG(_FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("$", ::ams::diag::LogSeverity_Info, 0, _FMT_, _VL_) +#define AMS_SDK_PUT(_MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("$", ::ams::diag::LogSeverity_Info, 0, _MSG_, _ML_) + +#else + +#define AMS_SDK_LOG(...) do { /* ... */ } while (false) +#define AMS_SDK_VLOG(_FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_SDK_PUT(_MSG_, _ML_) do { /* ... */ } while (false) + +#endif \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp new file mode 100644 index 000000000..203ada5ee --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + #define AMS_IMPL_ENABLE_SDK_LOG +#else + //#define AMS_IMPL_ENABLE_SDK_LOG +#endif + +#if defined(AMS_ENABLE_LOG) + #define AMS_IMPL_ENABLE_LOG + + #if defined(AMS_DISABLE_LOG) + #error "Incoherent log configuration" + #endif +#elif defined(AMS_DISABLE_LOG) + +#elif defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + #define AMS_IMPL_ENABLE_LOG +#else + //#define AMS_IMPL_ENABLE_LOG +#endif diff --git a/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp new file mode 100644 index 000000000..81b78bcd1 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::diag::impl { + + void LogImpl(const LogMetaData &meta, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void VLogImpl(const LogMetaData &meta, const char *fmt, std::va_list vl); + void PutImpl(const LogMetaData &meta, const char *msg, size_t msg_size); + +} diff --git a/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp new file mode 100644 index 000000000..ae95743d6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +#if defined(AMS_IMPL_ENABLE_LOG) || defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_) \ + (::ams::diag::LogMetaData { \ + { __LINE__, __FILE__, AMS_CURRENT_FUNCTION_NAME }, \ + _MOD_, \ + _SEV_, \ + _VER_, \ + false, \ + nullptr, \ + 0, \ + }) + +#define AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, ...) ::ams::diag::impl::LogImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), __VA_ARGS__) +#define AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) ::ams::diag::impl::VLogImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), _FMT_, _VL_) +#define AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) ::ams::diag::impl::PutImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), _MSG_, _ML_) + +#else + +#define AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif diff --git a/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp new file mode 100644 index 000000000..c54df2afc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +#if defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_IMPL_STRUCTURED_SDK_LOG(_MOD_, _SEV_, _VER_, ...) AMS_IMPL_STRUCTURED_LOG_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, __VA_ARGS__) +#define AMS_IMPL_STRUCTURED_SDK_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, _FMT_, _VL_) +#define AMS_IMPL_STRUCTURED_SDK_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, _MSG_, _ML_) + +#else + +#define AMS_IMPL_STRUCTURED_SDK_LOG(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_SDK_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_SDK_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif diff --git a/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp new file mode 100644 index 000000000..a00db1932 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::diag::impl { + + int GetValidSizeAsUtf8String(const char *str, size_t len); + +} diff --git a/libraries/libstratosphere/include/stratosphere/lm.hpp b/libraries/libstratosphere/include/stratosphere/lm.hpp new file mode 100644 index 000000000..c25c69c9b --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/lm.hpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp b/libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp new file mode 100644 index 000000000..753417482 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm { + + void Initialize(); + void Finalize(); + + void SetDestination(u32 destination); + +} diff --git a/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp b/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp new file mode 100644 index 000000000..6871ba856 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm { + + void StartLogging(); + void StopLogging(); + + void GetLog(char *dst, size_t size, s64 *out_write_size, u32 *out_drop_count); + +} diff --git a/libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp b/libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp new file mode 100644 index 000000000..19cb029e3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm { + + enum LogDestination { + LogDestination_TargetManager = (1 << 0), + LogDestination_Uart = (1 << 1), + LogDestination_UartIfSleep = (1 << 2), + + LogDestination_All = 0xFFFF, + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp index f8028b078..ee6ed273c 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp @@ -116,12 +116,14 @@ namespace ams::ncm { } static const AtmosphereProgramId Mitm; + static const AtmosphereProgramId AtmosphereLogManager; }; inline constexpr const AtmosphereProgramId AtmosphereProgramId::Mitm = { 0x010041544D530000ul }; + inline constexpr const AtmosphereProgramId AtmosphereProgramId::AtmosphereLogManager = { 0x0100000000000420ul }; inline constexpr bool IsAtmosphereProgramId(const ProgramId &program_id) { - return program_id == AtmosphereProgramId::Mitm; + return program_id == AtmosphereProgramId::Mitm || program_id == AtmosphereProgramId::AtmosphereLogManager; } inline constexpr bool IsSystemProgramId(const AtmosphereProgramId &program_id) { diff --git a/libraries/libstratosphere/include/stratosphere/os/os_waitable_utils.hpp b/libraries/libstratosphere/include/stratosphere/os/os_waitable_utils.hpp index 5a25dd429..6cfd5ab7a 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_waitable_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_waitable_utils.hpp @@ -88,6 +88,17 @@ namespace ams::os { using WaitAnyFunction = WaitableHolderType * (*)(WaitableManagerType *); + class NotBoolButInt { + private: + int m_value; + public: + constexpr ALWAYS_INLINE NotBoolButInt(int v) : m_value(v) { /* ... */ } + + constexpr ALWAYS_INLINE operator int() const { return m_value; } + + explicit operator bool() const = delete; + }; + } template requires (sizeof...(Args) > 0) @@ -100,4 +111,14 @@ namespace ams::os { return impl::WaitAnyImpl(static_cast(&::ams::os::WaitAny), std::forward(args)...).second; } + template requires (sizeof...(Args) > 0) + inline std::pair TryWaitAny(WaitableManagerType *manager, Args &&... args) { + return impl::WaitAnyImpl(static_cast(&::ams::os::TryWaitAny), manager, std::forward(args)...); + } + + template requires (sizeof...(Args) > 0) + inline impl::NotBoolButInt TryWaitAny(Args &&... args) { + return impl::WaitAnyImpl(static_cast(&::ams::os::TryWaitAny), std::forward(args)...).second; + } + } diff --git a/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp b/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp index 3902552d4..e7e5d5190 100644 --- a/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp @@ -20,12 +20,13 @@ namespace ams::psc { enum PmState { - PmState_Awake = 0, - PmState_ReadyAwaken = 1, - PmState_ReadySleep = 2, - PmState_ReadySleepCritical = 3, - PmState_ReadyAwakenCritical = 4, - PmState_ReadyShutdown = 5, + PmState_FullAwake = 0, + PmState_MinimumAwake = 1, + PmState_SleepReady = 2, + PmState_EssentialServicesSleepReady = 3, + PmState_EssentialServicesAwake = 4, + PmState_ShutdownReady = 5, + PmState_Unknown = 6, }; constexpr inline u32 MaximumDependencyLevels = 20; diff --git a/libraries/libstratosphere/include/stratosphere/ro.hpp b/libraries/libstratosphere/include/stratosphere/ro.hpp index d3a695220..ad1c8f858 100644 --- a/libraries/libstratosphere/include/stratosphere/ro.hpp +++ b/libraries/libstratosphere/include/stratosphere/ro.hpp @@ -19,3 +19,6 @@ #include #include #include +#include + +#include diff --git a/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp b/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp new file mode 100644 index 000000000..9068ba8f4 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::ro::impl { + + struct ExceptionInfo { + uintptr_t module_address; + size_t module_size; + uintptr_t info_offset; + size_t info_size; + }; + + bool GetExceptionInfo(ExceptionInfo *out, uintptr_t pc); + +} \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp b/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp new file mode 100644 index 000000000..051d5cecf --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::rocrt { + + constexpr inline const u32 ModuleHeaderVersion = util::FourCC<'M','O','D','0'>::Code; + + struct ModuleHeader { + u32 signature; + u32 dynamic_offset; + u32 bss_start_offset; + u32 bss_end_offset; + u32 exception_info_start_offset; + u32 exception_info_end_offset; + u32 module_offset; + }; + + struct ModuleHeaderLocation { + u32 pad; + u32 header_offset; + }; + + constexpr inline u32 CheckModuleHeaderSignature(const ModuleHeader *header) { + if (header->signature == ModuleHeaderVersion) { + return header->signature; + } else { + return 0; + } + } + + constexpr inline ModuleHeader *GetModuleHeader(const ModuleHeaderLocation *loc) { + return reinterpret_cast(reinterpret_cast(loc) + loc->header_offset); + } + + constexpr inline uintptr_t GetDynamicOffset(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast(loc) + loc->header_offset + header->dynamic_offset; + } + + + constexpr inline uintptr_t GetBssStartAddress(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast(loc) + loc->header_offset + header->bss_start_offset; + } + + constexpr inline uintptr_t GetBssEndAddress(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast(loc) + loc->header_offset + header->bss_end_offset; + } + + constexpr inline uintptr_t GetModuleOffset(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast(loc) + loc->header_offset + header->module_offset; + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/time.hpp b/libraries/libstratosphere/include/stratosphere/time.hpp index 26349ec08..cc2e5e571 100644 --- a/libraries/libstratosphere/include/stratosphere/time.hpp +++ b/libraries/libstratosphere/include/stratosphere/time.hpp @@ -17,9 +17,11 @@ #pragma once #include -#include -#include #include +#include +#include +#include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp b/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp index 464a43baf..6af945a8d 100644 --- a/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp @@ -23,4 +23,15 @@ namespace ams::time::impl::util { Result GetSpanBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to); + bool IsLeapYear(int year); + bool IsValidDate(int year, int month, int day); + + int GetDaysInMonth(int year, int month); + + int DateToDays(int year, int month, int day); + void DaysToDate(int *out_year, int *out_month, int *out_day, int days); + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time); + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time); + } diff --git a/libraries/libstratosphere/include/stratosphere/time/time_api.hpp b/libraries/libstratosphere/include/stratosphere/time/time_api.hpp index 1784ea93b..6ea77190a 100644 --- a/libraries/libstratosphere/include/stratosphere/time/time_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/time/time_api.hpp @@ -29,6 +29,8 @@ namespace ams::time { bool IsInitialized(); + bool IsValidDate(int year, int month, int day); + Result GetElapsedSecondsBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to); } diff --git a/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp b/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp new file mode 100644 index 000000000..69b5fe138 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::time { + + struct CalendarTime { + s16 year; + s8 month; + s8 day; + s8 hour; + s8 minute; + s8 second; + + bool IsValid() const; + + CalendarTime &operator+=(const TimeSpan &ts); + CalendarTime &operator-=(const TimeSpan &ts); + + friend CalendarTime operator+(const CalendarTime &lhs, const TimeSpan &rhs); + friend CalendarTime operator-(const CalendarTime &lhs, const TimeSpan &rhs); + + friend TimeSpan operator-(const CalendarTime &lhs, const CalendarTime &rhs); + + constexpr friend bool operator==(const CalendarTime &lhs, const CalendarTime &rhs) { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second; } + constexpr friend bool operator!=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(lhs == rhs); } + + constexpr friend bool operator<=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(rhs < lhs); } + constexpr friend bool operator>=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(lhs < rhs); } + constexpr friend bool operator> (const CalendarTime &lhs, const CalendarTime &rhs) { return rhs < lhs; } + + constexpr friend bool operator< (const CalendarTime &lhs, const CalendarTime &rhs) { + if (!std::is_constant_evaluated()) { + AMS_ASSERT(lhs.IsValid()); + AMS_ASSERT(rhs.IsValid()); + } + + constexpr auto ToUint64 = [] ALWAYS_INLINE_LAMBDA (const time::CalendarTime &time) { + return (static_cast(time.year) << 40) | + (static_cast(time.month) << 32) | + (static_cast(time.day) << 24) | + (static_cast(time.hour) << 16) | + (static_cast(time.minute) << 8) | + (static_cast(time.second) << 0); + }; + + return ToUint64(lhs) < ToUint64(rhs); + } + }; + static_assert(sizeof(CalendarTime) == sizeof(u64)); + +} diff --git a/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp b/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp new file mode 100644 index 000000000..950c5e532 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::time { + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time); + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time); + +} diff --git a/libraries/libstratosphere/include/stratosphere/util.hpp b/libraries/libstratosphere/include/stratosphere/util.hpp index db47d867f..c557e4b7a 100644 --- a/libraries/libstratosphere/include/stratosphere/util.hpp +++ b/libraries/libstratosphere/include/stratosphere/util.hpp @@ -13,9 +13,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #pragma once -#include "util/util_uuid_api.hpp" -#include "util/util_compression.hpp" -#include "util/util_ini.hpp" +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp b/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp new file mode 100644 index 000000000..52e849d86 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#define AMS_SINGLETON_TRAITS(_CLASSNAME_) \ + private: \ + NON_COPYABLE(_CLASSNAME_); \ + NON_MOVEABLE(_CLASSNAME_); \ + private: \ + _CLASSNAME_ (); \ + public: \ + static _CLASSNAME_ &GetInstance() { \ + /* Declare singleton instance variables. */ \ + static constinit ::ams::util::TypedStorage<_CLASSNAME_> s_singleton_storage; \ + static constinit ::ams::os::SdkMutex s_singleton_mutex; \ + static constinit bool s_initialized_singleton = false; \ + \ + /* Ensure the instance is created. */ \ + if (AMS_UNLIKELY(!s_initialized_singleton)) { \ + std::scoped_lock lk(s_singleton_mutex); \ + \ + if (AMS_LIKELY(!s_initialized_singleton)) { \ + new (::ams::util::GetPointer(s_singleton_storage)) _CLASSNAME_; \ + s_initialized_singleton = true; \ + } \ + } \ + \ + return ::ams::util::GetReference(s_singleton_storage); \ + } diff --git a/libraries/libstratosphere/source/boot2/boot2_api.cpp b/libraries/libstratosphere/source/boot2/boot2_api.cpp index e892b8c2a..0270873aa 100644 --- a/libraries/libstratosphere/source/boot2/boot2_api.cpp +++ b/libraries/libstratosphere/source/boot2/boot2_api.cpp @@ -40,7 +40,7 @@ namespace ams::boot2 { ncm::SystemProgramId::Vi, /* vi */ ncm::SystemProgramId::Pgl, /* pgl */ ncm::SystemProgramId::Ns, /* ns */ - ncm::SystemProgramId::LogManager, /* lm */ + //ncm::SystemProgramId::LogManager, /* lm */ ncm::SystemProgramId::Ppc, /* ppc */ ncm::SystemProgramId::Ptm, /* ptm */ ncm::SystemProgramId::Hid, /* hid */ @@ -83,7 +83,7 @@ namespace ams::boot2 { ncm::SystemProgramId::Vi, /* vi */ ncm::SystemProgramId::Pgl, /* pgl */ ncm::SystemProgramId::Ns, /* ns */ - ncm::SystemProgramId::LogManager, /* lm */ + //ncm::SystemProgramId::LogManager, /* lm */ ncm::SystemProgramId::Ppc, /* ppc */ ncm::SystemProgramId::Ptm, /* ptm */ ncm::SystemProgramId::Hid, /* hid */ @@ -183,11 +183,22 @@ namespace ams::boot2 { } bool IsHtcEnabled() { - u8 enable_htc = 1; + u8 enable_htc = 0; settings::fwdbg::GetSettingsItemValue(&enable_htc, sizeof(enable_htc), "atmosphere", "enable_htc"); return enable_htc != 0; } + bool IsAtmosphereLogManagerEnabled() { + /* If htc is enabled, ams log manager is enabled. */ + if (IsHtcEnabled()) { + return true; + } + + u8 enable_ams_lm = 0; + settings::fwdbg::GetSettingsItemValue(&enable_ams_lm, sizeof(enable_ams_lm), "atmosphere", "enable_log_manager"); + return enable_ams_lm != 0; + } + bool IsMaintenanceMode() { /* Contact set:sys, retrieve boot!force_maintenance. */ if (IsForceMaintenance()) { @@ -390,7 +401,7 @@ namespace ams::boot2 { /* Check for and forward declare non-atmosphere mitm modules. */ DetectAndDeclareFutureMitms(); - /* Device whether to launch tma or htc. */ + /* Decide whether to launch tma or htc. */ if (IsHtcEnabled()) { LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Htc, ncm::StorageId::None), 0); LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Cs, ncm::StorageId::None), 0); @@ -398,6 +409,13 @@ namespace ams::boot2 { LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0); } + /* Decide whether to launch atmosphere or nintendo's log manager. */ + if (IsAtmosphereLogManagerEnabled()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::AtmosphereProgramId::AtmosphereLogManager, ncm::StorageId::None), 0); + } else { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::LogManager, ncm::StorageId::None), 0); + } + /* Launch additional programs. */ if (maintenance) { LaunchList(AdditionalMaintenanceLaunchPrograms, NumAdditionalMaintenanceLaunchPrograms); diff --git a/libraries/libstratosphere/source/diag/diag_log.cpp b/libraries/libstratosphere/source/diag/diag_log.cpp new file mode 100644 index 000000000..e40cd1057 --- /dev/null +++ b/libraries/libstratosphere/source/diag/diag_log.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "diag_log_impl.hpp" + +namespace ams::diag::impl { + + void CallAllLogObserver(const LogMetaData &meta, const LogBody &body); + + namespace { + + struct CallPrintDebugString { + void operator()(const LogMetaData &meta, const char *msg, size_t size, bool head, bool tail) { + const LogBody body = { + .message = msg, + .message_size = size, + .is_head = head, + .is_tail = tail + }; + + CallAllLogObserver(meta, body); + } + }; + + } + + void LogImpl(const LogMetaData &meta, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + VLogImpl(meta, fmt, vl); + va_end(vl); + } + + void VLogImpl(const LogMetaData &meta, const char *fmt, std::va_list vl) { + /* Print to stack buffer. */ + char msg_buffer[DebugPrintBufferLength]; + + /* TODO: VFormatString using utf-8 printer. */ + const size_t len = util::VSNPrintf(msg_buffer, sizeof(msg_buffer), fmt, vl); + + /* Call log observer. */ + CallPrintDebugString()(meta, msg_buffer, len, true, true); + } + + void PutImpl(const LogMetaData &meta, const char *msg, size_t msg_size) { + CallPrintDebugString()(meta, msg, msg_size, true, true); + } + +} diff --git a/libraries/libstratosphere/source/diag/diag_log_impl.hpp b/libraries/libstratosphere/source/diag/diag_log_impl.hpp new file mode 100644 index 000000000..0efaa432f --- /dev/null +++ b/libraries/libstratosphere/source/diag/diag_log_impl.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::diag { + + namespace impl { + + constexpr inline size_t DebugPrintBufferLength = 0x80; + + } + +} diff --git a/libraries/libstratosphere/source/diag/diag_log_observer.cpp b/libraries/libstratosphere/source/diag/diag_log_observer.cpp new file mode 100644 index 000000000..b3ca0c8d9 --- /dev/null +++ b/libraries/libstratosphere/source/diag/diag_log_observer.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "diag_log_impl.hpp" +#include "impl/diag_observer_manager.hpp" +#include "impl/diag_print_debug_string.hpp" + +namespace ams::diag { + + namespace impl { + + namespace { + + constexpr inline size_t DecorationStringLengthMax = 0x61; + + constexpr inline const char *EscapeSequencesForSeverity[] = { + "\x1B[90m", /* Dark Gray (Trace) */ + nullptr, /* None (Info) */ + "\x1B[33m", /* Yellow (Warn) */ + "\x1B[31m", /* Red (Error) */ + "\x1B[41m\x1B[37m", /* White-on-red (Fatal) */ + }; + + constexpr inline const char EscapeSequenceReset[] = "\x1B[0m"; + + constexpr inline size_t PrintBufferLength = DecorationStringLengthMax + impl::DebugPrintBufferLength + 1; + + constinit os::SdkMutex g_print_buffer_mutex; + constinit char g_print_buffer[PrintBufferLength]; + + inline void GetCurrentTime(int *h, int *m, int *s, int *ms) { + /* Get the current time. */ + const auto cur_time = os::GetSystemTick().ToTimeSpan(); + + /* Extract fields. */ + const s64 hours = cur_time.GetHours(); + const s64 minutes = cur_time.GetMinutes(); + const s64 seconds = cur_time.GetSeconds(); + const s64 milliseconds = cur_time.GetMilliSeconds(); + + /* Set out fields. */ + *h = static_cast(hours); + *m = static_cast(minutes - hours * 60); + *s = static_cast(seconds - minutes * 60); + *ms = static_cast(milliseconds - seconds * 1000); + } + + void TentativeDefaultLogObserver(const LogMetaData &meta, const LogBody &body, void *) { + /* Acquire access to the print buffer */ + std::scoped_lock lk(g_print_buffer_mutex); + + /* Get the escape sequence. */ + const char *escape = nullptr; + if (LogSeverity_Trace <= meta.severity && meta.severity <= LogSeverity_Fatal) { + escape = EscapeSequencesForSeverity[meta.severity]; + } + + /* Declare message variables. */ + const char *msg = nullptr; + size_t msg_size = 0; + + /* Handle structured logs. */ + const bool structured = meta.module_name != nullptr && std::strlen(meta.module_name) >= 2; + if (escape || structured) { + /* Insert timestamp, if head. */ + if (structured && body.is_head) { + /* Get current timestamp. */ + int hours, minutes, seconds, milliseconds; + GetCurrentTime(std::addressof(hours), std::addressof(minutes), std::addressof(seconds), std::addressof(milliseconds)); + + /* Print the timestamp/header. */ + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%s%d:%02d:%02d.%03d [%-5.63s] ", escape ? escape : "", hours, minutes, seconds, milliseconds, meta.module_name[0] == '$' ? meta.module_name + 1 : meta.module_name + 0); + + AMS_AUDIT(msg_size <= DecorationStringLengthMax); + } else if (escape) { + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%s", escape); + } + + /* Determine maximum remaining size. */ + const size_t max_msg_size = PrintBufferLength - msg_size - (escape ? sizeof(EscapeSequenceReset) - 1 : 0); + + /* Determine printable size. */ + size_t printable_size = std::min(body.message_size, max_msg_size); + + /* Determine newline status. */ + bool new_line = false; + if (body.message_size > 0 && body.message[body.message_size - 1] == '\n') { + --printable_size; + new_line = true; + } + + /* Print the messsage. */ + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%.*s%s%s", static_cast(printable_size), body.message, escape ? EscapeSequenceReset : "", new_line ? "\n" : ""); + + /* Set the message. */ + msg = g_print_buffer; + } else { + /* Use the body's message directly. */ + msg = body.message; + msg_size = body.message_size; + } + + /* Print the string. */ + impl::PrintDebugString(msg, msg_size); + } + + struct LogObserverContext { + const LogMetaData &meta; + const LogBody &body; + }; + + using LogObserverManager = ObserverManagerWithDefaultHolder; + + constinit LogObserverManager g_log_observer_manager(::ams::diag::InitializeLogObserverHolder, TentativeDefaultLogObserver, nullptr); + + } + + void CallAllLogObserver(const LogMetaData &meta, const LogBody &body) { + /* Create context. */ + const LogObserverContext context = { .meta = meta, .body = body }; + + /* Invoke the log observer. */ + g_log_observer_manager.InvokeAllObserver(context, [] (const LogObserverHolder &holder, const LogObserverContext &context) { + holder.log_observer(context.meta, context.body, holder.arg); + }); + } + + void ReplaceDefaultLogObserver(LogObserver observer) { + /* Get the default observer. */ + auto *default_holder = std::addressof(g_log_observer_manager.GetDefaultObserverHolder()); + + /* Unregister, replace, and re-register. */ + UnregisterLogObserver(default_holder); + InitializeLogObserverHolder(default_holder, observer, nullptr); + RegisterLogObserver(default_holder); + } + + void ResetDefaultLogObserver() { + /* Restore the default observer. */ + ReplaceDefaultLogObserver(TentativeDefaultLogObserver); + } + + } + + void RegisterLogObserver(LogObserverHolder *holder) { + impl::g_log_observer_manager.RegisterObserver(holder); + } + + void UnregisterLogObserver(LogObserverHolder *holder) { + impl::g_log_observer_manager.UnregisterObserver(holder); + } + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp b/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp new file mode 100644 index 000000000..533f6826e --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::diag::impl { + + namespace { + + constexpr inline uintptr_t ModulePathLengthOffset = 4; + constexpr inline uintptr_t ModulePathOffset = 8; + + } + + uintptr_t GetModuleInfoForHorizon(const char **out_path, size_t *out_path_length, size_t *out_module_size, uintptr_t address) { + /* Check for null address. */ + if (address == 0) { + return 0; + } + + /* Get module info. */ + ro::impl::ExceptionInfo exception_info; + if (!ro::impl::GetExceptionInfo(std::addressof(exception_info), address)) { + return 0; + } + + /* Locate the path in the first non-read-execute segment. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + auto cur_address = exception_info.module_address; + while (cur_address < exception_info.module_address + exception_info.module_size) { + if (R_FAILED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur_address))) { + return 0; + } + if (mem_info.perm != svc::MemoryPermission_ReadExecute) { + break; + } + cur_address += mem_info.size; + } + + /* Set output info. */ + *out_path = reinterpret_cast(cur_address + ModulePathOffset); + *out_path_length = *reinterpret_cast(cur_address + ModulePathLengthOffset); + *out_module_size = exception_info.module_size; + + return exception_info.module_address; + } + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp b/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp new file mode 100644 index 000000000..02119b1fa --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::diag::impl { + + template + class ObserverManager { + NON_COPYABLE(ObserverManager); + NON_MOVEABLE(ObserverManager); + private: + Holder *m_observer_list_head; + Holder **m_observer_list_tail; + os::ReadWriteLock m_lock; + public: + constexpr ObserverManager() : m_observer_list_head(nullptr), m_observer_list_tail(std::addressof(m_observer_list_head)), m_lock() { + /* ... */ + } + + constexpr ~ObserverManager() { + if (std::is_constant_evaluated()) { + this->UnregisterAllObserverLocked(); + } else { + this->UnregisterAllObserver(); + } + } + + void RegisterObserver(Holder *holder) { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + this->RegisterObserverLocked(holder); + } + + void UnregisterObserver(Holder *holder) { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + /* Check that we can unregister. */ + AMS_ASSERT(holder->is_registered); + + /* Remove the holder. */ + if (m_observer_list_head == holder) { + m_observer_list_head = holder->next; + if (m_observer_list_tail == std::addressof(holder->next)) { + m_observer_list_tail = std::addressof(m_observer_list_head); + } + } else { + for (auto *cur = m_observer_list_head; cur != nullptr; cur = cur->next) { + if (cur->next == holder) { + cur->next = holder->next; + + if (m_observer_list_tail == std::addressof(holder->next)) { + m_observer_list_tail = std::addressof(cur->next); + } + + break; + } + } + } + + /* Set unregistered. */ + holder->next = nullptr; + } + + void UnregisterAllObserver() { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + this->UnregisterAllObserverLocked(); + } + + void InvokeAllObserver(const Context &context) { + /* Use the holder's observer. */ + InvokeAllObserver(context, [] (const Holder &holder, const Context &context) { + holder.observer(context); + }); + } + + template + void InvokeAllObserver(const Context &context, Observer observer) { + /* Acquire a read hold on our lock. */ + std::shared_lock lk(m_lock); + + /* Invoke all observers. */ + for (const auto *holder = m_observer_list_head; holder != nullptr; holder = holder->next) { + observer(*holder, context); + } + } + protected: + constexpr void RegisterObserverLocked(Holder *holder) { + /* Check that we can register. */ + AMS_ASSERT(!holder->is_registered); + + /* Insert the holder. */ + *m_observer_list_tail = holder; + m_observer_list_tail = std::addressof(holder->next); + + /* Set registered. */ + holder->next = nullptr; + holder->is_registered = true; + } + + constexpr void UnregisterAllObserverLocked() { + /* Unregister all observers. */ + for (auto *holder = m_observer_list_head; holder != nullptr; holder = holder->next) { + holder->is_registered = false; + } + + /* Reset head/fail. */ + m_observer_list_head = nullptr; + m_observer_list_tail = std::addressof(m_observer_list_head); + } + }; + + template + class ObserverManagerWithDefaultHolder : public ObserverManager { + private: + Holder m_default_holder; + public: + template + constexpr ObserverManagerWithDefaultHolder(Initializer initializer, Args &&... args) : ObserverManager(), m_default_holder{} { + /* Initialize the default observer. */ + initializer(std::addressof(m_default_holder), std::forward(args)...); + + /* Register the default observer. */ + if (std::is_constant_evaluated()) { + this->RegisterObserverLocked(std::addressof(m_default_holder)); + } else { + this->RegisterObserver(std::addressof(m_default_holder)); + } + } + + Holder &GetDefaultObserverHolder() { + return m_default_holder; + } + + const Holder &GetDefaulObservertHolder() const { + return m_default_holder; + } + }; + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp b/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp new file mode 100644 index 000000000..cd2cc0c5b --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::diag::impl { + + void PrintDebugString(const char *msg, size_t size); + void PrintDebugString(const char *msg); + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp b/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp new file mode 100644 index 000000000..455d0eea3 --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "diag_print_debug_string.hpp" + +namespace ams::diag::impl { + + void PrintDebugString(const char *msg, size_t size) { + AMS_AUDIT(msg != nullptr || size == 0); + + if (size != 0) { + svc::OutputDebugString(msg, size); + } + } + + void PrintDebugString(const char *msg) { + AMS_AUDIT(msg != nullptr); + + PrintDebugString(msg, std::strlen(msg)); + } + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp b/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp new file mode 100644 index 000000000..da64de259 --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +/* TODO: Rename, if we change to e.g. use amsMain? */ +extern "C" int main(int argc, char **argv); + +namespace ams::diag::impl { + + uintptr_t GetModuleInfoForHorizon(const char **out_path, size_t *out_path_length, size_t *out_module_size, uintptr_t address); + + namespace { + + const char *GetLastCharacterPointer(const char *str, size_t len, char c) { + for (const char *last = str + len - 1; last >= str; --last) { + if (*last == c) { + return last; + } + } + return nullptr; + } + + void GetFileNameWithoutExtension(const char **out, size_t *out_size, const char *path, size_t path_length) { + const auto last_sep1 = GetLastCharacterPointer(path, path_length, '\\'); + const auto last_sep2 = GetLastCharacterPointer(path, path_length, '/'); + const auto ext = GetLastCharacterPointer(path, path_length, '.'); + + /* Handle last-separator. */ + if (last_sep1 && last_sep2) { + if (last_sep1 > last_sep2) { + *out = last_sep1 + 1; + } else { + *out = last_sep2 + 1; + } + } else if (last_sep1) { + *out = last_sep1 + 1; + } else if (last_sep2) { + *out = last_sep2 + 1; + } else { + *out = path; + } + + /* Handle extension. */ + if (ext && ext >= *out) { + *out_size = ext - *out; + } else { + *out_size = (path + path_length) - *out; + } + } + + constinit const char *g_process_name = nullptr; + constinit size_t g_process_name_size = 0; + constinit os::SdkMutex g_process_name_lock; + constinit bool g_got_process_name = false; + + void EnsureProcessNameCached() { + /* Ensure process name. */ + if (AMS_UNLIKELY(!g_got_process_name)) { + std::scoped_lock lk(g_process_name_lock); + if (AMS_LIKELY(!g_got_process_name)) { + const char *path; + size_t path_length; + size_t module_size; + + if (GetModuleInfoForHorizon(std::addressof(path), std::addressof(path_length), std::addressof(module_size), reinterpret_cast(main)) != 0) { + GetFileNameWithoutExtension(std::addressof(g_process_name), std::addressof(g_process_name_size), path, path_length); + AMS_ASSERT(g_process_name_size == 0 || util::VerifyUtf8String(g_process_name, g_process_name_size)); + } else { + g_process_name = ""; + g_process_name_size = 0; + } + } + } + } + + } + + void GetProcessNamePointer(const char **out, size_t *out_size) { + /* Ensure process name is cached. */ + EnsureProcessNameCached(); + + /* Get cached process name. */ + *out = g_process_name; + *out_size = g_process_name_size; + } + +} diff --git a/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp b/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp new file mode 100644 index 000000000..23866589f --- /dev/null +++ b/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::diag::impl { + + namespace { + + bool IsHeadOfCharacter(u8 c) { + return (c & 0xC0) != 0x80; + } + + size_t GetCharacterSize(u8 c) { + if ((c & 0x80) == 0) { + return 1; + } else if ((c & 0xE0) == 0xC0) { + return 2; + } else if ((c & 0xF0) == 0xE0) { + return 3; + } else if ((c & 0xF8) == 0xF0) { + return 4; + } + + return 0; + } + + const char *FindLastCharacterPointer(const char *str, size_t len) { + /* Find the head of the last character. */ + const char *cur; + for (cur = str + len - 1; cur >= str && !IsHeadOfCharacter(*reinterpret_cast(cur)); --cur) { + /* ... */ + } + + /* Return the last character. */ + if (AMS_LIKELY(cur >= str)) { + return cur; + } else { + return nullptr; + } + } + + } + + int GetValidSizeAsUtf8String(const char *str, size_t len) { + /* Check pre-condition. */ + AMS_ASSERT(str != nullptr); + + /* Check if we have no data. */ + if (len == 0) { + return 0; + } + + /* Get the last character pointer. */ + const auto *last_char_ptr = FindLastCharacterPointer(str, len); + if (last_char_ptr == nullptr) { + return -1; + } + + /* Get sizes. */ + const size_t actual_size = (str + len) - last_char_ptr; + const size_t last_char_size = GetCharacterSize(*reinterpret_cast(last_char_ptr)); + if (last_char_size == 0) { + return -1; + } else if (actual_size >= last_char_size) { + if (actual_size == last_char_size) { + return len; + } else { + return -1; + } + } else if (actual_size >= len) { + AMS_ASSERT(actual_size == len); + return -1; + } else { + return static_cast(len - actual_size); + } + } + +} diff --git a/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp b/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp index 63fd84a7d..1eec3c395 100644 --- a/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp +++ b/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp @@ -114,14 +114,14 @@ namespace ams::erpt::srv { pm_module.GetEventPointer()->Clear(); if (R_SUCCEEDED(pm_module.GetRequest(std::addressof(pm_state), std::addressof(pm_flags)))) { switch (pm_state) { - case psc::PmState_Awake: - case psc::PmState_ReadyAwaken: + case psc::PmState_FullAwake: + case psc::PmState_MinimumAwake: Stream::EnableFsAccess(true); break; - case psc::PmState_ReadyShutdown: + case psc::PmState_ShutdownReady: FinalizeForcedShutdownDetection(); [[fallthrough]]; - case psc::PmState_ReadySleep: + case psc::PmState_SleepReady: Stream::EnableFsAccess(false); break; default: diff --git a/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp b/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp index 68297f208..00535a01c 100644 --- a/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp +++ b/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp @@ -60,16 +60,16 @@ namespace ams::htc::server { /* Update sleeping state. */ switch (pm_state) { - case psc::PmState_Awake: + case psc::PmState_FullAwake: if (g_is_asleep) { g_htclow_manager->NotifyAwake(); g_is_asleep = false; } break; - case psc::PmState_ReadyAwaken: - case psc::PmState_ReadySleep: - case psc::PmState_ReadySleepCritical: - case psc::PmState_ReadyAwakenCritical: + case psc::PmState_MinimumAwake: + case psc::PmState_SleepReady: + case psc::PmState_EssentialServicesSleepReady: + case psc::PmState_EssentialServicesAwake: if (!g_is_asleep) { g_htclow_manager->NotifyAsleep(); g_is_asleep = true; @@ -81,16 +81,16 @@ namespace ams::htc::server { /* Update suspend state. */ switch (pm_state) { - case psc::PmState_Awake: - case psc::PmState_ReadyAwaken: + case psc::PmState_FullAwake: + case psc::PmState_MinimumAwake: if (g_is_suspended) { g_htclow_manager->Resume(); g_is_suspended = false; } break; - case psc::PmState_ReadySleep: - case psc::PmState_ReadySleepCritical: - case psc::PmState_ReadyAwakenCritical: + case psc::PmState_SleepReady: + case psc::PmState_EssentialServicesSleepReady: + case psc::PmState_EssentialServicesAwake: if (!g_is_suspended) { g_htclow_manager->Suspend(); g_is_suspended = true; diff --git a/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp b/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp new file mode 100644 index 000000000..231149d0d --- /dev/null +++ b/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::impl { + + enum LogDataChunkKey { + LogDataChunkKey_LogSessionBegin = 0, + LogDataChunkKey_LogSessionEnd = 1, + LogDataChunkKey_TextLog = 2, + LogDataChunkKey_LineNumber = 3, + LogDataChunkKey_FileName = 4, + LogDataChunkKey_FunctionName = 5, + LogDataChunkKey_ModuleName = 6, + LogDataChunkKey_ThreadName = 7, + LogDataChunkKey_LogPacketDropCount = 8, + LogDataChunkKey_UserSystemClock = 9, + LogDataChunkKey_ProcessName = 10, + }; + +} diff --git a/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp b/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp new file mode 100644 index 000000000..8a30d2a04 --- /dev/null +++ b/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::impl { + + constexpr inline size_t LogPacketHeaderSize = 0x18; + + class LogPacketHeader { + private: + u64 m_process_id; + u64 m_thread_id; + u8 m_flags; + u8 m_padding; + u8 m_severity; + u8 m_verbosity; + u32 m_payload_size; + public: + constexpr u64 GetProcessId() const { return m_process_id; } + constexpr void SetProcessId(u64 v) { m_process_id = v; } + + constexpr u64 GetThreadId() const { return m_thread_id; } + constexpr void SetThreadId(u64 v) { m_thread_id = v; } + + constexpr bool IsHead() const { return (m_flags & (1 << 0)) != 0; } + constexpr void SetHead(bool v) { m_flags = (m_flags & ~(1 << 0)) | ((v ? 1 : 0) << 0); } + + constexpr bool IsTail() const { return (m_flags & (1 << 1)) != 0; } + constexpr void SetTail(bool v) { m_flags = (m_flags & ~(1 << 1)) | ((v ? 1 : 0) << 1); } + + constexpr bool IsLittleEndian() const { return (m_flags & (1 << 2)) != 0; } + constexpr void SetLittleEndian(bool v) { m_flags = (m_flags & ~(1 << 2)) | ((v ? 1 : 0) << 2); } + + constexpr u8 GetSeverity() const { return m_severity; } + constexpr void SetSeverity(u8 v) { m_severity = v; } + + constexpr u8 GetVerbosity() const { return m_verbosity; } + constexpr void SetVerbosity(u8 v) { m_verbosity = v; } + + constexpr u32 GetPayloadSize() const { return m_payload_size; } + constexpr void SetPayloadSize(u32 v) { m_payload_size = v; } + }; + static_assert(sizeof(LogPacketHeader) == LogPacketHeaderSize); + +} diff --git a/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp new file mode 100644 index 000000000..bbf62d837 --- /dev/null +++ b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "lm_log_packet_transmitter_base.hpp" + +namespace ams::lm::impl { + + class LogPacketTransmitter : public LogPacketTransmitterBase { + public: + LogPacketTransmitter(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail) + : LogPacketTransmitterBase(buffer, buffer_size, flush_func, severity, verbosity, process_id, head, tail) { /* ... */ } + + void PushLogSessionBegin() { + bool value = true; + this->PushDataChunk(LogDataChunkKey_LogSessionBegin, std::addressof(value), sizeof(value)); + } + + void PushLogSessionEnd() { + bool value = true; + this->PushDataChunk(LogDataChunkKey_LogSessionEnd, std::addressof(value), sizeof(value)); + } + + void PushTextLog(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_TextLog, str, len); + } + + void PushLineNumber(u32 line) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(line), sizeof(line)); + } + + void PushFileName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_FileName, str, len); + } + + void PushFunctionName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_FunctionName, str, len); + } + + void PushModuleName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ModuleName, str, len); + } + + void PushThreadName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ThreadName, str, len); + } + + void PushLogPacketDropCount(u64 count) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(count), sizeof(count)); + } + + void PushUserSystemClock(s64 posix_time) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(posix_time), sizeof(posix_time)); + } + + void PushProcessName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ProcessName, str, len); + } + }; + +} diff --git a/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp new file mode 100644 index 000000000..96f2f67a9 --- /dev/null +++ b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_packet_transmitter_base.hpp" + +namespace ams::lm::impl { + + LogPacketTransmitterBase::LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast(buffer), alignof(LogPacketHeader))); + AMS_ASSERT(buffer_size >= LogPacketHeaderSize); + AMS_ASSERT(flush_func != nullptr); + + /* Construct log packet header. */ + m_header = std::construct_at(static_cast(buffer)); + + /* Set fields. */ + m_start = static_cast(buffer); + m_end = m_start + buffer_size; + m_payload = m_start + LogPacketHeaderSize; + m_current = m_payload; + + m_is_tail = tail; + + m_flush_function = flush_func; + + /* Set header fields. */ + m_header->SetProcessId(process_id); + m_header->SetThreadId(os::GetThreadId(os::GetCurrentThread())); + m_header->SetHead(head); + m_header->SetLittleEndian(util::IsLittleEndian()); + m_header->SetSeverity(severity); + m_header->SetVerbosity(verbosity); + } + + bool LogPacketTransmitterBase::Flush(bool is_tail) { + /* Check if we're already flushed. */ + if (m_current == m_payload) { + return true; + } + + /* Flush the data. */ + m_header->SetTail(is_tail); + m_header->SetPayloadSize(static_cast(m_current - m_payload)); + const auto result = m_flush_function(m_start, static_cast(m_current - m_start)); + m_header->SetHead(false); + + /* Reset. */ + m_current = m_payload; + + return result; + } + + size_t LogPacketTransmitterBase::GetRemainSize() { + return static_cast(m_end - m_current); + } + + size_t LogPacketTransmitterBase::GetPushableDataSize(size_t uleb_size) { + const size_t remain = this->GetRemainSize(); + if (remain < uleb_size + 2) { + return 0; + } + + const size_t cmp = remain - uleb_size; + u64 mask = 0x7F; + size_t n; + for (n = 1; mask + n < cmp; ++n) { + mask |= mask << 7; + } + + return cmp - n; + } + + size_t LogPacketTransmitterBase::GetRequiredSizeToPushUleb128(u64 v) { + /* Determine bytes needed for uleb128 value. */ + size_t required = 0; + do { + ++required; + v >>= 7; + } while (v > 0); + + return required; + } + + void LogPacketTransmitterBase::PushUleb128(u64 v) { + const u32 Mask = 0x7F; + const u32 InverseMask = ~Mask; + do { + /* Check we're within bounds. */ + AMS_ASSERT(m_current < m_end); + + /* Write byte. */ + *(m_current++) = static_cast(v & Mask) | (((v & InverseMask) != 0) ? 0x80 : 0x00); + + /* Adjust remaining bit range. */ + v >>= 7; + } while (v > 0); + } + + void LogPacketTransmitterBase::PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t data_size, bool is_text) { + /* Check pre-conditions. */ + AMS_ASSERT(data != nullptr); + + /* Push as much data as we can, until the chunk is complete. */ + const u8 *cur = static_cast(data); + const u8 * const end = cur + data_size; + const size_t required_key = this->GetRequiredSizeToPushUleb128(key); + do { + /* Get the pushable size. */ + size_t pushable_size = this->GetPushableDataSize(required_key); + size_t required_size = is_text ? 4 : 1; + if (pushable_size < required_size) { + this->Flush(false); + pushable_size = this->GetPushableDataSize(required_key); + } + AMS_ASSERT(pushable_size >= required_size); + + /* Determine the current size. */ + size_t current_size = std::min(pushable_size, end - cur); + if (is_text) { + const auto valid_size = diag::impl::GetValidSizeAsUtf8String(reinterpret_cast(cur), current_size); + if (valid_size >= 0) { + current_size = static_cast(valid_size); + } + } + + /* Push data. */ + this->PushUleb128(key); + this->PushUleb128(current_size); + this->PushData(cur, current_size); + + /* Advance. */ + cur = cur + current_size; + } while (cur < end); + + /* Check that we pushed all the data. */ + AMS_ASSERT(cur == end); + } + + void LogPacketTransmitterBase::PushData(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size <= this->GetRemainSize()); + + /* Push the data. */ + if (size > 0) { + std::memcpy(m_current, data, size); + m_current += size; + } + } + +} diff --git a/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp new file mode 100644 index 000000000..dfb7a7daf --- /dev/null +++ b/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "lm_log_packet_header.hpp" +#include "lm_log_data_chunk.hpp" + +namespace ams::lm::impl { + + class LogPacketTransmitterBase { + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + LogPacketHeader *m_header; + u8 *m_start; + u8 *m_end; + u8 *m_payload; + u8 *m_current; + bool m_is_tail; + FlushFunction m_flush_function; + protected: + LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail); + + ~LogPacketTransmitterBase() { + this->Flush(m_is_tail); + } + + void PushDataChunk(LogDataChunkKey key, const void *data, size_t size) { + this->PushDataChunkImpl(key, data, size, false); + } + + void PushDataChunk(LogDataChunkKey key, const char *str, size_t size) { + this->PushDataChunkImpl(key, str, size, true); + } + public: + bool Flush(bool is_tail); + private: + size_t GetRemainSize(); + size_t GetPushableDataSize(size_t uleb_size); + size_t GetRequiredSizeToPushUleb128(u64 v); + + void PushUleb128(u64 v); + void PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t size, bool is_text); + void PushData(const void *data, size_t size); + }; + +} diff --git a/libraries/libstratosphere/source/lm/lm_api.cpp b/libraries/libstratosphere/source/lm/lm_api.cpp new file mode 100644 index 000000000..8b5ee1cbf --- /dev/null +++ b/libraries/libstratosphere/source/lm/lm_api.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_remote_log_service.hpp" +#include "impl/lm_log_packet_header.hpp" +#include "impl/lm_log_packet_transmitter.hpp" + +namespace ams::diag::impl { + + void ReplaceDefaultLogObserver(LogObserver observer); + void ResetDefaultLogObserver(); + + void GetProcessNamePointer(const char **out, size_t *out_len); + +} + +namespace ams::lm { + + namespace { + + constinit sf::SharedPointer g_logger = nullptr; + + constexpr inline size_t TransmissionBufferSize = 1_KB; + constexpr inline size_t TransmissionBufferAlign = alignof(impl::LogPacketHeader); + + constinit os::SdkMutex g_transmission_buffer_mutex; + + alignas(TransmissionBufferAlign) constinit char g_transmission_buffer[TransmissionBufferSize]; + + using PushTextFunction = void (impl::LogPacketTransmitter::*)(const char *, size_t); + + bool LogPacketTransmitterFlushFunction(const u8 *data, size_t size) { + const Result result = g_logger->Log(sf::InAutoSelectBuffer(data, size)); + R_ABORT_UNLESS(result); + return R_SUCCEEDED(result); + } + + void InvokePushTextWithUtf8Sanitizing(impl::LogPacketTransmitter &transmitter, PushTextFunction push_func, const char *str) { + /* Get the string length. */ + const auto len = std::strlen(str); + + if (len == 0 || util::VerifyUtf8String(str, len)) { + (transmitter.*push_func)(str, len); + } else { + (transmitter.*push_func)("(Invalid UTF8 string)", sizeof("(Invalid UTF8 string)") - 1); + } + } + + void LogManagerLogObserver(const diag::LogMetaData &meta, const diag::LogBody &body, void *) { + /* Check pre-conditions. */ + AMS_ASSERT(!meta.use_default_locale_charset); + + /* Acquire access to the transmission buffer. */ + std::scoped_lock lk(g_transmission_buffer_mutex); + + /* Create transmitter. */ + impl::LogPacketTransmitter transmitter(g_transmission_buffer, TransmissionBufferSize, LogPacketTransmitterFlushFunction, static_cast(meta.severity), static_cast(meta.verbosity), 0, body.is_head, body.is_tail); + + /* Push head-only logs. */ + if (body.is_head) { + transmitter.PushUserSystemClock(os::GetSystemTick().ToTimeSpan().GetSeconds()); + transmitter.PushLineNumber(meta.source_info.line_number); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushFileName, meta.source_info.file_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushFunctionName, meta.source_info.function_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushModuleName, meta.module_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushThreadName, os::GetThreadNamePointer(os::GetCurrentThread())); + + const char *process_name; + size_t process_name_len; + diag::impl::GetProcessNamePointer(std::addressof(process_name), std::addressof(process_name_len)); + + transmitter.PushProcessName(process_name, process_name_len); + } + + /* Push the actual log. */ + transmitter.PushTextLog(body.message, body.message_size); + } + + } + + void Initialize() { + AMS_ABORT_UNLESS(g_logger == nullptr); + + /* Create the logger. */ + { + auto service = CreateLogService(); + R_ABORT_UNLESS(service->OpenLogger(std::addressof(g_logger), sf::ClientProcessId{})); + } + + /* Replace the default log observer. */ + diag::impl::ReplaceDefaultLogObserver(LogManagerLogObserver); + } + + void Finalize() { + AMS_ABORT_UNLESS(g_logger != nullptr); + + /* Reset the default log observer. */ + diag::impl::ResetDefaultLogObserver(); + + /* Destroy the logger. */ + g_logger = nullptr; + } + + void SetDestination(u32 destination) { + g_logger->SetDestination(destination); + } + +} diff --git a/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp b/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp new file mode 100644 index 000000000..09fc70633 --- /dev/null +++ b/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_remote_log_service.hpp" +#include "lm_service_name.hpp" + +namespace ams::lm { + + namespace { + + struct LmRemoteLogServiceTag; + using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<0x80, LmRemoteLogServiceTag>; + using RemoteObjectFactory = ams::sf::ObjectFactory; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + RemoteAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + Result RemoteLogService::OpenLogger(sf::Out> out, const sf::ClientProcessId &client_process_id) { + /* Send libnx command. */ + ::Service logger_srv; + { + #define NX_SERVICE_ASSUME_NON_DOMAIN + R_TRY(serviceDispatch(&m_srv, 0, + .out_num_objects = 1, + .out_objects = &logger_srv, + )); + #undef NX_SERVICE_ASSUME_NON_DOMAIN + } + + /* Open logger. */ + out.SetValue(RemoteObjectFactory::CreateSharedEmplaced<::ams::lm::ILogger, RemoteLogger>(logger_srv)); + return ResultSuccess(); + } + + sf::SharedPointer CreateLogService() { + ::Service srv; + R_ABORT_UNLESS(sm::GetService(std::addressof(srv), LogServiceName)); + + return RemoteObjectFactory::CreateSharedEmplaced<::ams::lm::ILogService, RemoteLogService>(srv); + } + +} diff --git a/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp b/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp new file mode 100644 index 000000000..8ead3cd3c --- /dev/null +++ b/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sf/lm_i_log_service.hpp" + +namespace ams::lm { + + /* TODO: Real libnx primitives? */ + + #define NX_SERVICE_ASSUME_NON_DOMAIN + + class RemoteLogger { + private: + ::Service m_srv; + public: + RemoteLogger(::Service &s) : m_srv(s) { /* ... */ } + ~RemoteLogger() { ::serviceClose(std::addressof(m_srv)); } + public: + /* Actual commands. */ + Result Log(const sf::InAutoSelectBuffer &message) { + return serviceDispatch(&m_srv, 0, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcAutoSelect }, + .buffers = { { message.GetPointer(), message.GetSize() } }, + ); + } + + Result SetDestination(u32 destination) { + return serviceDispatchIn(&m_srv, 1, destination); + } + }; + static_assert(lm::IsILogger); + + class RemoteLogService { + private: + ::Service m_srv; + public: + RemoteLogService(::Service &s) : m_srv(s) { /* ... */ } + ~RemoteLogService() { ::serviceClose(std::addressof(m_srv)); } + public: + /* Actual commands. */ + Result OpenLogger(sf::Out> out, const sf::ClientProcessId &client_process_id); + }; + static_assert(lm::IsILogService); + + #undef NX_SERVICE_ASSUME_NON_DOMAIN + + sf::SharedPointer CreateLogService(); + +} diff --git a/libraries/libstratosphere/source/lm/lm_service_name.hpp b/libraries/libstratosphere/source/lm/lm_service_name.hpp new file mode 100644 index 000000000..7bf8bf5cb --- /dev/null +++ b/libraries/libstratosphere/source/lm/lm_service_name.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm { + + constexpr inline const sm::ServiceName LogServiceName = sm::ServiceName::Encode("lm"); + + constexpr inline const sm::ServiceName LogGetterServiceName = sm::ServiceName::Encode("lm:get"); + +} diff --git a/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp b/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp new file mode 100644 index 000000000..119b816b2 --- /dev/null +++ b/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#define AMS_LM_I_LOG_GETTER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, StartLogging, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, StopLogging, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetLog, (const sf::OutAutoSelectBuffer &message, sf::Out out_size, sf::Out out_drop_count), (message, out_size, out_drop_count)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogGetter, AMS_LM_I_LOG_GETTER_INTERFACE_INFO) diff --git a/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp b/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp new file mode 100644 index 000000000..c27616fcb --- /dev/null +++ b/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#define AMS_LM_I_LOGGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Log, (const sf::InAutoSelectBuffer &message), (message)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, SetDestination, (u32 destination), (destination)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogger, AMS_LM_I_LOGGER_INTERFACE_INFO) + +#define AMS_LM_I_LOG_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenLogger, (sf::Out> out, const sf::ClientProcessId &client_process_id), (out, client_process_id)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogService, AMS_LM_I_LOG_SERVICE_INTERFACE_INFO) diff --git a/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp b/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp new file mode 100644 index 000000000..9881b83ab --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_custom_sink_buffer.hpp" + +namespace ams::lm::srv { + + bool CustomSinkBuffer::TryPush(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(size <= m_buffer_size); + AMS_ASSERT(data || size == 0); + + /* If we have nothing to push, succeed. */ + if (size == 0) { + return true; + } + + /* Check that we can push the data. */ + if (size > m_buffer_size - m_used_buffer_size) { + return false; + } + + /* Push the data. */ + std::memcpy(m_buffer + m_used_buffer_size, data, size); + m_used_buffer_size += size; + + return true; + } + + bool CustomSinkBuffer::TryFlush() { + /* Check that we have data to flush. */ + if (m_used_buffer_size == 0) { + return false; + } + + /* Try to flush the data. */ + if (!m_flush_function(m_buffer, m_used_buffer_size)) { + return false; + } + + /* Clear our used size. */ + m_used_buffer_size = 0; + + return true; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp b/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp new file mode 100644 index 000000000..10e134819 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + class CustomSinkBuffer { + NON_COPYABLE(CustomSinkBuffer); + NON_MOVEABLE(CustomSinkBuffer); + public: + using FlushFunction = bool (*)(const u8 *buffer, size_t buffer_size); + private: + u8 *m_buffer; + size_t m_buffer_size; + size_t m_used_buffer_size; + FlushFunction m_flush_function; + public: + constexpr explicit CustomSinkBuffer(void *buffer, size_t buffer_size, FlushFunction f) : m_buffer(static_cast(buffer)), m_buffer_size(buffer_size), m_used_buffer_size(0), m_flush_function(f) { + AMS_ASSERT(m_buffer != nullptr); + AMS_ASSERT(m_buffer_size > 0); + AMS_ASSERT(m_flush_function != nullptr); + } + + bool TryPush(const void *data, size_t size); + bool TryFlush(); + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp b/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp new file mode 100644 index 000000000..93184bc1a --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_event_log_transmitter.hpp" +#include "lm_log_buffer.hpp" +#include "../impl/lm_log_packet_header.hpp" +#include "../impl/lm_log_packet_transmitter.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline size_t TransmitterBufferAlign = alignof(impl::LogPacketHeader); + constexpr inline size_t TransmitterBufferSizeForSessionInfo = impl::LogPacketHeaderSize + 3; + constexpr inline size_t TransmitterBufferSizeForDropCount = impl::LogPacketHeaderSize + 10; + + bool DefaultFlushFunction(const u8 *data, size_t size) { + return LogBuffer::GetDefaultInstance().TryPush(data, size); + } + + } + + EventLogTransmitter &EventLogTransmitter::GetDefaultInstance() { + static constinit EventLogTransmitter s_default_event_log_transmitter(DefaultFlushFunction); + return s_default_event_log_transmitter; + } + + bool EventLogTransmitter::PushLogSessionBegin(u64 process_id) { + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast(diag::LogSeverity_Info), 0, process_id, true, true); + + /* Push session begin. */ + transmitter.PushLogSessionBegin(); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (!success) { + ++m_log_packet_drop_count; + } + + return success; + } + + bool EventLogTransmitter::PushLogSessionEnd(u64 process_id) { + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast(diag::LogSeverity_Info), 0, process_id, true, true); + + /* Push session end. */ + transmitter.PushLogSessionEnd(); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (!success) { + ++m_log_packet_drop_count; + } + + return success; + } + + bool EventLogTransmitter::PushLogPacketDropCountIfExists() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_log_packet_drop_count_mutex); + + /* If we have no dropped packets, nothing to push. */ + if (m_log_packet_drop_count == 0) { + return true; + } + + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForDropCount]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast(diag::LogSeverity_Info), 0, 0, true, true); + + /* Push log packet drop count. */ + transmitter.PushLogPacketDropCount(m_log_packet_drop_count); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (success) { + m_log_packet_drop_count = 0; + } else { + ++m_log_packet_drop_count; + } + + return success; + } + + void EventLogTransmitter::IncreaseLogPacketDropCount() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_log_packet_drop_count_mutex); + + /* Increase the dropped packet count. */ + ++m_log_packet_drop_count; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp b/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp new file mode 100644 index 000000000..028152033 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + class EventLogTransmitter { + NON_COPYABLE(EventLogTransmitter); + NON_MOVEABLE(EventLogTransmitter); + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + FlushFunction m_flush_function; + size_t m_log_packet_drop_count; + os::SdkMutex m_log_packet_drop_count_mutex; + public: + constexpr explicit EventLogTransmitter(FlushFunction f) : m_flush_function(f), m_log_packet_drop_count(0), m_log_packet_drop_count_mutex() { + AMS_ASSERT(f != nullptr); + } + + static EventLogTransmitter &GetDefaultInstance(); + + bool PushLogSessionBegin(u64 process_id); + bool PushLogSessionEnd(u64 process_id); + + bool PushLogPacketDropCountIfExists(); + + void IncreaseLogPacketDropCount(); + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp b/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp new file mode 100644 index 000000000..82496147f --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_server_proxy.hpp" +#include "lm_sd_card_logger.hpp" +#include "lm_log_buffer.hpp" +#include "lm_event_log_transmitter.hpp" + +namespace ams::lm::srv { + + bool IsSleeping(); + + namespace { + + alignas(os::ThreadStackAlignment) u8 g_flush_thread_stack[8_KB]; + + constinit u8 g_fs_heap[32_KB]; + constinit lmem::HeapHandle g_fs_heap_handle; + + constinit os::ThreadType g_flush_thread; + + os::Event g_stop_event(os::EventClearMode_ManualClear); + os::Event g_sd_logging_event(os::EventClearMode_ManualClear); + os::Event g_host_connection_event(os::EventClearMode_ManualClear); + + constinit std::unique_ptr g_sd_card_detection_event_notifier; + os::SystemEvent g_sd_card_detection_event; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *ptr, size_t size) { + return lmem::FreeToExpHeap(g_fs_heap_handle, ptr); + } + + void HostConnectionObserver(bool is_connected) { + /* Update the host connection event. */ + if (is_connected) { + g_host_connection_event.Signal(); + } else { + g_host_connection_event.Clear(); + + /* Potentially cancel the log buffer push. */ + if (!g_sd_logging_event.TryWait()) { + LogBuffer::GetDefaultInstance().CancelPush(); + } + } + } + + void SdLoggingObserver(bool is_available) { + /* Update the SD card logging event. */ + if (is_available) { + g_sd_logging_event.Signal(); + } else { + g_sd_logging_event.Clear(); + + /* Potentially cancel the log buffer push. */ + if (!g_host_connection_event.TryWait()) { + LogBuffer::GetDefaultInstance().CancelPush(); + } + } + } + + bool WaitForFlush() { + while (true) { + /* Wait for something to be signaled. */ + os::WaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase(), g_sd_card_detection_event.GetBase()); + + /* If we're stopping, no flush. */ + if (g_stop_event.TryWait()) { + return false; + } + + /* If host is connected/we're logging to sd, flush. */ + if (g_host_connection_event.TryWait() || g_sd_logging_event.TryWait()) { + return true; + } + + /* If the sd card is newly inserted, flush. */ + if (g_sd_card_detection_event.TryWait()) { + g_sd_card_detection_event.Clear(); + + if (fs::IsSdCardInserted()) { + return true; + } + } + } + } + + void FlushThreadFunction(void *) { + /* Disable abort. */ + fs::SetEnabledAutoAbort(false); + + /* Create fs heap. */ + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap, sizeof(g_fs_heap), lmem::CreateOption_None); + AMS_ABORT_UNLESS(g_fs_heap_handle != nullptr); + + /* Set fs allocation functions. */ + fs::SetAllocator(AllocateForFs, DeallocateForFs); + + /* Create SD card detection event notifier. */ + R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier))); + R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear)); + + /* Set connection observers. */ + SdCardLogger::GetInstance().SetLoggingObserver(SdLoggingObserver); + LogServerProxy::GetInstance().SetConnectionObserver(HostConnectionObserver); + + /* Do flush loop. */ + do { + if (LogBuffer::GetDefaultInstance().Flush()) { + EventLogTransmitter::GetDefaultInstance().PushLogPacketDropCountIfExists(); + } + } while (WaitForFlush()); + + /* Clear connection observer. */ + LogServerProxy::GetInstance().SetConnectionObserver(nullptr); + + /* Finalize the SD card logger. */ + SdCardLogger::GetInstance().Finalize(); + SdCardLogger::GetInstance().SetLoggingObserver(nullptr); + + /* Destroy the fs heap. */ + lmem::DestroyExpHeap(g_fs_heap_handle); + } + + } + + bool IsFlushAvailable() { + /* If we're sleeping, we can't flush. */ + if (IsSleeping()) { + return false; + } + + /* Try to wait for an event. */ + if (os::TryWaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase()) < 0) { + return false; + } + + /* Return whether we're not stopping. */ + return !os::TryWaitEvent(g_stop_event.GetBase()); + } + + void InitializeFlushThread() { + /* Create the flush thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_flush_thread), FlushThreadFunction, nullptr, g_flush_thread_stack, sizeof(g_flush_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, Flush))); + os::SetThreadNamePointer(std::addressof(g_flush_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, Flush)); + + /* Clear the stop event. */ + g_stop_event.Clear(); + + /* Start the flush thread. */ + os::StartThread(std::addressof(g_flush_thread)); + } + + void FinalizeFlushThread() { + /* Signal the flush thread to stop. */ + g_stop_event.Signal(); + + /* Wait for the flush thread to stop. */ + os::WaitThread(std::addressof(g_flush_thread)); + os::DestroyThread(std::addressof(g_flush_thread)); + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp b/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp new file mode 100644 index 000000000..6a9a14cb1 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "../lm_service_name.hpp" +#include "lm_log_service_impl.hpp" +#include "lm_log_getter.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline size_t LogSessionCountMax = 42; + constexpr inline size_t LogGetterSessionCountMax = 1; + + constexpr inline size_t SessionCountMax = LogSessionCountMax + LogGetterSessionCountMax; + + constexpr inline size_t PortCountMax = 2; + + struct ServerManagerOptions { + static constexpr size_t PointerBufferSize = 0x400; + static constexpr size_t MaxDomains = 31; + static constexpr size_t MaxDomainObjects = 61; + }; + + using ServerManager = sf::hipc::ServerManager; + + constinit util::TypedStorage g_server_manager_storage; + constinit ServerManager *g_server_manager = nullptr; + + constinit util::TypedStorage g_pm_module_storage; + constinit psc::PmModule *g_pm_module; + constinit os::WaitableHolderType g_pm_module_holder; + + constexpr const psc::PmModuleId PmModuleDependencies[] = { psc::PmModuleId_TmaHostIo, psc::PmModuleId_Fs }; + + /* Service objects. */ + constinit sf::UnmanagedServiceObject g_log_service_object; + constinit sf::UnmanagedServiceObject g_log_getter_service_object; + + constinit std::atomic g_is_sleeping = false; + + } + + bool IsSleeping() { + return g_is_sleeping; + } + + void InitializeIpcServer() { + /* Check that we're not already initialized. */ + AMS_ABORT_UNLESS(g_server_manager == nullptr); + AMS_ABORT_UNLESS(g_pm_module == nullptr); + + /* Create and initialize the psc module. */ + g_pm_module = util::ConstructAt(g_pm_module_storage); + R_ABORT_UNLESS(g_pm_module->Initialize(psc::PmModuleId_Lm, PmModuleDependencies, util::size(PmModuleDependencies), os::EventClearMode_ManualClear)); + + /* Create the psc module waitable holder. */ + os::InitializeWaitableHolder(std::addressof(g_pm_module_holder), g_pm_module->GetEventPointer()->GetBase()); + os::SetWaitableHolderUserData(std::addressof(g_pm_module_holder), psc::PmModuleId_Lm); + + /* Create the server manager. */ + g_server_manager = util::ConstructAt(g_server_manager_storage); + + /* Add the pm module holder. */ + g_server_manager->AddUserWaitableHolder(std::addressof(g_pm_module_holder)); + + /* Create services. */ + R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_service_object.GetShared(), LogServiceName, LogSessionCountMax)); + R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_getter_service_object.GetShared(), LogGetterServiceName, LogGetterSessionCountMax)); + + /* Start the server manager. */ + g_server_manager->ResumeProcessing(); + } + + void LoopIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Loop forever, servicing the server. */ + auto prev_state = psc::PmState_Unknown; + while (true) { + /* Get the next signaled holder. */ + auto *signaled_holder = g_server_manager->WaitSignaled(); + if (signaled_holder != std::addressof(g_pm_module_holder)) { + /* If ipc, process. */ + R_ABORT_UNLESS(g_server_manager->Process(signaled_holder)); + } else { + /* If pm module, clear the event. */ + g_pm_module->GetEventPointer()->Clear(); + g_server_manager->AddUserWaitableHolder(signaled_holder); + + /* Get the power state. */ + psc::PmState pm_state; + psc::PmFlagSet pm_flags; + R_ABORT_UNLESS(g_pm_module->GetRequest(std::addressof(pm_state), std::addressof(pm_flags))); + + /* Handle the power state. */ + if (prev_state == psc::PmState_EssentialServicesAwake && pm_state == psc::PmState_MinimumAwake) { + g_is_sleeping = false; + } else if (prev_state == psc::PmState_MinimumAwake && pm_state == psc::PmState_SleepReady) { + g_is_sleeping = true; + } else if (pm_state == psc::PmState_ShutdownReady) { + g_is_sleeping = true; + } + + /* Set the previous state. */ + prev_state = pm_state; + + /* Acknowledge the state transition. */ + R_ABORT_UNLESS(g_pm_module->Acknowledge(pm_state, ResultSuccess())); + } + } + } + + void StopIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Stop the server manager. */ + g_server_manager->RequestStopProcessing(); + } + + void FinalizeIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Destroy the server manager. */ + std::destroy_at(g_server_manager); + g_server_manager = nullptr; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp new file mode 100644 index 000000000..778cbc212 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_buffer.hpp" +#include "lm_log_server_proxy.hpp" +#include "lm_sd_card_logger.hpp" +#include "lm_time_util.hpp" +#include "lm_log_packet_parser.hpp" + +namespace ams::lm::srv { + + namespace { + + void UpdateUserSystemClock(const u8 *data, size_t size) { + /* Get the current time. */ + const time::PosixTime current_time = GetCurrentTime(); + + /* Get the base time. */ + s64 base_time = current_time.value - os::GetSystemTick().ToTimeSpan().GetSeconds(); + + /* Modify the message timestamp. */ + LogPacketParser::ParsePacket(data, size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool { + /* Check that we're a header message. */ + if (!header.IsHead()) { + return true; + } + + /* Find the timestamp data chunk. */ + return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg) -> bool { + /* Convert the argument. */ + const s64 *p_base_time = static_cast(arg); + + /* Modify user system clock. */ + if (key == impl::LogDataChunkKey_UserSystemClock) { + /* Get the time from the chunk. */ + s64 time; + AMS_ASSERT(chunk_size == sizeof(time)); + std::memcpy(std::addressof(time), chunk, chunk_size); + + /* Add the base time. */ + time += *p_base_time; + + /* Update the time in the chunk. */ + std::memcpy(const_cast(chunk), std::addressof(time), sizeof(time)); + } + + return true; + }, arg); + }, std::addressof(base_time)); + } + + bool DefaultFlushFunction(const u8 *data, size_t size) { + /* Update clock. */ + static constinit bool s_is_user_system_clock_updated = false; + if (!s_is_user_system_clock_updated) { + UpdateUserSystemClock(data, size); + s_is_user_system_clock_updated = true; + } + + /* Send the message. */ + const bool tma_success = LogServerProxy::GetInstance().Send(data, size); + const bool sd_success = SdCardLogger::GetInstance().Write(data, size); + const bool is_success = tma_success || sd_success; + + /* If we succeeded, wipe the current time. */ + s_is_user_system_clock_updated &= !is_success; + + return is_success; + } + + } + + LogBuffer &LogBuffer::GetDefaultInstance() { + static constinit u8 s_default_buffers[128_KB * 2]; + static constinit LogBuffer s_default_log_buffer(s_default_buffers, sizeof(s_default_buffers), DefaultFlushFunction); + + return s_default_log_buffer; + } + + void LogBuffer::CancelPush() { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Cancel any pending pushes. */ + if (m_push_ready_wait_count > 0) { + m_push_canceled = true; + m_cv_push_ready.Broadcast(); + } + } + + bool LogBuffer::PushImpl(const void *data, size_t size, bool blocking) { + /* Check pre-conditions. */ + AMS_ASSERT(size <= m_buffer_size); + AMS_ASSERT(data != nullptr || size == 0); + + /* Check that we have data to push. */ + if (size == 0) { + return true; + } + + /* Wait to be able to push. */ + u8 *dst; + { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Wait for enough space to be available. */ + while (size > m_buffer_size - m_push_buffer->m_stored_size) { + /* Only block if we're allowed to. */ + if (!blocking) { + return false; + } + + /* Wait for push to be ready. */ + { + ++m_push_ready_wait_count; + m_cv_push_ready.Wait(m_push_buffer_mutex); + --m_push_ready_wait_count; + } + + /* Check if push was canceled. */ + if (m_push_canceled) { + if (m_push_ready_wait_count == 0) { + m_push_canceled = false; + } + + return false; + } + } + + /* Set the destination. */ + dst = m_push_buffer->m_head + m_push_buffer->m_stored_size; + + /* Advance the push buffer. */ + m_push_buffer->m_stored_size += size; + ++m_push_buffer->m_reference_count; + } + + /* Copy the data to the push buffer. */ + std::memcpy(dst, data, size); + + /* Close our push buffer reference, and signal that we can flush. */ + { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* If there are no pending pushes, signal that we can flush. */ + if ((--m_push_buffer->m_reference_count) == 0) { + m_cv_flush_ready.Signal(); + } + } + + return true; + } + + bool LogBuffer::FlushImpl(bool blocking) { + /* Acquire exclusive access to the flush buffer. */ + std::scoped_lock lk(m_flush_buffer_mutex); + + /* If we don't have data to flush, wait for us to have data. */ + if (m_flush_buffer->m_stored_size == 0) { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Wait for there to be pushed data. */ + while (m_push_buffer->m_stored_size == 0 || m_push_buffer->m_reference_count != 0) { + /* Only block if we're allowed to. */ + if (!blocking) { + return false; + } + + /* Wait for us to be ready to flush. */ + m_cv_flush_ready.Wait(m_push_buffer_mutex); + } + + /* Swap the push buffer and the flush buffer pointers. */ + std::swap(m_push_buffer, m_flush_buffer); + + /* Signal that we can push. */ + m_cv_push_ready.Broadcast(); + } + + /* Flush any data. */ + if (!m_flush_function(m_flush_buffer->m_head, m_flush_buffer->m_stored_size)) { + return false; + } + + /* Reset the flush buffer. */ + m_flush_buffer->m_stored_size = 0; + + return true; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp new file mode 100644 index 000000000..e1167169c --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + class LogBuffer { + NON_COPYABLE(LogBuffer); + NON_MOVEABLE(LogBuffer); + private: + struct BufferInfo { + u8 *m_head; + size_t m_stored_size; + size_t m_reference_count; + }; + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + BufferInfo m_buffers[2]; + BufferInfo *m_push_buffer; + BufferInfo *m_flush_buffer; + size_t m_buffer_size; + FlushFunction m_flush_function; + os::SdkMutex m_push_buffer_mutex; + os::SdkMutex m_flush_buffer_mutex; + os::SdkConditionVariable m_cv_push_ready; + os::SdkConditionVariable m_cv_flush_ready; + bool m_push_canceled; + size_t m_push_ready_wait_count; + public: + constexpr explicit LogBuffer(void *buffer, size_t buffer_size, FlushFunction f) + : m_buffers{}, m_push_buffer(m_buffers + 0), m_flush_buffer(m_buffers + 1), + m_buffer_size(buffer_size / 2), m_flush_function(f), m_push_buffer_mutex{}, + m_flush_buffer_mutex{}, m_cv_push_ready{}, m_cv_flush_ready{}, + m_push_canceled(false), m_push_ready_wait_count(0) + { + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(buffer_size > 0); + AMS_ASSERT(f != nullptr); + + m_buffers[0].m_head = static_cast(buffer); + m_buffers[1].m_head = static_cast(buffer) + (buffer_size / 2); + } + + static LogBuffer &GetDefaultInstance(); + + bool Push(const void *data, size_t size) { return this->PushImpl(data, size, true); } + bool TryPush(const void *data, size_t size) { return this->PushImpl(data, size, false); } + + void CancelPush(); + + bool Flush() { return this->FlushImpl(true); } + bool TryFlush() { return this->FlushImpl(false); } + private: + bool PushImpl(const void *data, size_t size, bool blocking); + bool FlushImpl(bool blocking); + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp new file mode 100644 index 000000000..aebf8da92 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_getter.hpp" +#include "lm_log_getter_impl.hpp" + +namespace ams::lm::srv { + + extern bool g_is_logging_to_custom_sink; + + Result LogGetter::StartLogging() { + g_is_logging_to_custom_sink = true; + return ResultSuccess(); + } + + Result LogGetter::StopLogging() { + g_is_logging_to_custom_sink = false; + return ResultSuccess(); + } + + Result LogGetter::GetLog(const sf::OutAutoSelectBuffer &message, sf::Out out_size, sf::Out out_drop_count) { + /* Try to flush logs. */ + if (LogGetterImpl::GetBuffer().TryFlush()) { + *out_size = LogGetterImpl::GetLog(message.GetPointer(), message.GetSize(), out_drop_count.GetPointer()); + } else { + /* Otherwise, we got no data. */ + *out_size = 0; + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp new file mode 100644 index 000000000..340512611 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "../sf/lm_i_log_getter.hpp" + +namespace ams::lm::srv { + + class LogGetter { + public: + Result StartLogging(); + Result StopLogging(); + Result GetLog(const sf::OutAutoSelectBuffer &message, sf::Out out_size, sf::Out out_drop_count); + }; + static_assert(lm::IsILogGetter); + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp new file mode 100644 index 000000000..081004eed --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_getter_impl.hpp" + +namespace ams::lm::srv { + + CustomSinkBuffer &LogGetterImpl::GetBuffer() { + static constinit u8 s_buffer[32_KB]; + static constinit CustomSinkBuffer s_custom_sink_buffer(s_buffer, sizeof(s_buffer), FlushFunction); + return s_custom_sink_buffer; + } + + s64 LogGetterImpl::GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count) { + /* Check pre-condition. */ + AMS_ASSERT(buffer != nullptr); + + /* Determine how much we can get. */ + size_t min_size = s_buffer_size; + if (buffer_size < s_buffer_size) { + min_size = buffer_size; + IncreaseLogPacketDropCount(); + } + + /* Get the data. */ + std::memcpy(buffer, s_message, min_size); + + /* Set output drop count. */ + *out_drop_count = s_log_packet_drop_count; + s_log_packet_drop_count = 0; + + return min_size; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp new file mode 100644 index 000000000..876edaf0f --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "lm_custom_sink_buffer.hpp" + +namespace ams::lm::srv { + + class LogGetterImpl { + NON_COPYABLE(LogGetterImpl); + NON_MOVEABLE(LogGetterImpl); + private: + static constinit inline const u8 *s_message = nullptr; + static constinit inline size_t s_buffer_size = 0; + static constinit inline size_t s_log_packet_drop_count = 0; + private: + LogGetterImpl(); + public: + static CustomSinkBuffer &GetBuffer(); + static s64 GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count); + + static void IncreaseLogPacketDropCount() { ++s_log_packet_drop_count; } + private: + static bool FlushFunction(const u8 *buffer, size_t buffer_size) { + s_message = buffer; + s_buffer_size = buffer_size; + return true; + } + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp new file mode 100644 index 000000000..bb881e2c7 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_packet_parser.hpp" + +namespace ams::lm::srv { + + namespace { + + const u8 *ParseUleb128(u64 *out, const u8 *cur, const u8 *end) { + u64 value = 0; + size_t shift = 0; + while (cur < end && shift + 7 <= BITSIZEOF(u64)) { + value |= static_cast(*cur & 0x7F) << shift; + if ((*cur & 0x80) == 0) { + *out = value; + return cur; + } + + ++cur; + shift += 7; + } + + return end; + } + + } + + bool LogPacketParser::ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg) { + const u8 *cur = static_cast(buffer); + const u8 *end = cur + buffer_size; + + while (cur < end) { + /* Check that we can parse a header. */ + size_t remaining_size = end - cur; + if (remaining_size < sizeof(impl::LogPacketHeader)) { + AMS_ASSERT(remaining_size >= sizeof(impl::LogPacketHeader)); + return false; + } + + /* Get the header. */ + impl::LogPacketHeader header; + std::memcpy(std::addressof(header), cur, sizeof(header)); + + /* Advance past the header. */ + cur += sizeof(header); + + /* Check that we can parse the payload. */ + const auto payload_size = header.GetPayloadSize(); + remaining_size = end - cur; + if (remaining_size < payload_size) { + AMS_ASSERT(remaining_size >= payload_size); + return false; + } + + /* Invoke the callback. */ + if (!callback(header, cur, payload_size, arg)) { + return false; + } + + /* Advance. */ + cur += payload_size; + } + + /* Check that we parsed all the data. */ + AMS_ASSERT(cur == end); + + return true; + } + + bool LogPacketParser::ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg) { + const u8 *cur = static_cast(payload); + const u8 *end = cur + payload_size; + + while (cur < end) { + /* Get the key. */ + u64 key; + const auto key_last = ParseUleb128(std::addressof(key), cur, end); + if (key_last >= end) { + return false; + } + cur = key_last + 1; + + /* Get the size. */ + u64 size; + const auto size_last = ParseUleb128(std::addressof(size), cur, end); + if (size_last >= end) { + return false; + } + cur = size_last + 1; + + /* If we're in bounds, invoke the callback. */ + if (cur + size <= end && !callback(static_cast(key), cur, size, arg)) { + return false; + } + + cur += size; + } + + return true; + } + + bool LogPacketParser::FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size) { + /* Create context for iteration. */ + struct FindDataChunkContext { + const void *chunk; + size_t chunk_size; + bool found; + impl::LogDataChunkKey key; + } context = { nullptr, 0, false, key }; + + /* Find the chunk. */ + LogPacketParser::ParsePacket(buffer, buffer_size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool { + /* If the header isn't a header packet, continue. */ + if (!header.IsHead()) { + return true; + } + + return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey cur_key, const void *chunk, size_t chunk_size, void *arg) -> bool { + /* Get the context. */ + auto *context = static_cast(arg); + + /* Check if we found the desired key. */ + if (context->key == cur_key) { + context->chunk = chunk; + context->chunk_size = chunk_size; + context->found = true; + return false; + } + + /* Otherwise, continue. */ + return true; + }, arg); + }, std::addressof(context)); + + /* Write the chunk we found. */ + if (context.found) { + *out = context.chunk; + *out_size = context.chunk_size; + return true; + } else { + return false; + } + } + + size_t LogPacketParser::ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size > 0); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(buffer_size > 0); + + /* Find the relevant data chunk. */ + const void *chunk; + size_t chunk_size; + const bool found = LogPacketParser::FindDataChunk(std::addressof(chunk), std::addressof(chunk_size), impl::LogDataChunkKey_ModuleName, buffer, buffer_size); + if (!found || chunk_size == 0) { + dst[0] = '\x00'; + return 0; + } + + /* Copy as much of the module name as we can. */ + const size_t copy_size = std::min(chunk_size, dst_size - 1); + std::memcpy(dst, chunk, copy_size); + dst[copy_size] = '\x00'; + + return chunk_size; + } + + void LogPacketParser::ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg) { + /* Declare context for inter-call storage. */ + struct PreviousPacketContext { + u64 process_id; + u64 thread_id; + size_t carry_size; + bool ends_with_text_log; + }; + static constinit PreviousPacketContext s_previous_packet_context = {}; + + /* Get the packet header. */ + auto *header = static_cast(buffer); + auto *payload = static_cast(buffer) + impl::LogPacketHeaderSize; + auto payload_size = buffer_size - impl::LogPacketHeaderSize; + + /* Determine if the packet is a continuation. */ + const bool is_continuation = !header->IsHead() && header->GetProcessId() == s_previous_packet_context.process_id && header->GetThreadId() == s_previous_packet_context.thread_id; + + /* Require that the packet be a header or a continuation. */ + if (!header->IsHead() && !is_continuation) { + return; + } + + /* If the packet is a continuation, handle the leftover data. */ + if (is_continuation && s_previous_packet_context.carry_size > 0) { + /* Invoke the callback on what we can. */ + const size_t sendable = std::min(s_previous_packet_context.carry_size, payload_size); + if (s_previous_packet_context.ends_with_text_log) { + callback(payload, sendable, arg); + } + + /* Advance the leftover data. */ + s_previous_packet_context.carry_size -= sendable; + payload += sendable; + payload_size -= sendable; + } + + /* If we've sent the whole payload, we're done. */ + if (payload_size == 0) { + return; + } + + /* Parse the payload. */ + size_t carry_size = 0; + bool ends_with_text_log = false; + { + const u8 *cur = reinterpret_cast(payload); + const u8 *end = cur + payload_size; + + while (cur < end) { + /* Get the key. */ + u64 key; + const auto key_last = ParseUleb128(std::addressof(key), cur, end); + if (key_last >= end) { + break; + } + cur = key_last + 1; + + /* Get the size. */ + u64 size; + const auto size_last = ParseUleb128(std::addressof(size), cur, end); + if (size_last >= end) { + break; + } + cur = size_last + 1; + + /* Process the data. */ + const bool is_text_log = static_cast(key) == impl::LogDataChunkKey_TextLog; + const size_t remaining = end - cur; + + if (size >= remaining) { + carry_size = size - remaining; + ends_with_text_log = is_text_log; + } + + if (is_text_log) { + const size_t sendable_size = std::min(size, remaining); + callback(reinterpret_cast(cur), sendable_size, arg); + } + + cur += size; + } + } + + /* If the packet isn't a tail packet, update the context. */ + if (!header->IsTail()) { + s_previous_packet_context.process_id = header->GetProcessId(); + s_previous_packet_context.thread_id = header->GetThreadId(); + s_previous_packet_context.carry_size = carry_size; + s_previous_packet_context.ends_with_text_log = ends_with_text_log; + } + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp new file mode 100644 index 000000000..f6f1e6ab4 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "../impl/lm_log_data_chunk.hpp" +#include "../impl/lm_log_packet_header.hpp" + +namespace ams::lm::srv { + + class LogPacketParser { + public: + using ParsePacketCallback = bool (*)(const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg); + using ParseDataChunkCallback = bool (*)(impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg); + using ParseTextLogCallback = void (*)(const char *txt, size_t size, void *arg); + public: + static bool ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg); + static bool ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg); + + static bool FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size); + + static size_t ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size); + + static void ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg); + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp new file mode 100644 index 000000000..d6ace0833 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_server_proxy.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline const char PortName[] = "iywys@$LogManager"; + constexpr inline const int HtcsSessionCountMax = 2; + constexpr inline const TimeSpan PollingInterval = TimeSpan::FromSeconds(1); + + constinit u8 g_htcs_heap_buffer[2_KB]; + + constexpr inline const int InvalidHtcsSocket = -1; + + constexpr ALWAYS_INLINE bool IsValidHtcsSocket(int socket) { + return socket >= 0; + } + + static_assert(!IsValidHtcsSocket(InvalidHtcsSocket)); + + bool IsHtcEnabled() { + u8 enable_htc = 0; + settings::fwdbg::GetSettingsItemValue(&enable_htc, sizeof(enable_htc), "atmosphere", "enable_htc"); + return enable_htc != 0; + } + + } + + LogServerProxy::LogServerProxy() : m_cv_connected(), m_stop_event(os::EventClearMode_ManualClear), m_connection_mutex(), m_observer_mutex(), m_server_socket(InvalidHtcsSocket), m_client_socket(InvalidHtcsSocket), m_connection_observer(nullptr) { + /* ... */ + } + + void LogServerProxy::Start() { + /* Create thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), [](void *_this) { static_cast(_this)->LoopAuto(); }, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, HtcsConnection))); + + /* Set thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, HtcsConnection)); + + /* Clear stop event. */ + m_stop_event.Clear(); + + /* Start thread. */ + os::StartThread(std::addressof(m_thread)); + } + + void LogServerProxy::Stop() { + /* Signal to connection thread to stop. */ + m_stop_event.Signal(); + + /* Close client socket. */ + if (const int client_socket = m_client_socket; client_socket >= 0) { + htcs::Close(client_socket); + } + + /* Close server socket. */ + if (const int server_socket = m_server_socket; server_socket >= 0) { + htcs::Close(server_socket); + } + + /* Wait for the connection thread to exit. */ + os::WaitThread(std::addressof(m_thread)); + os::DestroyThread(std::addressof(m_thread)); + } + + bool LogServerProxy::IsConnected() { + /* Return whether there's a valid client socket. */ + return IsValidHtcsSocket(m_client_socket); + } + + void LogServerProxy::SetConnectionObserver(ConnectionObserver observer) { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_observer_mutex); + + /* Set the observer. */ + m_connection_observer = observer; + } + + bool LogServerProxy::Send(const u8 *data, size_t size) { + /* Send as much data as we can, until it's all send. */ + size_t offset = 0; + while (this->IsConnected() && offset < size) { + /* Try to send the remaining data. */ + if (const auto result = htcs::Send(m_client_socket, data + offset, size - offset, 0); result >= 0) { + /* Advance. */ + offset += static_cast(result); + } else { + /* We failed to send data, shutdown the socket. */ + htcs::Shutdown(m_client_socket, htcs::HTCS_SHUT_RDWR); + + /* Wait a second, before returning to the caller. */ + os::SleepThread(TimeSpan::FromSeconds(1)); + + return false; + } + } + + /* Return whether we sent all the data. */ + AMS_ASSERT(offset <= size); + return offset == size; + } + + void LogServerProxy::LoopAuto() { + /* If we're not using htcs, there's nothing to do. */ + if (!IsHtcEnabled()) { + return; + } + + /* Check that we have enough working memory. */ + const auto working_memory_size = htcs::GetWorkingMemorySize(HtcsSessionCountMax); + AMS_ABORT_UNLESS(working_memory_size <= sizeof(g_htcs_heap_buffer)); + + /* Initialize htcs for the duration that we loop. */ + htcs::InitializeForDisableDisconnectionEmulation(g_htcs_heap_buffer, working_memory_size); + ON_SCOPE_EXIT { htcs::Finalize(); }; + + /* Setup socket address. */ + htcs::SockAddrHtcs server_address; + server_address.family = htcs::HTCS_AF_HTCS; + server_address.peer_name = htcs::GetPeerNameAny(); + std::strcpy(server_address.port_name.name, PortName); + + /* Manage htcs connections until a stop is requested. */ + do { + /* Create a server socket. */ + const auto server_socket = htcs::Socket(); + if (!IsValidHtcsSocket(server_socket)) { + continue; + } + m_server_socket = server_socket; + + /* Ensure we cleanup the server socket when done. */ + ON_SCOPE_EXIT { + htcs::Close(server_socket); + m_server_socket = InvalidHtcsSocket; + }; + + /* Bind to the server socket. */ + if (htcs::Bind(server_socket, std::addressof(server_address)) != 0) { + continue; + } + + /* Listen on the server socket. */ + if (htcs::Listen(server_socket, 0) != 0) { + continue; + } + + /* Loop on clients, until we're asked to stop. */ + while (!m_stop_event.TryWait()) { + /* Accept a client socket. */ + const auto client_socket = htcs::Accept(server_socket, nullptr); + if (!IsValidHtcsSocket(client_socket)) { + break; + } + m_client_socket = client_socket; + + /* Note that we're connected. */ + this->InvokeConnectionObserver(true); + this->SignalConnection(); + + /* Ensure we cleanup the client socket when done. */ + ON_SCOPE_EXIT { + htcs::Close(client_socket); + m_client_socket = InvalidHtcsSocket; + + this->InvokeConnectionObserver(false); + }; + + /* Receive data (and do nothing with it), so long as we're connected. */ + u8 v; + while (htcs::Recv(client_socket, std::addressof(v), sizeof(v), 0) == sizeof(v)) { /* ... */ } + } + } while (!m_stop_event.TimedWait(PollingInterval)); + } + + void LogServerProxy::SignalConnection() { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_connection_mutex); + + /* Broadcast to our connected cv. */ + m_cv_connected.Broadcast(); + } + + void LogServerProxy::InvokeConnectionObserver(bool connected) { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_observer_mutex); + + /* If we have an observer, observe the connection state. */ + if (m_connection_observer) { + m_connection_observer(connected); + } + } + + void StartLogServerProxy() { + LogServerProxy::GetInstance().Start(); + } + + void StopLogServerProxy() { + LogServerProxy::GetInstance().Stop(); + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp new file mode 100644 index 000000000..0422961e8 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + class LogServerProxy { + AMS_SINGLETON_TRAITS(LogServerProxy); + public: + using ConnectionObserver = void (*)(bool connected); + private: + alignas(os::ThreadStackAlignment) u8 m_thread_stack[4_KB]; + os::ThreadType m_thread; + os::SdkConditionVariable m_cv_connected; + os::Event m_stop_event; + os::SdkMutex m_connection_mutex; + os::SdkMutex m_observer_mutex; + std::atomic m_server_socket; + std::atomic m_client_socket; + ConnectionObserver m_connection_observer; + public: + void Start(); + void Stop(); + + bool IsConnected(); + + void SetConnectionObserver(ConnectionObserver observer); + + bool Send(const u8 *data, size_t size); + private: + void LoopAuto(); + void SignalConnection(); + void InvokeConnectionObserver(bool connected); + }; + + void StartLogServerProxy(); + void StopLogServerProxy(); + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp b/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp new file mode 100644 index 000000000..9988f9b73 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_log_service_impl.hpp" +#include "lm_logger_impl.hpp" + +namespace ams::lm::srv { + + namespace { + + struct LoggerImplAllocatorTag; + using LoggerAllocator = ams::sf::ExpHeapStaticAllocator<3_KB, LoggerImplAllocatorTag>; + using LoggerObjectFactory = ams::sf::ObjectFactory; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + LoggerAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + Result LogServiceImpl::OpenLogger(sf::Out> out, const sf::ClientProcessId &client_process_id) { + /* Open logger. */ + out.SetValue(LoggerObjectFactory::CreateSharedEmplaced<::ams::lm::ILogger, LoggerImpl>(this, client_process_id.GetValue())); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp b/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp new file mode 100644 index 000000000..5be95e1b1 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "../sf/lm_i_log_service.hpp" + +namespace ams::lm::srv { + + class LogServiceImpl { + public: + Result OpenLogger(sf::Out> out, const sf::ClientProcessId &client_process_id); + }; + static_assert(lm::IsILogService); + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp b/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp new file mode 100644 index 000000000..c39abb6b6 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_logger_impl.hpp" +#include "lm_event_log_transmitter.hpp" +#include "lm_log_buffer.hpp" +#include "lm_log_packet_parser.hpp" +#include "lm_log_getter_impl.hpp" +#include "../impl/lm_log_packet_header.hpp" + +namespace ams::lm::srv { + + bool IsFlushAvailable(); + + bool g_is_logging_to_custom_sink = false; + + namespace { + + constinit u32 g_log_destination = lm::LogDestination_TargetManager; + + bool SetProcessId(const sf::InAutoSelectBuffer &message, u64 process_id) { + /* Check the message. */ + AMS_ASSERT(util::IsAligned(reinterpret_cast(message.GetPointer()), alignof(impl::LogPacketHeader))); + + /* Get a modifiable copy of the header. */ + auto *header = const_cast(reinterpret_cast(message.GetPointer())); + + /* Check that the message size is correct. */ + if (impl::LogPacketHeaderSize + header->GetPayloadSize() != message.GetSize()) { + return false; + } + + /* Set the header's process id. */ + header->SetProcessId(process_id); + + return true; + } + + void PutLogToTargetManager(const sf::InAutoSelectBuffer &message) { + /* Try to push the message. */ + bool success; + if (IsFlushAvailable()) { + success = LogBuffer::GetDefaultInstance().Push(message.GetPointer(), message.GetSize()); + } else { + success = LogBuffer::GetDefaultInstance().TryPush(message.GetPointer(), message.GetSize()); + } + + /* If we fail, increment dropped packet count. */ + if (!success) { + EventLogTransmitter::GetDefaultInstance().IncreaseLogPacketDropCount(); + } + } + + void PutLogToUart(const sf::InAutoSelectBuffer &message) { + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + { + /* Get header. */ + auto *data = message.GetPointer(); + auto data_size = message.GetSize(); + const auto *header = reinterpret_cast(data); + + /* Get the module name. */ + char module_name[0x10] = {}; + LogPacketParser::ParseModuleName(module_name, sizeof(module_name), data, data_size); + + /* Create log metadata. */ + const diag::LogMetaData log_meta = { + .module_name = module_name, + .severity = static_cast(header->GetSeverity()), + .verbosity = header->GetVerbosity(), + }; + + LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *arg) { + /* Get metadata. */ + const auto &meta = *static_cast(arg); + + /* Put the message to uart. */ + diag::impl::PutImpl(meta, txt, size); + }, const_cast(std::addressof(log_meta))); + } + #endif + } + + void PutLogToCustomSink(const sf::InAutoSelectBuffer &message) { + LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *) { + /* Try to push the message. */ + if (!LogGetterImpl::GetBuffer().TryPush(txt, size)) { + LogGetterImpl::IncreaseLogPacketDropCount(); + } + }, nullptr); + } + + } + + LoggerImpl::LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id) : m_parent(parent), m_process_id(process_id.value) { + /* Log start of session for process. */ + EventLogTransmitter::GetDefaultInstance().PushLogSessionBegin(m_process_id); + } + + LoggerImpl::~LoggerImpl() { + /* Log end of session for process. */ + EventLogTransmitter::GetDefaultInstance().PushLogSessionEnd(m_process_id); + } + + Result LoggerImpl::Log(const sf::InAutoSelectBuffer &message) { + /* Try to set the log process id. */ + /* NOTE: Nintendo succeeds here, for whatever purpose, so we will as well. */ + R_UNLESS(SetProcessId(message, m_process_id), ResultSuccess()); + + /* If we should, log to target manager. */ + if (g_log_destination & lm::LogDestination_TargetManager) { + PutLogToTargetManager(message); + } + + /* If we should, log to uart. */ + if ((g_log_destination & lm::LogDestination_Uart) || (IsFlushAvailable() && (g_log_destination & lm::LogDestination_UartIfSleep))) { + PutLogToUart(message); + } + + /* If we should, log to custom sink. */ + if (g_is_logging_to_custom_sink) { + PutLogToCustomSink(message); + } + + return ResultSuccess(); + } + + Result LoggerImpl::SetDestination(u32 destination) { + /* Set the log destination. */ + g_log_destination = destination; + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp b/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp new file mode 100644 index 000000000..83ebfa1a3 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "lm_log_service_impl.hpp" + +namespace ams::lm::srv { + + class LoggerImpl { + private: + LogServiceImpl *m_parent; + u64 m_process_id; + public: + explicit LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id); + ~LoggerImpl(); + public: + Result Log(const sf::InAutoSelectBuffer &message); + Result SetDestination(u32 destination); + }; + static_assert(lm::IsILogger); + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp b/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp new file mode 100644 index 000000000..e31cfadc1 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_sd_card_logger.hpp" +#include "lm_time_util.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr const char SdCardMountName[] = "sdcard"; + constexpr const char LogFileExtension[] = "nxbinlog"; + + constexpr const char SettingName[] = "lm"; + constexpr const char SettingKeyLoggingEnabled[] = "enable_sd_card_logging"; + constexpr const char SettingKeyOutputDirectory[] = "sd_card_log_output_directory"; + + constexpr inline size_t LogFileHeaderSize = 8; + constexpr inline u32 LogFileHeaderMagic = util::ReverseFourCC<'p','h','p','h'>::Code; + constexpr inline u8 LogFileHeaderVersion = 1; + + struct LogFileHeader { + u32 magic; + u8 version; + u8 reserved[3]; + }; + static_assert(sizeof(LogFileHeader) == LogFileHeaderSize); + + constinit os::SdkMutex g_sd_card_logging_enabled_mutex; + constinit bool g_determined_sd_card_logging_enabled = false; + constinit bool g_sd_card_logging_enabled = false; + + constinit os::SdkMutex g_sd_card_detection_event_mutex; + constinit bool g_sd_card_inserted_cache = false; + constinit bool g_sd_card_detection_event_initialized = false; + constinit std::unique_ptr g_sd_card_detection_event_notifier; + os::SystemEvent g_sd_card_detection_event; + + bool GetSdCardLoggingEnabledImpl() { + bool enabled; + const auto size = settings::fwdbg::GetSettingsItemValue(std::addressof(enabled), sizeof(enabled), SettingName, SettingKeyLoggingEnabled); + if (size != sizeof(enabled)) { + AMS_ASSERT(size == sizeof(enabled)); + return false; + } + + return enabled; + } + + bool GetSdCardLoggingEnabled() { + if (AMS_UNLIKELY(!g_determined_sd_card_logging_enabled)) { + std::scoped_lock lk(g_sd_card_logging_enabled_mutex); + + if (AMS_LIKELY(!g_determined_sd_card_logging_enabled)) { + g_sd_card_logging_enabled = GetSdCardLoggingEnabledImpl(); + g_determined_sd_card_logging_enabled = true; + } + } + + return g_sd_card_logging_enabled; + } + + void EnsureSdCardDetectionEventInitialized() { + if (AMS_UNLIKELY(!g_sd_card_detection_event_initialized)) { + std::scoped_lock lk(g_sd_card_detection_event_mutex); + + if (AMS_LIKELY(!g_sd_card_detection_event_initialized)) { + /* Create SD card detection event notifier. */ + R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier))); + R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear)); + + /* Get initial inserted value. */ + g_sd_card_inserted_cache = fs::IsSdCardInserted(); + + g_sd_card_detection_event_initialized = true; + } + } + } + + void GetSdCardStatus(bool *out_inserted, bool *out_status_changed) { + /* Ensure that we can detect the sd card. */ + EnsureSdCardDetectionEventInitialized(); + + /* Check if there's a detection event. */ + const bool status_changed = g_sd_card_detection_event.TryWait(); + if (status_changed) { + g_sd_card_detection_event.Clear(); + + /* Update the inserted cache. */ + g_sd_card_inserted_cache = fs::IsSdCardInserted(); + } + + *out_inserted = g_sd_card_inserted_cache; + *out_status_changed = status_changed; + } + + bool GetSdCardLogOutputDirectory(char *dst, size_t size) { + /* Get the output directory size. */ + const auto value_size = settings::fwdbg::GetSettingsItemValueSize(SettingName, SettingKeyOutputDirectory); + if (value_size > size) { + AMS_ASSERT(value_size <= size); + return false; + } + + /* Get the output directory. */ + const auto read_size = settings::fwdbg::GetSettingsItemValue(dst, size, SettingName, SettingKeyOutputDirectory); + AMS_ASSERT(read_size == value_size); + + return read_size == value_size; + } + + bool EnsureLogDirectory(const char *dir) { + /* Generate the log directory path. */ + char path[0x80]; + const size_t len = util::SNPrintf(path, sizeof(path), "%s:/%s", SdCardMountName, dir); + if (len >= sizeof(path)) { + AMS_ASSERT(len < sizeof(path)); + return false; + } + + /* Ensure the directory. */ + /* NOTE: Nintendo does not perform recusrive directory ensure, only a single CreateDirectory level. */ + return R_SUCCEEDED(fs::EnsureDirectoryRecursively(path)); + } + + bool MakeLogFilePathWithoutExtension(char *dst, size_t size, const char *dir) { + /* Get the current time. */ + const auto cur_time = time::ToCalendarTimeInUtc(lm::srv::GetCurrentTime()); + + /* Get the device serial number. */ + settings::system::SerialNumber serial_number; + settings::system::GetSerialNumber(std::addressof(serial_number)); + + /* Print the path. */ + const size_t len = util::SNPrintf(dst, size, "%s:/%s/%s_%04d%02d%02d%02d%02d%02d", SdCardMountName, dir, serial_number.str, cur_time.year, cur_time.month, cur_time.day, cur_time.hour, cur_time.minute, cur_time.second); + + AMS_ASSERT(len < size); + return len < size; + } + + bool GenerateLogFile(char *dst, size_t size, const char *dir) { + /* Generate the log file path. */ + char path_without_ext[0x80]; + if (!MakeLogFilePathWithoutExtension(path_without_ext, sizeof(path_without_ext), dir)) { + return false; + } + + /* Try to find an available log file path. */ + constexpr auto MaximumLogIndex = 99; + for (auto i = 1; i <= MaximumLogIndex; ++i) { + /* Print the current log file path. */ + const size_t len = (i == 1) ? util::SNPrintf(dst, size, "%s.%s", path_without_ext, LogFileExtension) : util::SNPrintf(dst, size, "%s_%d.%s", path_without_ext, i, LogFileExtension); + if (len >= size) { + AMS_ASSERT(len < size); + return false; + } + + /* Try to create the log file. */ + const auto result = fs::CreateFile(dst, 0); + if (R_SUCCEEDED(result)) { + return true; + } else if (fs::ResultPathAlreadyExists::Includes(result)) { + /* The log file already exists, so try the next index. */ + continue; + } else { + /* We failed to create a log file. */ + return false; + } + } + + /* We ran out of log file indices. */ + return false; + } + + Result WriteLogFileHeaderImpl(const char *path) { + /* Open the log file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the log file header. */ + const LogFileHeader header = { + .magic = LogFileHeaderMagic, + .version = LogFileHeaderVersion + }; + + R_TRY(fs::WriteFile(file, 0, std::addressof(header), sizeof(header), fs::WriteOption::Flush)); + + return ResultSuccess(); + } + + bool WriteLogFileHeader(const char *path) { + return R_SUCCEEDED(WriteLogFileHeaderImpl(path)); + } + + Result WriteLogFileBodyImpl(const char *path, s64 offset, const u8 *data, size_t size) { + /* Open the log file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the data. */ + R_TRY(fs::WriteFile(file, offset, data, size, fs::WriteOption::Flush)); + + return ResultSuccess(); + } + + bool WriteLogFileBody(const char *path, s64 offset, const u8 *data, size_t size) { + return R_SUCCEEDED(WriteLogFileBodyImpl(path, offset, data, size)); + } + + } + + SdCardLogger::SdCardLogger() : m_logging_observer_mutex(), m_is_enabled(false), m_is_sd_card_mounted(false), m_is_sd_card_status_unknown(false), m_log_file_offset(0), m_logging_observer(nullptr) { + /* ... */ + } + + bool SdCardLogger::GetEnabled() const { + return m_is_enabled; + } + + void SdCardLogger::SetEnabled(bool enabled) { + /* Only update if we need to. */ + if (m_is_enabled == enabled) { + return; + } + + /* Set enabled. */ + m_is_enabled = enabled; + + /* Invoke our observer. */ + std::scoped_lock lk(m_logging_observer_mutex); + + if (m_logging_observer) { + m_logging_observer(enabled); + } + } + + void SdCardLogger::SetLoggingObserver(LoggingObserver observer) { + std::scoped_lock lk(m_logging_observer_mutex); + + m_logging_observer = observer; + } + + bool SdCardLogger::Initialize() { + /* If we're already enabled, nothing to do. */ + if (this->GetEnabled()) { + return true; + } + + /* Get the sd card status. */ + bool inserted = false, status_changed = false; + GetSdCardStatus(std::addressof(inserted), std::addressof(status_changed)); + + /* Update whether status is known. */ + if (status_changed) { + m_is_sd_card_status_unknown = false; + } + + /* If the SD isn't inserted, we can't initialize. */ + if (!inserted) { + return false; + } + + /* If the status is unknown, we can't initialize. */ + if (m_is_sd_card_status_unknown) { + return false; + } + + /* Mount the SD card. */ + if (R_FAILED(fs::MountSdCard(SdCardMountName))) { + return false; + } + + /* Note that the SD card is mounted. */ + m_is_sd_card_mounted = true; + + /* Get the output directory. */ + char output_dir[0x80]; + if (!GetSdCardLogOutputDirectory(output_dir, sizeof(output_dir))) { + return false; + } + + /* Ensure the output directory exists. */ + if (!EnsureLogDirectory(output_dir)) { + return false; + } + + /* Ensure that a log file exists for us to write to. */ + if (!GenerateLogFile(m_log_file_path, sizeof(m_log_file_path), output_dir)) { + return false; + } + + /* Write the log file header. */ + if (!WriteLogFileHeader(m_log_file_path)) { + return false; + } + + /* Set our initial offset. */ + m_log_file_offset = LogFileHeaderSize; + + return true; + } + + void SdCardLogger::Finalize() { + this->SetEnabled(false); + if (m_is_sd_card_mounted) { + fs::Unmount(SdCardMountName); + m_is_sd_card_mounted = false; + } + } + + bool SdCardLogger::Write(const u8 *data, size_t size) { + /* Only write if sd card logging is enabled. */ + if (!GetSdCardLoggingEnabled()) { + return false; + } + + /* Ensure we keep our pre and post-conditions in check. */ + bool success = false; + ON_SCOPE_EXIT { + if (!success && m_is_sd_card_mounted) { + fs::Unmount(SdCardMountName); + m_is_sd_card_mounted = false; + m_is_sd_card_status_unknown = true; + } + this->SetEnabled(success); + }; + + /* Try to initialize. */ + if (!this->Initialize()) { + return false; + } + + /* Try to write the log file. */ + if (!WriteLogFileBody(m_log_file_path, m_log_file_offset, data, size)) { + return false; + } + + /* Advance. */ + m_log_file_offset += size; + + /* We succeeded. */ + success = true; + return true; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp b/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp new file mode 100644 index 000000000..dbf0cd549 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + class SdCardLogger { + AMS_SINGLETON_TRAITS(SdCardLogger); + public: + using LoggingObserver = void (*)(bool available); + private: + os::SdkMutex m_logging_observer_mutex; + bool m_is_enabled; + bool m_is_sd_card_mounted; + bool m_is_sd_card_status_unknown; + char m_log_file_path[0x80]; + s64 m_log_file_offset; + LoggingObserver m_logging_observer; + public: + void Finalize(); + + void SetLoggingObserver(LoggingObserver observer); + + bool Write(const u8 *data, size_t size); + private: + bool GetEnabled() const; + void SetEnabled(bool enabled); + + bool Initialize(); + }; + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp b/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp new file mode 100644 index 000000000..056d32911 --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "lm_time_util.hpp" + +namespace ams::lm::srv { + + namespace { + + constinit std::atomic_bool g_is_time_invalid = false; + + constinit time::PosixTime InvalidPosixTime = { .value = 0 }; + + constexpr bool IsValidPosixTime(time::PosixTime time) { + return time.value > 0; + } + + void EnsureTimeInitialized() { + static constinit os::SdkMutex g_time_initialized_mutex; + static constinit bool g_time_initialized = false; + + if (AMS_UNLIKELY(!g_time_initialized)) { + std::scoped_lock lk(g_time_initialized_mutex); + + if (AMS_LIKELY(!g_time_initialized)) { + R_ABORT_UNLESS(time::Initialize()); + g_time_initialized = true; + } + } + } + + } + + time::PosixTime GetCurrentTime() { + /* Ensure that we can use time services. */ + EnsureTimeInitialized(); + + /* Repeatedly try to get a valid time. */ + for (auto wait_seconds = 1; wait_seconds <= 8; wait_seconds *= 2) { + /* Get the standard user system clock time. */ + time::PosixTime current_time{}; + if (R_FAILED(time::StandardUserSystemClock::GetCurrentTime(std::addressof(current_time)))) { + return InvalidPosixTime; + } + + /* If the time is valid, return it. */ + if (IsValidPosixTime(current_time)) { + return current_time; + } + + /* Check if we've failed to get a time in the past. */ + if (g_is_time_invalid) { + return InvalidPosixTime; + } + + /* Wait a bit before trying again. */ + os::SleepThread(TimeSpan::FromSeconds(wait_seconds)); + } + + /* We failed to get a valid time. */ + g_is_time_invalid = true; + return InvalidPosixTime; + } + +} diff --git a/libraries/libstratosphere/source/lm/srv/lm_time_util.hpp b/libraries/libstratosphere/source/lm/srv/lm_time_util.hpp new file mode 100644 index 000000000..b1385841c --- /dev/null +++ b/libraries/libstratosphere/source/lm/srv/lm_time_util.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::lm::srv { + + time::PosixTime GetCurrentTime(); + +} diff --git a/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.cpp b/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.cpp new file mode 100644 index 000000000..f2454f774 --- /dev/null +++ b/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::ro::impl { + + namespace { + + bool SearchSegmentHead(uintptr_t *out, uintptr_t target, svc::MemoryState state) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + bool success = false; + + for (uintptr_t cur = target; cur <= target; cur = mem_info.addr - 1) { + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur)); + + if (mem_info.state != state || mem_info.perm != svc::MemoryPermission_ReadExecute) { + break; + } + + *out = mem_info.addr; + success = true; + } + + return success; + } + + bool SearchSegmentTail(uintptr_t *out, uintptr_t target, svc::MemoryState state) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + bool success = false; + + for (uintptr_t cur = target; cur >= target; cur = mem_info.addr + mem_info.size) { + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur)); + + if (mem_info.state != state) { + break; + } + + *out = mem_info.addr + mem_info.size - 1; + success = true; + } + + return success; + } + + bool QueryModule(uintptr_t *out_address, size_t *out_size, uintptr_t pc) { + /* Query the program counter. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), pc)); + + /* Check memory info. */ + if (mem_info.perm != svc::MemoryPermission_ReadExecute) { + return false; + } + + if (mem_info.state != svc::MemoryState_Code && mem_info.state != svc::MemoryState_AliasCode) { + return false; + } + + /* Find head/tail. */ + uintptr_t head = 0, tail = 0; + AMS_ABORT_UNLESS(SearchSegmentHead(std::addressof(head), pc, mem_info.state)); + AMS_ABORT_UNLESS(SearchSegmentTail(std::addressof(tail), pc, mem_info.state)); + AMS_ABORT_UNLESS(SearchSegmentTail(std::addressof(tail), tail + 1, mem_info.state == svc::MemoryState_Code ? svc::MemoryState_CodeData : svc::MemoryState_AliasCodeData)); + + /* Set output. */ + *out_address = head; + *out_size = tail + 1 - head; + + return true; + } + + } + + bool GetExceptionInfo(ExceptionInfo *out, uintptr_t pc) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Find the module. */ + if (!QueryModule(std::addressof(out->module_address), std::addressof(out->module_size), pc)) { + return false; + } + + /* Validate the module. */ + rocrt::ModuleHeaderLocation *loc = reinterpret_cast(out->module_address); + rocrt::ModuleHeader *header = rocrt::GetModuleHeader(loc); + AMS_ABORT_UNLESS(header->signature == rocrt::ModuleHeaderVersion); + + /* Set the exception info. */ + out->info_offset = loc->header_offset + header->exception_info_start_offset; + out->info_size = header->exception_info_end_offset - header->exception_info_start_offset; + + return true; + } + +} diff --git a/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp b/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp index 6a6435d4a..059cae467 100644 --- a/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp +++ b/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp @@ -17,6 +17,140 @@ namespace ams::time::impl::util { + namespace { + + constexpr inline const int DaysPerMonth[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + static_assert(std::accumulate(std::begin(DaysPerMonth), std::end(DaysPerMonth), 0) == 365); + + constexpr inline const std::array SumDaysPerMonth = [] { + std::array days = {}; + for (size_t i = 1; i < days.size(); ++i) { + days[i] = days[i - 1] + DaysPerMonth[i - 1]; + } + return days; + }(); + static_assert(SumDaysPerMonth[ 0] == 0); + static_assert(SumDaysPerMonth[11] + DaysPerMonth[11] == 365); + + constexpr bool IsLeapYearImpl(int year) { + if ((year % 400) == 0) { + return true; + } else if ((year % 100) == 0) { + return false; + } else if ((year % 4) == 0) { + return true; + } else { + return false; + } + } + + constexpr int DateToDaysImpl(int year, int month, int day) { + /* Lightly validate input. */ + AMS_ASSERT(year > 0); + AMS_ASSERT(month > 0); + AMS_ASSERT(day > 0); + + /* Adjust months within range. */ + year += month / 12; + month %= 12; + if (month == 0) { + month = 12; + } + AMS_ASSERT(1 <= month && month <= 12); + + /* Calculate days. */ + int res = (year - 1) * 365; + res += (year / 4) - (year / 100) + (year / 400); + res += SumDaysPerMonth[month - 1] + day; + + /* Subtract leap day, if it hasn't happened yet. */ + if (month < 3 && IsLeapYearImpl(year)) { + res -= 1; + } + + /* Subtract the current day. */ + res -= 1; + + return res; + } + + constexpr void DaysToDateImpl(int *out_year, int *out_month, int *out_day, int days) { + /* Lightly validate input. */ + AMS_ASSERT(days > 0); + + /* Declare unit conversion factors. */ + constexpr int DaysPerYear = 365; + constexpr int DaysPerFourYears = DaysPerYear * 4 + 1; + constexpr int DaysPerCentury = DaysPerFourYears * 25 - 1; + constexpr int DaysPerFourCenturies = DaysPerCentury * 4 + 1; + + /* Adjust date. */ + days -= 59; + days += 365; + + /* Determine various units. */ + int four_centuries = days / DaysPerFourCenturies; + int four_centuries_rem = days % DaysPerFourCenturies; + if (four_centuries_rem < 0) { + four_centuries_rem += DaysPerFourCenturies; + --four_centuries; + } + + int centuries = four_centuries_rem / DaysPerCentury; + int centuries_rem = four_centuries_rem % DaysPerCentury; + + int four_years = centuries_rem / DaysPerFourYears; + int four_years_rem = centuries_rem % DaysPerFourYears; + + int years = four_years_rem / DaysPerYear; + int years_rem = four_years_rem % DaysPerYear; + + /* Adjust into range. */ + int year = 400 * four_centuries + 100 * centuries + 4 * four_years + years; + int month = (5 * years_rem + 2) / 153; + int day = years_rem - (153 * month + 2) / 5 + 1; + + /* Adjust in case we fell into a pathological case. */ + if (years == 4 || centuries == 4) { + month = 11; + day = 29; + year -= 1; + } + + /* Adjust month. */ + if (month <= 9) { + month += 3; + } else { + month -= 9; + year += 1; + } + + /* Set output. */ + if (out_year) { + *out_year = year; + } + if (out_month) { + *out_month = month; + } + if (out_day) { + *out_day = day; + } + } + + constexpr inline int EpochDays = DateToDaysImpl(1970, 1, 1); + + static_assert([]() -> bool { + int year{}, month{}, day{}; + + DaysToDateImpl(std::addressof(year), std::addressof(month), std::addressof(day), EpochDays); + + return year == 1970 && month == 1 && day == 1; + }()); + + } + Result GetSpanBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to) { AMS_ASSERT(out != nullptr); @@ -31,4 +165,97 @@ namespace ams::time::impl::util { return ResultSuccess(); } + bool IsValidDate(int year, int month, int day) { + return 1 <= year && 1 <= month && month <= 12 && 1 <= day && day <= GetDaysInMonth(year, month); + } + + bool IsLeapYear(int year) { + AMS_ASSERT(year > 0); + + return IsLeapYearImpl(year); + } + + int GetDaysInMonth(int year, int month) { + /* Check pre-conditions. */ + AMS_ASSERT(year > 0); + AMS_ASSERT(1 <= month && month <= 12); + + if (month == 2 && IsLeapYear(year)) { + return DaysPerMonth[month - 1] + 1; + } else { + return DaysPerMonth[month - 1]; + } + } + + int DateToDays(int year, int month, int day) { + return DateToDaysImpl(year, month, day); + } + + void DaysToDate(int *out_year, int *out_month, int *out_day, int days) { + DaysToDateImpl(out_year, out_month, out_day, days); + } + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time) { + constexpr s64 SecondsPerDay = TimeSpan::FromDays(1).GetSeconds(); + constexpr s64 SecondsPerHour = TimeSpan::FromHours(1).GetSeconds(); + constexpr s64 SecondsPerMinute = TimeSpan::FromMinutes(1).GetSeconds(); + + /* Get year/month/day. */ + int year, month, day; + DaysToDate(std::addressof(year), std::addressof(month), std::addressof(day), static_cast(posix_time.value / SecondsPerDay) + EpochDays); + + /* Handle negative posix times. */ + s64 posix_abs = posix_time.value >= 0 ? posix_time.value : -1 * posix_time.value; + s64 posix_rem = posix_abs % SecondsPerDay; + if (posix_time.value < 0) { + posix_rem *= -1; + } + + /* Adjust remainder if negative. */ + if (posix_rem < 0) { + if ((--day) <= 0) { + if ((--month) <= 0) { + --year; + month = 12; + } + day = time::impl::util::GetDaysInMonth(year, month); + } + + posix_rem += SecondsPerDay; + } + + const int hour = posix_rem / SecondsPerHour; + posix_rem %= SecondsPerHour; + + const int minute = posix_rem / SecondsPerMinute; + posix_rem %= SecondsPerMinute; + + const int second = posix_rem; + + return CalendarTime { + .year = static_cast(year), + .month = static_cast(month), + .day = static_cast(day), + .hour = static_cast(hour), + .minute = static_cast(minute), + .second = static_cast(second), + }; + } + + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time) { + /* Validate pre-conditions. */ + AMS_ASSERT(IsValidDate(calendar_time.year, calendar_time.month, calendar_time.day)); + AMS_ASSERT(0 <= calendar_time.hour && calendar_time.hour <= 23); + AMS_ASSERT(0 <= calendar_time.minute && calendar_time.minute <= 59); + AMS_ASSERT(0 <= calendar_time.second && calendar_time.second <= 59); + + /* Extract/convert fields. */ + const s64 days = static_cast(time::impl::util::DateToDays(calendar_time.year, calendar_time.month, calendar_time.day)) - EpochDays; + const s64 hours = calendar_time.hour; + const s64 minutes = calendar_time.minute; + const s64 seconds = calendar_time.second; + + return PosixTime { .value = ((((days * 24) + hours) * 60) + minutes) * 60 + seconds }; + } + } diff --git a/libraries/libstratosphere/source/time/time_api.cpp b/libraries/libstratosphere/source/time/time_api.cpp index 80f7741b9..736cbcae8 100644 --- a/libraries/libstratosphere/source/time/time_api.cpp +++ b/libraries/libstratosphere/source/time/time_api.cpp @@ -34,11 +34,10 @@ namespace ams::time { InitializeMode_SystemUser, }; - u32 g_initialize_count = 0; - InitializeMode g_initialize_mode = InitializeMode_None; + constinit u32 g_initialize_count = 0; + constinit InitializeMode g_initialize_mode = InitializeMode_None; - /* TODO: os::SdkMutex */ - os::Mutex g_initialize_mutex(false); + constinit os::SdkMutex g_initialize_mutex; Result InitializeImpl(InitializeMode mode) { std::scoped_lock lk(g_initialize_mutex); @@ -76,7 +75,11 @@ namespace ams::time { } Result InitializeForSystemUser() { - return InitializeImpl(InitializeMode_System); + if (hos::GetVersion() >= hos::Version_9_0_0) { + return InitializeImpl(InitializeMode_SystemUser); + } else { + return InitializeImpl(InitializeMode_Normal); + } } Result Finalize() { @@ -98,6 +101,10 @@ namespace ams::time { return g_initialize_count > 0; } + bool IsValidDate(int year, int month, int day) { + return impl::util::IsValidDate(year, month, day); + } + Result GetElapsedSecondsBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to) { return impl::util::GetSpanBetween(out, from, to); } diff --git a/libraries/libstratosphere/source/time/time_calendar_time.cpp b/libraries/libstratosphere/source/time/time_calendar_time.cpp new file mode 100644 index 000000000..e166bbdec --- /dev/null +++ b/libraries/libstratosphere/source/time/time_calendar_time.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::time { + + bool CalendarTime::IsValid() const { + return time::IsValidDate(this->year, this->month, this->day); + } + + CalendarTime &CalendarTime::operator+=(const TimeSpan &ts) { + *this = ToCalendarTimeInUtc(ToPosixTimeFromUtc(*this) + ts); + return *this; + } + + CalendarTime &CalendarTime::operator-=(const TimeSpan &ts) { + *this = ToCalendarTimeInUtc(ToPosixTimeFromUtc(*this) - ts); + return *this; + } + + CalendarTime operator+(const CalendarTime &lhs, const TimeSpan &rhs) { + return ToCalendarTimeInUtc(ToPosixTimeFromUtc(lhs) + rhs); + } + + CalendarTime operator-(const CalendarTime &lhs, const TimeSpan &rhs) { + return ToCalendarTimeInUtc(ToPosixTimeFromUtc(lhs) - rhs); + } + + TimeSpan operator-(const CalendarTime &lhs, const CalendarTime &rhs) { + return ToPosixTimeFromUtc(lhs) - ToPosixTimeFromUtc(rhs); + } + +} diff --git a/libraries/libstratosphere/source/time/time_timezone_api.cpp b/libraries/libstratosphere/source/time/time_timezone_api.cpp new file mode 100644 index 000000000..221412cd1 --- /dev/null +++ b/libraries/libstratosphere/source/time/time_timezone_api.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::time { + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time) { + return impl::util::ToCalendarTimeInUtc(posix_time); + } + + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time) { + return impl::util::ToPosixTimeFromUtc(calendar_time); + } + +} diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 2c69b15d1..86614a8a4 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include diff --git a/libraries/libvapours/include/vapours/util/util_format_string.hpp b/libraries/libvapours/include/vapours/util/util_format_string.hpp index 0dd2faf45..e9cf4d991 100644 --- a/libraries/libvapours/include/vapours/util/util_format_string.hpp +++ b/libraries/libvapours/include/vapours/util/util_format_string.hpp @@ -20,10 +20,10 @@ namespace ams::util { - int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...); + int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); int VSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); - int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...); + int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); int TVSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); } \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp b/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp new file mode 100644 index 000000000..e21dbc39b --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::util { + + bool VerifyUtf8String(const char *str, size_t size); + + int GetCodePointCountOfUtf8String(const char *str, size_t size); + +} diff --git a/libraries/libvapours/source/util/util_format_string.cpp b/libraries/libvapours/source/util/util_format_string.cpp index b6510e773..3d794b030 100644 --- a/libraries/libvapours/source/util/util_format_string.cpp +++ b/libraries/libvapours/source/util/util_format_string.cpp @@ -136,6 +136,7 @@ namespace ams::util { /* Parse length. */ constexpr bool SizeIsLong = sizeof(size_t) == sizeof(long); + constexpr bool PointerIsLong = sizeof(uintptr_t) == sizeof(long); constexpr bool IntMaxIsLong = sizeof(intmax_t) == sizeof(long); constexpr bool PtrDiffIsLong = sizeof(ptrdiff_t) == sizeof(long); switch (*format) { @@ -174,10 +175,10 @@ namespace ams::util { const char specifier = *(format++); switch (specifier) { case 'p': - if constexpr (sizeof(uintptr_t) == sizeof(long long)) { - SetFlag(FormatSpecifierFlag_LongLong); - } else { + if constexpr (PointerIsLong) { SetFlag(FormatSpecifierFlag_Long); + } else { + SetFlag(FormatSpecifierFlag_LongLong); } SetFlag(FormatSpecifierFlag_Hash); [[fallthrough]]; diff --git a/libraries/libvapours/source/util/util_utf8_string_util.cpp b/libraries/libvapours/source/util/util_utf8_string_util.cpp new file mode 100644 index 000000000..062653e61 --- /dev/null +++ b/libraries/libvapours/source/util/util_utf8_string_util.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::util { + + namespace { + + constexpr inline const u8 CodePointByteLengthTable[0x100] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + constexpr ALWAYS_INLINE size_t GetCodePointByteLength(u8 c) { + return CodePointByteLengthTable[c]; + } + + constexpr ALWAYS_INLINE bool IsValidTail(u8 c) { + return (c & 0xC0) == 0x80; + } + + constexpr inline bool VerifyCode(const u8 *code, size_t size) { + switch (size) { + case 1: + break; + case 2: + if (!IsValidTail(code[1])) { + return false; + } + break; + case 3: + if (code[0] == 0xE0 && (code[1] & 0x20) == 0x00) { + return false; + } + if (code[0] == 0xED && (code[1] & 0x20) != 0x00) { + return false; + } + if (!IsValidTail(code[1]) || !IsValidTail(code[2])) { + return false; + } + break; + case 4: + if (code[0] == 0xF0 && (code[1] & 0x30) == 0x00) { + return false; + } + if (code[0] == 0xF4 && (code[1] & 0x30) != 0x00) { + return false; + } + if (!IsValidTail(code[1]) || !IsValidTail(code[2]) || !IsValidTail(code[3])) { + return false; + } + break; + default: + return false; + } + + return true; + } + + } + + bool VerifyUtf8String(const char *str, size_t size) { + return GetCodePointCountOfUtf8String(str, size) != -1; + } + + int GetCodePointCountOfUtf8String(const char *str, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(str != nullptr); + AMS_ASSERT(size > 0); + + /* Parse codepoints. */ + int count = 0; + + while (size > 0) { + /* Get and check the current codepoint. */ + const u8 *code = reinterpret_cast(str); + const size_t code_size = GetCodePointByteLength(code[0]); + + if (code_size > size || !VerifyCode(code, code_size)) { + return -1; + } + + /* Advance. */ + str += code_size; + size -= code_size; + + /* Increment count. */ + ++count; + } + + return count; + } + +} diff --git a/stratosphere/LogManager/LogManager.json b/stratosphere/LogManager/LogManager.json new file mode 100644 index 000000000..9fd3ea4b3 --- /dev/null +++ b/stratosphere/LogManager/LogManager.json @@ -0,0 +1,88 @@ +{ + "name": "LogManager", + "title_id": "0x0100000000000420", + "title_id_range_min": "0x0100000000000420", + "title_id_range_max": "0x0100000000000420", + "main_thread_stack_size": "0x00003000", + "main_thread_priority": 38, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 1, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["fsp-srv", "htc", "htcs", "psc:m", "set", "set:sys", "srepo:u", "time:su", "time:u", "tma_log"], + "service_host": ["lm", "lm:get"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "type": "syscalls", + "value": { + "svcSetHeapSize": "0x01", + "svcSetMemoryPermission": "0x02", + "svcSetMemoryAttribute": "0x03", + "svcMapMemory": "0x04", + "svcUnmapMemory": "0x05", + "svcQueryMemory": "0x06", + "svcExitProcess": "0x07", + "svcCreateThread": "0x08", + "svcStartThread": "0x09", + "svcExitThread": "0x0a", + "svcSleepThread": "0x0b", + "svcGetThreadPriority": "0x0c", + "svcSetThreadPriority": "0x0d", + "svcGetThreadCoreMask": "0x0e", + "svcSetThreadCoreMask": "0x0f", + "svcGetCurrentProcessorNumber": "0x10", + "svcSignalEvent": "0x11", + "svcClearEvent": "0x12", + "svcMapSharedMemory": "0x13", + "svcUnmapSharedMemory": "0x14", + "svcCreateTransferMemory": "0x15", + "svcCloseHandle": "0x16", + "svcResetSignal": "0x17", + "svcWaitSynchronization": "0x18", + "svcCancelSynchronization": "0x19", + "svcArbitrateLock": "0x1a", + "svcArbitrateUnlock": "0x1b", + "svcWaitProcessWideKeyAtomic": "0x1c", + "svcSignalProcessWideKey": "0x1d", + "svcGetSystemTick": "0x1e", + "svcConnectToNamedPort": "0x1f", + "svcSendSyncRequestLight": "0x20", + "svcSendSyncRequest": "0x21", + "svcSendSyncRequestWithUserBuffer": "0x22", + "svcSendAsyncRequestWithUserBuffer": "0x23", + "svcGetProcessId": "0x24", + "svcGetThreadId": "0x25", + "svcBreak": "0x26", + "svcOutputDebugString": "0x27", + "svcReturnFromException": "0x28", + "svcGetInfo": "0x29", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 64 + }] +} \ No newline at end of file diff --git a/stratosphere/LogManager/Makefile b/stratosphere/LogManager/Makefile new file mode 100644 index 000000000..aab86a63a --- /dev/null +++ b/stratosphere/LogManager/Makefile @@ -0,0 +1,113 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) + +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).nsp + +ifeq ($(strip $(APP_JSON)),) +$(OUTPUT).nsp : $(OUTPUT).nso +else +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +endif + +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/stratosphere/LogManager/source/lm_main.cpp b/stratosphere/LogManager/source/lm_main.cpp new file mode 100644 index 000000000..6fbd28412 --- /dev/null +++ b/stratosphere/LogManager/source/lm_main.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +extern "C" { + extern u32 __start__; + + u32 __nx_applet_type = AppletType_None; + u32 __nx_fs_num_sessions = 2; + + #define INNER_HEAP_SIZE 0x0 + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + + void __libnx_initheap(void); + void __appInit(void); + void __appExit(void); + + void *__libnx_alloc(size_t size); + void *__libnx_aligned_alloc(size_t alignment, size_t size); + void __libnx_free(void *mem); +} + +namespace ams { + + ncm::ProgramId CurrentProgramId = ncm::AtmosphereProgramId::AtmosphereLogManager; + +} + +using namespace ams; + +#define AMS_LM_USE_FATAL_ERROR 1 + +#if AMS_LM_USE_FATAL_ERROR + +extern "C" { + + /* Exception handling. */ + alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize]; + u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); + void __libnx_exception_handler(ThreadExceptionDump *ctx); + +} + +void __libnx_exception_handler(ThreadExceptionDump *ctx) { + ams::CrashHandler(ctx); +} + +#endif + +void __libnx_initheap(void) { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; +} + +void __appInit(void) { + hos::InitializeForStratosphere(); + + R_ABORT_UNLESS(sm::Initialize()); + + R_ABORT_UNLESS(::fsInitialize()); + R_ABORT_UNLESS(::setsysInitialize()); + R_ABORT_UNLESS(::pscmInitialize()); + + ams::CheckApiVersion(); +} + +void __appExit(void) { + fsExit(); +} + +namespace ams { + + void *Malloc(size_t size) { + AMS_ABORT("ams::Malloc was called"); + } + + void Free(void *ptr) { + AMS_ABORT("ams::Free was called"); + } + +} + +void *operator new(size_t size) { + AMS_ABORT("operator new(size_t) was called"); +} + +void operator delete(void *p) { + AMS_ABORT("operator delete(void *) was called"); +} + +void *__libnx_alloc(size_t size) { + AMS_ABORT("__libnx_alloc was called"); +} + +void *__libnx_aligned_alloc(size_t alignment, size_t size) { + AMS_ABORT("__libnx_aligned_alloc was called"); +} + +void __libnx_free(void *mem) { + AMS_ABORT("__libnx_free was called"); +} + +namespace ams::lm::srv { + + void StartLogServerProxy(); + void StopLogServerProxy(); + + void InitializeFlushThread(); + void FinalizeFlushThread(); + + void InitializeIpcServer(); + void LoopIpcServer(); + void FinalizeIpcServer(); + +} + +int main(int argc, char **argv) +{ + /* Check thread priority. */ + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(LogManager, MainThread)); + + /* Set thread name. */ + os::ChangeThreadPriority(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, IpcServer)); + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(lm, IpcServer)); + + /* Start log server proxy. */ + lm::srv::StartLogServerProxy(); + + /* Initialize flush thread. */ + lm::srv::InitializeFlushThread(); + + /* Process IPC server. */ + lm::srv::InitializeIpcServer(); + lm::srv::LoopIpcServer(); + lm::srv::FinalizeIpcServer(); + + /* Finalize flush thread. */ + lm::srv::FinalizeFlushThread(); + + /* Stop log server proxy. */ + lm::srv::StopLogServerProxy(); + + return 0; +} diff --git a/stratosphere/Makefile b/stratosphere/Makefile index 9b4cc7470..1d0416dd8 100644 --- a/stratosphere/Makefile +++ b/stratosphere/Makefile @@ -1,4 +1,4 @@ -MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec +MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec LogManager SUBFOLDERS := $(MODULES) diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index 8e15ae3f9..ccea406f0 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -317,6 +317,14 @@ namespace ams::settings::fwdbg { /* (note: this is normally not necessary, and ips patches can be used.) */ R_ABORT_UNLESS(ParseSettingsItemValue("ro", "ease_nro_restriction", "u8!0x1")); + /* Control whether lm should log to the SD card. */ + /* Note that this setting does nothing when log manager is not enabled. */ + R_ABORT_UNLESS(ParseSettingsItemValue("lm", "enable_sd_card_logging", "u8!0x1")); + + /* Control the output directory for SD card logs. */ + /* Note that this setting does nothing when log manager is not enabled/sd card logging is not enabled. */ + R_ABORT_UNLESS(ParseSettingsItemValue("lm", "sd_card_log_output_directory", "str!atmosphere/binlogs")); + /* Atmosphere custom settings. */ /* Reboot from fatal automatically after some number of milliseconds. */ @@ -370,6 +378,11 @@ namespace ams::settings::fwdbg { /* 0 = Disabled, 1 = Enabled */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_htc", "u8!0x0")); + /* Controls whether atmosphere's log manager is enabled. */ + /* Note that this setting is ignored (and treated as 1) when htc is enabled. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_log_manager", "u8!0x0")); + /* Hbloader custom settings. */ /* Controls the size of the homebrew heap when running as applet. */ diff --git a/stratosphere/loader/source/ldr_process_creation.cpp b/stratosphere/loader/source/ldr_process_creation.cpp index 88f95ac64..7a957ffc9 100644 --- a/stratosphere/loader/source/ldr_process_creation.cpp +++ b/stratosphere/loader/source/ldr_process_creation.cpp @@ -13,6 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include "ldr_capabilities.hpp" #include "ldr_content_management.hpp" #include "ldr_development_manager.hpp"