Add the logger from nn::diag

This commit is contained in:
Alex Barney 2021-03-11 03:01:27 -07:00
parent 7950a91dd0
commit 39977d8e90
13 changed files with 434 additions and 15 deletions

View file

@ -0,0 +1,38 @@
namespace LibHac.Diag
{
public class DiagClient
{
internal DiagClientGlobals Globals;
public DiagClientImpl Impl => new DiagClientImpl(this);
internal HorizonClient Hos => Globals.Hos;
public DiagClient(HorizonClient horizonClient)
{
Globals.Initialize(this, horizonClient);
}
}
internal struct DiagClientGlobals
{
public HorizonClient Hos;
public object InitMutex;
public LogObserverGlobals LogObserver;
public void Initialize(DiagClient diagClient, HorizonClient horizonClient)
{
Hos = horizonClient;
InitMutex = new object();
}
}
// Functions in the nn::diag::detail namespace use this struct.
public readonly struct DiagClientImpl
{
internal readonly DiagClient Diag;
internal HorizonClient Hos => Diag.Hos;
internal ref DiagClientGlobals Globals => ref Diag.Globals;
internal DiagClientImpl(DiagClient parentClient) => Diag = parentClient;
}
}

View file

@ -0,0 +1,145 @@
using System.Collections.Generic;
using LibHac.Os;
namespace LibHac.Diag.Impl
{
internal interface IObserverHolder
{
bool IsRegistered { get; set; }
}
internal class ObserverManager<TObserver, TItem> where TObserver : IObserverHolder
{
private LinkedList<TObserver> _observers;
private ReaderWriterLock _rwLock;
public delegate void Function(ref TObserver observer, in TItem item);
public ObserverManager(HorizonClient hos)
{
_observers = new LinkedList<TObserver>();
_rwLock = new ReaderWriterLock(hos.Os);
}
public void RegisterObserver(TObserver observerHolder)
{
Assert.False(observerHolder.IsRegistered);
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
_observers.AddFirst(observerHolder);
observerHolder.IsRegistered = true;
}
public void UnregisterObserver(TObserver observerHolder)
{
Assert.True(observerHolder.IsRegistered);
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<TObserver> foundObserver = _observers.Find(observerHolder);
if (foundObserver is not null)
{
_observers.Remove(foundObserver);
}
observerHolder.IsRegistered = false;
}
public void UnregisterAllObservers()
{
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<TObserver> curNode = _observers.First;
while (curNode is not null)
{
curNode.ValueRef.IsRegistered = false;
curNode = curNode.Next;
}
_observers.Clear();
}
public void InvokeAllObserver(in TItem item, Function function)
{
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<TObserver> curNode = _observers.First;
while (curNode is not null)
{
function(ref curNode.ValueRef, in item);
curNode = curNode.Next;
}
}
}
// Todo: Use generic class when ref structs can be used as generics
internal class LogObserverManager
{
private readonly LinkedList<LogObserverHolder> _observers;
private ReaderWriterLock _rwLock;
public delegate void Function(ref LogObserverHolder observer, in LogObserverContext item);
public LogObserverManager(HorizonClient hos)
{
_observers = new LinkedList<LogObserverHolder>();
_rwLock = new ReaderWriterLock(hos.Os);
}
public void RegisterObserver(LogObserverHolder observerHolder)
{
Assert.False(observerHolder.IsRegistered);
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
_observers.AddFirst(observerHolder);
observerHolder.IsRegistered = true;
}
public void UnregisterObserver(LogObserverHolder observerHolder)
{
Assert.True(observerHolder.IsRegistered);
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<LogObserverHolder> foundObserver = _observers.Find(observerHolder);
if (foundObserver is not null)
{
_observers.Remove(foundObserver);
}
observerHolder.IsRegistered = false;
}
public void UnregisterAllObservers()
{
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<LogObserverHolder> curNode = _observers.First;
while (curNode is not null)
{
curNode.ValueRef.IsRegistered = false;
curNode = curNode.Next;
}
_observers.Clear();
}
public void InvokeAllObserver(in LogObserverContext item, Function function)
{
using ScopedLock<ReaderWriterLock> lk = ScopedLock.Lock(ref _rwLock);
LinkedListNode<LogObserverHolder> curNode = _observers.First;
while (curNode is not null)
{
function(ref curNode.ValueRef, in item);
curNode = curNode.Next;
}
}
}
}

47
src/LibHac/Diag/Log.cs Normal file
View file

@ -0,0 +1,47 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
namespace LibHac.Diag
{
public static class Log
{
// Todo: Should we split large logs into smaller chunks like Horizon does?
public static void LogImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan<byte> message)
{
diag.PutImpl(in metaData, message);
}
public static void PutImpl(this DiagClientImpl diag, in LogMetaData metaData, ReadOnlySpan<byte> message)
{
var logBody = new LogBody
{
Message = new U8Span(message),
IsHead = true,
IsTail = true
};
diag.CallAllLogObserver(in metaData, in logBody);
}
public static void LogImpl(this DiagClientImpl diag, ReadOnlySpan<byte> moduleName, LogSeverity severity,
ReadOnlySpan<byte> message, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "",
[CallerMemberName] string functionName = "")
{
var metaData = new LogMetaData
{
SourceInfo = new SourceInfo
{
LineNumber = lineNumber,
FileName = fileName,
FunctionName = functionName
},
ModuleName = new U8Span(moduleName),
Severity = severity,
Verbosity = 0
};
diag.LogImpl(in metaData, message);
}
}
}

View file

@ -0,0 +1,110 @@
using LibHac.Common;
using LibHac.Diag.Impl;
namespace LibHac.Diag
{
public delegate void LogObserver(in LogMetaData metaData, in LogBody body, object arguments);
internal struct LogObserverGlobals
{
public LogObserverHolder DefaultLogObserverHolder;
public nint ManagerGuard;
public LogObserverManager Manager;
}
public class LogObserverHolder
{
internal LogObserver Observer;
internal bool IsRegistered;
internal object Arguments;
}
public static class LogObserverFuncs
{
public static void InitializeLogObserverHolder(this DiagClient diag, ref LogObserverHolder holder,
LogObserver observer, object arguments)
{
holder.Observer = observer;
holder.IsRegistered = false;
holder.Arguments = arguments;
}
public static void RegisterLogObserver(this DiagClient diag, LogObserverHolder observerHolder)
{
diag.Impl.GetLogObserverManager().RegisterObserver(observerHolder);
}
public static void UnregisterLogObserver(this DiagClient diag, LogObserverHolder observerHolder)
{
diag.Impl.GetLogObserverManager().UnregisterObserver(observerHolder);
}
private static void TentativeDefaultLogObserver(in LogMetaData metaData, in LogBody body, object arguments)
{
}
private static LogObserverManager GetLogObserverManager(this DiagClientImpl diag)
{
ref LogObserverGlobals g = ref diag.Globals.LogObserver;
using var guard = new InitializationGuard(ref g.ManagerGuard, diag.Globals.InitMutex);
if (!guard.IsInitialized)
{
g.Manager = new LogObserverManager(diag.Hos);
g.DefaultLogObserverHolder = new LogObserverHolder();
diag.Diag.InitializeLogObserverHolder(ref g.DefaultLogObserverHolder, TentativeDefaultLogObserver, null);
g.Manager.RegisterObserver(g.DefaultLogObserverHolder);
}
return g.Manager;
}
internal static void CallAllLogObserver(this DiagClientImpl diag, in LogMetaData metaData, in LogBody body)
{
var context = new LogObserverContext
{
MetaData = metaData,
Body = body
};
LogObserverManager manager = diag.GetLogObserverManager();
manager.InvokeAllObserver(in context, InvokeFunction);
static void InvokeFunction(ref LogObserverHolder holder, in LogObserverContext item)
{
holder.Observer(in item.MetaData, in item.Body, holder.Arguments);
}
}
internal static void ReplaceDefaultLogObserver(this DiagClientImpl diag, LogObserver observer)
{
ref LogObserverGlobals g = ref diag.Globals.LogObserver;
diag.Diag.UnregisterLogObserver(g.DefaultLogObserverHolder);
diag.Diag.InitializeLogObserverHolder(ref g.DefaultLogObserverHolder, observer, null);
diag.Diag.RegisterLogObserver(g.DefaultLogObserverHolder);
}
internal static void ResetDefaultLogObserver(this DiagClientImpl diag)
{
ref LogObserverGlobals g = ref diag.Globals.LogObserver;
diag.Diag.UnregisterLogObserver(g.DefaultLogObserverHolder);
diag.Diag.InitializeLogObserverHolder(ref g.DefaultLogObserverHolder, TentativeDefaultLogObserver, null);
diag.Diag.RegisterLogObserver(g.DefaultLogObserverHolder);
}
}
}
namespace LibHac.Diag.Impl
{
// Todo: Make fields references once C# 10 is released
internal ref struct LogObserverContext
{
public LogMetaData MetaData;
public LogBody Body;
}
}

