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.
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<const char *>(cur_address + ModulePathOffset);
|
||||
*out_path_length = *reinterpret_cast<const u32 *>(cur_address + ModulePathLengthOffset);
|
||||
*out_module_size = exception_info.module_size;
|
||||
|
||||
return exception_info.module_address;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::diag::impl {
|
||||
|
||||
template<typename Holder, typename Context>
|
||||
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<typename Observer>
|
||||
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<typename Holder, typename Context>
|
||||
class ObserverManagerWithDefaultHolder : public ObserverManager<Holder, Context> {
|
||||
private:
|
||||
Holder m_default_holder;
|
||||
public:
|
||||
template<typename Initializer, typename... Args>
|
||||
constexpr ObserverManagerWithDefaultHolder(Initializer initializer, Args &&... args) : ObserverManager<Holder, Context>(), m_default_holder{} {
|
||||
/* Initialize the default observer. */
|
||||
initializer(std::addressof(m_default_holder), std::forward<Args>(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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::diag::impl {
|
||||
|
||||
void PrintDebugString(const char *msg, size_t size);
|
||||
void PrintDebugString(const char *msg);
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
/* 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<uintptr_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<const u8 *>(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<const u8 *>(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<int>(len - actual_size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user