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; _currentProcessId = InitialProcessCountMax;
TickGenerator = config.TickGenerator; TickGenerator = config.TickGenerator ?? new DefaultTickGenerator();
ServiceManager = new ServiceManager(); ServiceManager = new ServiceManager();
LoaderClient = CreatePrivilegedHorizonClient(); LoaderClient = CreatePrivilegedHorizonClient();

View file

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

View file

@ -14,9 +14,4 @@ namespace LibHac
/// </summary> /// </summary>
public ITickGenerator? TickGenerator { get; set; } 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;
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
namespace hactoolnet namespace hactoolnet
@ -100,6 +99,6 @@ namespace hactoolnet
public Options Options; public Options Options;
public KeySet KeySet; public KeySet KeySet;
public ProgressBar Logger; public ProgressBar Logger;
public FileSystemClient FsClient; public HorizonClient Horizon;
} }
} }

View file

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

View file

@ -31,7 +31,7 @@ namespace hactoolnet
bool signNeeded = ctx.Options.SignSave; bool signNeeded = ctx.Options.SignSave;
var save = new SaveDataFileSystem(ctx.KeySet, file, ctx.Options.IntegrityLevel, true); 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); fs.Register("save".ToU8Span(), save);

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Text; using System.Text;
using LibHac; using LibHac;
using LibHac.Common.Keys; using LibHac.Common.Keys;
using LibHac.Diag;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Util; using LibHac.Util;
@ -61,6 +62,7 @@ namespace hactoolnet
StreamWriter logWriter = null; StreamWriter logWriter = null;
ResultLogger resultLogger = null; ResultLogger resultLogger = null;
LogObserverHolder logObserver = null;
try try
{ {
@ -71,14 +73,23 @@ namespace hactoolnet
Horizon horizon = HorizonFactory.CreateWithDefaultFsConfig(new HorizonConfiguration(), Horizon horizon = HorizonFactory.CreateWithDefaultFsConfig(new HorizonConfiguration(),
new InMemoryFileSystem(), ctx.KeySet); new InMemoryFileSystem(), ctx.KeySet);
ctx.FsClient = horizon.CreatePrivilegedHorizonClient().Fs; ctx.Horizon = horizon.CreatePrivilegedHorizonClient();
if (ctx.Options.AccessLog != null) if (ctx.Options.AccessLog != null)
{ {
logWriter = new StreamWriter(ctx.Options.AccessLog); logWriter = new StreamWriter(ctx.Options.AccessLog);
logObserver = new LogObserverHolder();
ctx.FsClient.SetLocalSystemAccessLogForDebug(true); // ReSharper disable once AccessToDisposedClosure
ctx.FsClient.SetGlobalAccessLogMode(GlobalAccessLogMode.Log); // 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) if (ctx.Options.ResultLog != null)
@ -100,6 +111,11 @@ namespace hactoolnet
} }
finally finally
{ {
if (logObserver != null)
{
ctx.Horizon.Diag.UnregisterLogObserver(logObserver);
}
logWriter?.Dispose(); logWriter?.Dispose();
if (resultLogger != null) if (resultLogger != null)