mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add the logger from nn::diag
This commit is contained in:
parent
7950a91dd0
commit
39977d8e90
13 changed files with 434 additions and 15 deletions
38
src/LibHac/Diag/DiagClient.cs
Normal file
38
src/LibHac/Diag/DiagClient.cs
Normal 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;
|
||||
}
|
||||
}
|
145
src/LibHac/Diag/Impl/ObserverManager.cs
Normal file
145
src/LibHac/Diag/Impl/ObserverManager.cs
Normal 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
47
src/LibHac/Diag/Log.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
110
src/LibHac/Diag/LogObserver.cs
Normal file
110
src/LibHac/Diag/LogObserver.cs
Normal 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;
|
||||
}
|
||||
}
|
38
src/LibHac/Diag/LogTypes.cs
Normal file
38
src/LibHac/Diag/LogTypes.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ namespace LibHac
|
|||
{
|
||||
_currentProcessId = InitialProcessCountMax;
|
||||
|
||||
TickGenerator = config.TickGenerator;
|
||||
TickGenerator = config.TickGenerator ?? new DefaultTickGenerator();
|
||||
ServiceManager = new ServiceManager();
|
||||
|
||||
LoaderClient = CreatePrivilegedHorizonClient();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -14,9 +14,4 @@ namespace LibHac
|
|||
/// </summary>
|
||||
public ITickGenerator? TickGenerator { get; set; }
|
||||
}
|
||||
|
||||
public interface ITickGenerator
|
||||
{
|
||||
Tick GetCurrentTick();
|
||||
}
|
||||
}
|
||||
|
|
24
src/LibHac/Os/ITickGenerator.cs
Normal file
24
src/LibHac/Os/ITickGenerator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue