All lm service and diag logging implementation

This commit is contained in:
XorTroll 2020-05-06 23:34:10 +02:00
parent 3971f3e053
commit d74751d5e9
4 changed files with 511 additions and 0 deletions

View File

@ -112,6 +112,7 @@ extern "C" {
#include "switch/services/notif.h"
#include "switch/services/mii.h"
#include "switch/services/miiimg.h"
#include "switch/services/lm.h"
#include "switch/display/binder.h"
#include "switch/display/parcel.h"
@ -145,6 +146,7 @@ extern "C" {
#include "switch/runtime/nxlink.h"
#include "switch/runtime/resolver.h"
#include "switch/runtime/ringcon.h"
#include "switch/runtime/diag.h"
#include "switch/runtime/util/utf.h"

View File

@ -0,0 +1,407 @@
/**
* @file diag.h
* @brief Diagnostics utils (logging implementation, wrapping lm service)
* @author XorTroll
* @copyright libnx Authors
*/
#pragma once
#include "../services/lm.h"
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <stdio.h>
/// DiagLogSeverity
typedef enum {
DiagLogSeverity_Trace = 0,
DiagLogSeverity_Info = 1,
DiagLogSeverity_Warn = 2,
DiagLogSeverity_Error = 3,
DiagLogSeverity_Fatal = 4
} DiagLogSeverity;
/// DiagLogPacketFlags
typedef enum {
DiagLogPacketFlags_Head = BIT(0), ///< First packet.
DiagLogPacketFlags_Tail = BIT(1) ///< Last packet.
} DiagLogPacketFlags;
/// DiagLogPacketHeader
typedef struct {
u64 process_id; ///< Process ID.
u64 thread_id; ///< Thread ID.
u8 flags; ///< \ref DiagLogPacketFlags
u8 pad; ///< Padding
u8 severity; ///< \ref DiagLogSeverity
u8 verbosity; ///< Verbosity.
u32 payload_size; ///< Total packet size after this header.
} DiagLogPacketHeader;
/// DiagLogDataChunkKey
typedef enum {
DiagLogDataChunkKey_LogSessionBegin = 0, ///< Log session begin (unknown)
DiagLogDataChunkKey_LogSessionEnd = 1, ///< Log session end (unknown)
DiagLogDataChunkKey_TextLog = 2, ///< Text to be logged.
DiagLogDataChunkKey_LineNumber = 3, ///< Source line number.
DiagLogDataChunkKey_FileName = 4, ///< Source file name.
DiagLogDataChunkKey_FunctionName = 5, ///< Source function name.
DiagLogDataChunkKey_ModuleName = 6, ///< Process module name.
DiagLogDataChunkKey_ThreadName = 7, ///< Process thread name.
DiagLogDataChunkKey_LogPacketDropCount = 8, ///< Log packet drop count (unknown)
DiagLogDataChunkKey_UserSystemClock = 9, ///< User system clock (unknown)
DiagLogDataChunkKey_ProcessName = 10 ///< Process name.
} DiagLogDataChunkKey;
/// DiagLogDataChunkTypeHeader (see specific types below)
typedef struct {
u8 chunk_key; ///< \ref DiagLogDataChunkKey
u8 chunk_len; ///< Value length.
} DiagLogDataChunkTypeHeader;
// Specific chunk types.
NX_CONSTEXPR void diagChunkTypeCreate(void *chunk_type, size_t chunk_type_size, DiagLogDataChunkTypeHeader *header, DiagLogDataChunkKey key) {
__builtin_memset(chunk_type, 0, chunk_type_size);
header->chunk_key = key;
header->chunk_len = 0;
}
typedef struct {
DiagLogDataChunkTypeHeader header;
u8 value;
} DiagU8ChunkType;
NX_CONSTEXPR void diagU8ChunkTypeCreate(DiagU8ChunkType *chunk_type, DiagLogDataChunkKey key) {
diagChunkTypeCreate(chunk_type, sizeof(DiagU8ChunkType), &chunk_type->header, key);
}
NX_CONSTEXPR void diagU8ChunkTypeSetValue(DiagU8ChunkType *chunk_type, DiagLogDataChunkKey key, u8 value) {
diagU8ChunkTypeCreate(chunk_type, key);
chunk_type->header.chunk_len = sizeof(u8);
chunk_type->value = value;
}
typedef struct {
DiagLogDataChunkTypeHeader header;
u32 value;
} DiagU32ChunkType;
NX_CONSTEXPR void diagU32ChunkTypeCreate(DiagU32ChunkType *chunk_type, DiagLogDataChunkKey key) {
diagChunkTypeCreate(chunk_type, sizeof(DiagU32ChunkType), &chunk_type->header, key);
}
NX_CONSTEXPR void diagU32ChunkTypeSetValue(DiagU32ChunkType *chunk_type, DiagLogDataChunkKey key, u32 value) {
diagU32ChunkTypeCreate(chunk_type, key);
chunk_type->header.chunk_len = sizeof(u32);
chunk_type->value = value;
}
typedef struct {
DiagLogDataChunkTypeHeader header;
u64 value;
} DiagU64ChunkType;
NX_CONSTEXPR void diagU64ChunkTypeCreate(DiagU64ChunkType *chunk_type, DiagLogDataChunkKey key) {
diagChunkTypeCreate(chunk_type, sizeof(DiagU64ChunkType), &chunk_type->header, key);
}
NX_CONSTEXPR void diagU64ChunkTypeSetValue(DiagU64ChunkType *chunk_type, DiagLogDataChunkKey key, u64 value) {
diagU64ChunkTypeCreate(chunk_type, key);
chunk_type->header.chunk_len = sizeof(u64);
chunk_type->value = value;
}
#define DIAG_MAX_STRING_LEN 0xFF
typedef struct {
DiagLogDataChunkTypeHeader header;
char value[DIAG_MAX_STRING_LEN + 1];
} DiagStringChunkType;
NX_CONSTEXPR void diagStringChunkTypeCreate(DiagStringChunkType *chunk_type, DiagLogDataChunkKey key) {
diagChunkTypeCreate(chunk_type, sizeof(DiagStringChunkType), &chunk_type->header, key);
}
NX_CONSTEXPR void diagStringChunkTypeSetValue(DiagStringChunkType *chunk_type, DiagLogDataChunkKey key, const char *value, const size_t value_len) {
diagStringChunkTypeCreate(chunk_type, key);
const size_t len = (value_len > DIAG_MAX_STRING_LEN) ? DIAG_MAX_STRING_LEN : value_len;
chunk_type->header.chunk_len = (u8)len;
__builtin_memcpy(chunk_type->value, value, len);
}
typedef struct {
DiagU8ChunkType log_session_begin;
DiagU8ChunkType log_session_end;
DiagStringChunkType text_log;
DiagU32ChunkType line_number;
DiagStringChunkType file_name;
DiagStringChunkType function_name;
DiagStringChunkType module_name;
DiagStringChunkType thread_name;
DiagU64ChunkType log_packet_drop_count;
DiagU64ChunkType user_system_clock;
DiagStringChunkType process_name;
} DiagLogPacketPayload;
// Struct containing everything that will be logged.
typedef struct {
DiagLogPacketHeader header;
DiagLogPacketPayload payload;
} DiagLogPacket;
// Helper structs for logging parameters.
typedef struct {
u32 line_number;
const char *file_name;
const char *function_name;
} DiagSourceInfo;
typedef struct {
DiagSourceInfo source_info;
DiagLogSeverity severity;
bool verbosity;
const char *text_log;
} DiagLogMetadata;
// Log packet allocation/freeing
/// Allocates the amount of log packets needed depending on the message length.
NX_INLINE DiagLogPacket *diagAllocateLogPackets(const size_t msg_len, size_t *out_packet_count) {
size_t remaining_len = msg_len;
size_t packet_count = 1;
while(remaining_len > DIAG_MAX_STRING_LEN) {
packet_count++;
remaining_len -= DIAG_MAX_STRING_LEN;
}
// Always send at least 2 packets, a head and a tail.
if(packet_count < 2) {
packet_count = 2;
}
DiagLogPacket *packets = (DiagLogPacket*)calloc(packet_count, sizeof(DiagLogPacket));
if(packets == NULL) {
return 0;
}
*out_packet_count = packet_count;
return packets;
}
NX_INLINE void diagFreeLogPackets(DiagLogPacket *packet_buf) {
free(packet_buf);
}
// Other log packet / chunk type helpers
NX_CONSTEXPR bool diagIsChunkTypeEmpty(const DiagLogDataChunkTypeHeader *chunk_header) {
return chunk_header->chunk_len == 0;
}
NX_CONSTEXPR size_t diagComputeChunkKeySize(const DiagLogDataChunkTypeHeader *chunk_header) {
if(diagIsChunkTypeEmpty(chunk_header)) {
return 0;
}
return sizeof(DiagLogDataChunkTypeHeader) + chunk_header->chunk_len;
}
NX_CONSTEXPR size_t diagComputeLogPacketPayloadSize(const DiagLogPacket *packet) {
return diagComputeChunkKeySize(&packet->payload.log_session_begin.header) +
diagComputeChunkKeySize(&packet->payload.log_session_end.header) +
diagComputeChunkKeySize(&packet->payload.text_log.header) +
diagComputeChunkKeySize(&packet->payload.line_number.header) +
diagComputeChunkKeySize(&packet->payload.file_name.header) +
diagComputeChunkKeySize(&packet->payload.function_name.header) +
diagComputeChunkKeySize(&packet->payload.module_name.header) +
diagComputeChunkKeySize(&packet->payload.thread_name.header) +
diagComputeChunkKeySize(&packet->payload.log_packet_drop_count.header) +
diagComputeChunkKeySize(&packet->payload.user_system_clock.header) +
diagComputeChunkKeySize(&packet->payload.process_name.header);
}
// Log payload encoding helpers
NX_CONSTEXPR u8 *diagLogPayloadEncode(u8 *payload_buf, const void *data, size_t size) {
if(size > 0) {
__builtin_memcpy(payload_buf, data, size);
return payload_buf + size;
}
return payload_buf;
}
NX_CONSTEXPR u8 *diagLogPayloadEncodeU8ChunkType(u8 *payload_buf, DiagU8ChunkType *chunk_type) {
if(diagIsChunkTypeEmpty(&chunk_type->header)) {
return payload_buf;
}
u8 *buf = diagLogPayloadEncode(payload_buf, &chunk_type->header, sizeof(DiagLogDataChunkTypeHeader));
// Copy value.
return diagLogPayloadEncode(buf, &chunk_type->value, chunk_type->header.chunk_len);
}
NX_CONSTEXPR u8 *diagLogPayloadEncodeU32ChunkType(u8 *payload_buf, DiagU32ChunkType *chunk_type) {
if(diagIsChunkTypeEmpty(&chunk_type->header)) {
return payload_buf;
}
u8 *buf = diagLogPayloadEncode(payload_buf, &chunk_type->header, sizeof(DiagLogDataChunkTypeHeader));
// Copy value.
return diagLogPayloadEncode(buf, &chunk_type->value, chunk_type->header.chunk_len);
}
NX_CONSTEXPR u8 *diagLogPayloadEncodeU64ChunkType(u8 *payload_buf, DiagU64ChunkType *chunk_type) {
if(diagIsChunkTypeEmpty(&chunk_type->header)) {
return payload_buf;
}
u8 *buf = diagLogPayloadEncode(payload_buf, &chunk_type->header, sizeof(DiagLogDataChunkTypeHeader));
// Copy value.
return diagLogPayloadEncode(buf, &chunk_type->value, chunk_type->header.chunk_len);
}
NX_CONSTEXPR u8 *diagLogPayloadEncodeStringChunkType(u8 *payload_buf, DiagStringChunkType *chunk_type) {
if(diagIsChunkTypeEmpty(&chunk_type->header)) {
return payload_buf;
}
u8 *buf = diagLogPayloadEncode(payload_buf, &chunk_type->header, sizeof(DiagLogDataChunkTypeHeader));
// Copy string by its length.
return diagLogPayloadEncode(buf, chunk_type->value, chunk_type->header.chunk_len);
}
/**
* @brief Logs via lm.
* @note Retail lm is stubbed.
* @param[in] metadata Log parameters.
*/
NX_INLINE void diagLogImpl(const DiagLogMetadata *metadata) {
Result rc = smInitialize();
if(R_SUCCEEDED(rc)) {
rc = lmInitialize();
smExit();
}
if(R_SUCCEEDED(rc)) {
size_t count = 0;
const size_t text_log_len = __builtin_strlen(metadata->text_log);
DiagLogPacket *packets = diagAllocateLogPackets(text_log_len, &count);
if(count > 0 && packets != NULL) {
DiagLogPacket *head_packet = &packets[0];
head_packet->header.flags = DiagLogPacketFlags_Head;
DiagLogPacket *tail_packet = &packets[count - 1];
tail_packet->header.flags = DiagLogPacketFlags_Tail;
const size_t file_name_len = __builtin_strlen(metadata->source_info.file_name);
diagStringChunkTypeSetValue(&head_packet->payload.file_name, DiagLogDataChunkKey_FileName, metadata->source_info.file_name, file_name_len);
const size_t function_name_len = __builtin_strlen(metadata->source_info.function_name);
diagStringChunkTypeSetValue(&head_packet->payload.function_name, DiagLogDataChunkKey_FunctionName, metadata->source_info.function_name, function_name_len);
diagU32ChunkTypeSetValue(&head_packet->payload.line_number, DiagLogDataChunkKey_LineNumber, metadata->source_info.line_number);
const char *module = "libnx";
diagStringChunkTypeSetValue(&head_packet->payload.module_name, DiagLogDataChunkKey_ModuleName, module, __builtin_strlen(module));
size_t remaining_len = text_log_len;
DiagLogPacket *cur_packet = head_packet;
const char *text_log_buf = metadata->text_log;
while(remaining_len > 0) {
const size_t cur_len = (remaining_len > DIAG_MAX_STRING_LEN) ? DIAG_MAX_STRING_LEN : remaining_len;
diagStringChunkTypeSetValue(&cur_packet->payload.text_log, DiagLogDataChunkKey_TextLog, text_log_buf, cur_len);
cur_packet++;
text_log_buf += cur_len;
remaining_len -= cur_len;
}
size_t i = 0;
for(; i < count; i++) {
DiagLogPacket *cur_packet = &packets[i];
cur_packet->header.severity = (u8)metadata->severity;
cur_packet->header.verbosity = (u8)metadata->verbosity;
cur_packet->header.payload_size = (u32)diagComputeLogPacketPayloadSize(cur_packet);
const size_t log_buf_size = cur_packet->header.payload_size + sizeof(DiagLogPacketHeader);
void *log_buf = calloc(1, log_buf_size);
if(log_buf != NULL) {
// Write the packet's header.
u8 *payload_buf = diagLogPayloadEncode((u8*)log_buf, &cur_packet->header, sizeof(DiagLogPacketHeader));
// Write non-empty packets to the payload.
if(!diagIsChunkTypeEmpty(&cur_packet->payload.log_session_begin.header)) {
payload_buf = diagLogPayloadEncodeU8ChunkType(payload_buf, &cur_packet->payload.log_session_begin);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.log_session_end.header)) {
payload_buf = diagLogPayloadEncodeU8ChunkType(payload_buf, &cur_packet->payload.log_session_end);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.text_log.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.text_log);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.line_number.header)) {
payload_buf = diagLogPayloadEncodeU32ChunkType(payload_buf, &cur_packet->payload.line_number);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.file_name.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.file_name);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.function_name.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.function_name);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.module_name.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.module_name);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.thread_name.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.thread_name);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.log_packet_drop_count.header)) {
payload_buf = diagLogPayloadEncodeU64ChunkType(payload_buf, &cur_packet->payload.log_packet_drop_count);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.user_system_clock.header)) {
payload_buf = diagLogPayloadEncodeU64ChunkType(payload_buf, &cur_packet->payload.user_system_clock);
}
if(!diagIsChunkTypeEmpty(&cur_packet->payload.process_name.header)) {
payload_buf = diagLogPayloadEncodeStringChunkType(payload_buf, &cur_packet->payload.process_name);
}
rc = lmLog(log_buf, log_buf_size);
free(log_buf);
if(R_FAILED(rc)) {
break;
}
}
}
diagFreeLogPackets(packets);
}
lmExit();
}
}
// Helper macros.
#define DIAG_DETAILED_LOG(log_severity, log_verbosity, msg) ({ \
const DiagLogMetadata __log_metadata = { \
.source_info = { \
.line_number = __LINE__, \
.file_name = __FILE__, \
.function_name = __func__, \
}, \
.severity = log_severity, \
.verbosity = log_verbosity, \
.text_log = msg, \
}; \
diagLogImpl(&__log_metadata); \
})
#define DIAG_DETAILED_LOGF(log_severity, log_verbosity, fmt, ...) ({ \
char msg[0x400] = {}; \
sprintf(msg, fmt, ##__VA_ARGS__); \
DIAG_DETAILED_LOG(log_severity, log_verbosity, msg); \
})
#define DIAG_DETAILED_VLOG(log_severity, log_verbosity, fmt, args) ({ \
char msg[0x400] = {}; \
vsprintf(msg, fmt, args); \
DIAG_DETAILED_LOG(log_severity, log_verbosity, msg); \
})
#define DIAG_LOG(msg) DIAG_DETAILED_LOG(DiagLogSeverity_Info, false, msg)
#define DIAG_LOGF(fmt, ...) DIAG_DETAILED_LOGF(DiagLogSeverity_Info, false, fmt, ##__VA_ARGS__)
#define DIAG_VLOG(fmt, args) DIAG_DETAILED_VLOG(DiagLogSeverity_Info, false, fmt, args)