View file

@ -0,0 +1,38 @@
using System;
using LibHac.Common;
namespace LibHac.Diag
{
public enum LogSeverity
{
Trace,
Info,
Warn,
Error,
Fatal
}
public ref struct LogMetaData
{
public SourceInfo SourceInfo;
public U8Span ModuleName;
public LogSeverity Severity;
public int Verbosity;
public bool UseDefaultLocaleCharset;
public Span<byte> AdditionalData;
}
public struct SourceInfo
{
public int LineNumber;
public string FileName;
public string FunctionName;
}
public ref struct LogBody
{
public U8Span Message;
public bool IsHead;
public bool IsTail;
}
}

View file

@ -24,7 +24,7 @@ namespace LibHac
{
_currentProcessId = InitialProcessCountMax;
TickGenerator = config.TickGenerator;
TickGenerator = config.TickGenerator ?? new DefaultTickGenerator();
ServiceManager = new ServiceManager();
LoaderClient = CreatePrivilegedHorizonClient();

View file

@ -1,5 +1,6 @@
using System;
using LibHac.Arp;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Lr;
using LibHac.Os;
@ -18,6 +19,7 @@ namespace LibHac
public FileSystemClient Fs { get; }
public ServiceManagerClient Sm { get; }
public OsState Os { get; }
public DiagClient Diag { get; }
public LrClient Lr { get; }
public ArpClient Arp => ArpLazy.Value;
@ -29,6 +31,7 @@ namespace LibHac
Fs = new FileSystemClient(this);
Sm = new ServiceManagerClient(Horizon.ServiceManager);
Os = new OsState(this, horizon.TickGenerator);
Diag = new DiagClient(this);
Lr = new LrClient(this);
ArpLazy = new Lazy<ArpClient>(InitArpClient, true);

View file

@ -14,9 +14,4 @@ namespace LibHac
/// </summary>
public ITickGenerator? TickGenerator { get; set; }
}
public interface ITickGenerator
{
Tick GetCurrentTick();
}
}

View file

@ -0,0 +1,24 @@
using System.Diagnostics;
namespace LibHac.Os
{
public interface ITickGenerator
{
Tick GetCurrentTick();
}
internal class DefaultTickGenerator : ITickGenerator
{
private readonly long _initialTick;
public DefaultTickGenerator()
{
_initialTick = Stopwatch.GetTimestamp();
}
public Tick GetCurrentTick()
{
return new Tick(Stopwatch.GetTimestamp() - _initialTick);
}
}
}

View file

@ -1,6 +1,5 @@
using LibHac;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.FsSystem;
namespace hactoolnet
@ -100,6 +99,6 @@ namespace hactoolnet
public Options Options;
public KeySet KeySet;
public ProgressBar Logger;
public FileSystemClient FsClient;
public HorizonClient Horizon;
}
}