View File

@ -0,0 +1,44 @@
/**
* @file lm.h
* @brief Log manager (lm) service IPC wrapper.
* @author XorTroll
* @copyright libnx Authors
*/
#pragma once
#include "../types.h"
#include "../sf/service.h"
/// LmLogDestination
typedef enum {
LmLogDestination_TMA = BIT(0), ///< Logs to TMA.
LmLogDestination_UART = BIT(1), ///< Logs to UART.
LmLogDestination_UARTSleeping = BIT(2), ///< Logs to UART ().
LmLogDestination_All = 0xFFFF, ///< Logs to all locations.
} LmLogDestination;
/// Initialize lm. This is stubbed on retail LogManager, always succeeding. This service is automatically initialized and exited by diag.
Result lmInitialize(void);
/// Exit lm.
void lmExit(void);
/// Gets the Service object for the actual lm service session.
Service* lmGetServiceSession(void);
/// Gets the Service object for ILogger.
Service* lmGetServiceSession_Logger(void);
/**
* @brief Logs sent data.
* @note Check diag for a logging implementation over this service.
* @param[in] buf Log buffer.
* @param[in] buf_size Log buffer size.
*/
Result lmLog(const void *buf, size_t buf_size);
/**
* @brief Sets the log destination.
* @param[in] destination Log destination.
*/
Result lmSetDestination(LmLogDestination destination);

58
nx/source/services/lm.c Normal file
View File

@ -0,0 +1,58 @@
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "service_guard.h"
#include "runtime/hosversion.h"
#include "services/lm.h"
static Service g_lmSrv;
static Service g_lmLoggerSrv;
static Result _lmOpenLogger(Service* srv_out);
NX_GENERATE_SERVICE_GUARD(lm);
Result _lmInitialize(void) {
Result rc = smGetService(&g_lmSrv, "lm");
// Open logger interface.
if (R_SUCCEEDED(rc)) {
rc = _lmOpenLogger(&g_lmLoggerSrv);
}
return rc;
}
void _lmCleanup(void) {
serviceClose(&g_lmLoggerSrv);
serviceClose(&g_lmSrv);
}
Service* lmGetServiceSession(void) {
return &g_lmSrv;
}
Service* lmGetServiceSession_Logger(void) {
return &g_lmLoggerSrv;
}
static Result _lmOpenLogger(Service* srv_out) {
u64 pid_placeholder = 0;
return serviceDispatchIn(&g_lmSrv, 0, pid_placeholder,
.in_send_pid = true,
.out_num_objects = 1,
.out_objects = srv_out,
);
}
Result lmLog(const void *buf, size_t buf_size) {
return serviceDispatch(&g_lmLoggerSrv, 0,
.buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In },
.buffers = { { buf, buf_size } },
);
}
Result lmSetDestination(LmLogDestination destination) {
if(hosversionBefore(3,0,0))
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
u32 in = (u32)destination;
return serviceDispatchIn(&g_lmLoggerSrv, 1, in);
}