View file

@ -4,6 +4,7 @@ using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Npdm;
@ -45,7 +46,7 @@ namespace hactoolnet
if (ctx.Options.SectionOutDir[i] != null)
{
FileSystemClient fs = ctx.FsClient;
FileSystemClient fs = ctx.Horizon.Fs;
string mountName = $"section{i}";
@ -96,11 +97,14 @@ namespace hactoolnet
if (ctx.Options.RomfsOutDir != null)
{
FileSystemClient fs = ctx.FsClient;
FileSystemClient fs = ctx.Horizon.Fs;
fs.Register("rom".ToU8Span(), OpenFileSystemByType(NcaSectionType.Data));
fs.Register("output".ToU8Span(), new LocalFileSystem(ctx.Options.RomfsOutDir));
fs.Impl.EnableFileSystemAccessorAccessLog("rom".ToU8Span());
fs.Impl.EnableFileSystemAccessorAccessLog("output".ToU8Span());
FsUtils.CopyDirectoryWithProgress(fs, "rom:/".ToU8Span(), "output:/".ToU8Span(), logger: ctx.Logger).ThrowIfFailure();
fs.Unmount("rom".ToU8Span());
@ -153,7 +157,7 @@ namespace hactoolnet
if (ctx.Options.ExefsOutDir != null)
{
FileSystemClient fs = ctx.FsClient;
FileSystemClient fs = ctx.Horizon.Fs;
fs.Register("code".ToU8Span(), OpenFileSystemByType(NcaSectionType.Code));
fs.Register("output".ToU8Span(), new LocalFileSystem(ctx.Options.ExefsOutDir));

View file

@ -31,7 +31,7 @@ namespace hactoolnet
bool signNeeded = ctx.Options.SignSave;
var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true);
FileSystemClient fs = ctx.FsClient;
FileSystemClient fs = ctx.Horizon.Fs;
fs.Register("save".ToU8Span(), save);

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Text;
using LibHac;
using LibHac.Common.Keys;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Util;
@ -61,6 +62,7 @@ namespace hactoolnet
StreamWriter logWriter = null;
ResultLogger resultLogger = null;
LogObserverHolder logObserver = null;
try
{
@ -71,14 +73,23 @@ namespace hactoolnet
Horizon horizon = HorizonFactory.CreateWithDefaultFsConfig(new HorizonConfiguration(),
new InMemoryFileSystem(), ctx.KeySet);
ctx.FsClient = horizon.CreatePrivilegedHorizonClient().Fs;
ctx.Horizon = horizon.CreatePrivilegedHorizonClient();
if (ctx.Options.AccessLog != null)
{
logWriter = new StreamWriter(ctx.Options.AccessLog);
logObserver = new LogObserverHolder();
ctx.FsClient.SetLocalSystemAccessLogForDebug(true);
ctx.FsClient.SetGlobalAccessLogMode(GlobalAccessLogMode.Log);
// ReSharper disable once AccessToDisposedClosure
// References to logWriter should be gone by the time it's disposed
ctx.Horizon.Diag.InitializeLogObserverHolder(ref logObserver,
(in LogMetaData data, in LogBody body, object arguments) =>
logWriter.Write(body.Message.ToString()), null);
ctx.Horizon.Diag.RegisterLogObserver(logObserver);
ctx.Horizon.Fs.SetLocalSystemAccessLogForDebug(true);
ctx.Horizon.Fs.SetGlobalAccessLogMode(GlobalAccessLogMode.Log).ThrowIfFailure();
}
if (ctx.Options.ResultLog != null)
@ -100,6 +111,11 @@ namespace hactoolnet
}
finally
{
if (logObserver != null)
{
ctx.Horizon.Diag.UnregisterLogObserver(logObserver);
}
logWriter?.Dispose();
if (resultLogger != null)