Skip to content

Commit b217dca

Browse files
authored
[Microsoft.Android.Runtime.NativeAOT] Parse debug.dotnet.log (#9824)
Context: https://github.com/dotnet/java-interop/blob/9dea87dc1f3052ed0f9499c9858b27c83e7d139e/samples/Hello-NativeAOTFromAndroid/README.md The NativeAOT sample and related `Microsoft.Android.Runtime.NativeAOT` assembly is based in part on the `Hello-NativeAOTFromAndroid` sample in dotnet/java-interop, which contains this comment about Logging: > The (very!) extensive logging around JNI Global and Local > references mean that this sample should not be used as-is for > startup timing comparison. …because `adb logcat` is absolutely *spammed* by LREF and GREF logs. The "solution" in .NET for Android "proper" is to use the `debug.mono.log` system property to control logging. By default, LREF and GREF logs are disabled, but can be enabled by setting the `debug.mono.log` system property property: adb shell setprop debug.mono.log gref # only grefs adb shell setprop debug.mono.log lref,gref # lrefs & grefs adb shell setprop debug.mono.log all # EVERYTHING Begin plumbing similar support for a new `debug.dotnet.log` system property into `JavaInteropRuntime` via a new `DiagnosticSettings` type. This allows us to e.g. have LREF and GREF logging *disabled by default*, allowing for (somewhat) more representative startup timing comparisons, while allowing these logs to be enabled when needed. adb shell setprop debug.dotnet.log gref # only grefs adb shell setprop debug.dotnet.log lref,gref # only grefs adb shell setprop debug.dotnet.log lref,gref # both, right now Additionally, `=PATH` can be appended to redirect gref or lref logs to the file `PATH`. Note that `PATH` must be writable by the app, and that the value of `debug.dotnet.log` must be shorter than 92 bytes: adb shell mkdir -p /sdcard/Documents/jonp adb shell chmod 777 /sdcard/Documents/jonp adb shell setprop debug.dotnet.log gref=/sdcard/Documents/jonp/jni.txt,lref=/sdcard/Documents/jonp/jni.txt Note that in the above example on a Pixel 8 with Android 15, `/sdcard/Documents/jonp/jni.txt` can only be written to *once*; subsequent attempts to write to the file will error out, so we'll fallback to writing to `adb logcat`: E NativeAot: Failed to open log file `/sdcard/Documents/jonp/jni.txt`: System.UnauthorizedAccessException: UnauthorizedAccess_IODenied_Path, /sdcard/Documents/jonp/jni.txt E NativeAot: ---> System.IO.IOException: Permission denied … D NativeAot:LREF: +l+ lrefc 1 handle 0x723ff2501d/L from thread ''(1) …
1 parent bae53be commit b217dca

File tree

3 files changed

+143
-3
lines changed

3 files changed

+143
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System.IO;
2+
using System.Text;
3+
using System.Runtime.InteropServices;
4+
5+
using Java.Interop;
6+
7+
namespace Microsoft.Android.Runtime;
8+
9+
struct DiagnosticSettings {
10+
11+
public bool LogJniLocalReferences;
12+
private string? LrefPath;
13+
14+
public bool LogJniGlobalReferences;
15+
private string? GrefPath;
16+
17+
private TextWriter? GrefLrefLog;
18+
19+
20+
public TextWriter? GrefLog {
21+
get {
22+
if (!LogJniGlobalReferences) {
23+
return null;
24+
}
25+
return ((LrefPath != null && LrefPath == GrefPath)
26+
? GrefLrefLog ??= CreateWriter (LrefPath)
27+
: null)
28+
??
29+
((GrefPath != null)
30+
? CreateWriter (GrefPath)
31+
: null)
32+
??
33+
new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF");
34+
}
35+
}
36+
37+
public TextWriter? LrefLog {
38+
get {
39+
if (!LogJniLocalReferences) {
40+
return null;
41+
}
42+
return ((LrefPath != null && LrefPath == GrefPath)
43+
? GrefLrefLog ??= CreateWriter (LrefPath)
44+
: null)
45+
??
46+
((LrefPath != null)
47+
? CreateWriter (LrefPath)
48+
: null)
49+
??
50+
new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF");
51+
}
52+
}
53+
54+
TextWriter? CreateWriter (string path)
55+
{
56+
try {
57+
return File.CreateText (path);
58+
}
59+
catch (Exception e) {
60+
AndroidLog.Print (AndroidLogLevel.Error, "NativeAot", $"Failed to open log file `{path}`: {e}");
61+
return null;
62+
}
63+
}
64+
65+
public void AddDebugDotnetLog ()
66+
{
67+
Span<byte> value = stackalloc byte [RuntimeNativeMethods.PROP_VALUE_MAX];
68+
if (!RuntimeNativeMethods.TryGetSystemProperty ("debug.dotnet.log"u8, ref value)) {
69+
return;
70+
}
71+
AddParse (value);
72+
}
73+
74+
void AddParse (ReadOnlySpan<byte> value)
75+
{
76+
while (TryGetNextValue (ref value, out var v)) {
77+
if (v.SequenceEqual ("lref"u8)) {
78+
LogJniLocalReferences = true;
79+
}
80+
else if (v.StartsWith ("lref="u8)) {
81+
LogJniLocalReferences = true;
82+
var path = v.Slice ("lref=".Length);
83+
LrefPath = Encoding.UTF8.GetString (path);
84+
}
85+
else if (v.SequenceEqual ("gref"u8)) {
86+
LogJniGlobalReferences = true;
87+
}
88+
else if (v.StartsWith ("gref="u8)) {
89+
LogJniGlobalReferences = true;
90+
var path = v.Slice ("gref=".Length);
91+
GrefPath = Encoding.UTF8.GetString (path);
92+
}
93+
else if (v.SequenceEqual ("all"u8)) {
94+
LogJniLocalReferences = true;
95+
LogJniGlobalReferences = true;
96+
}
97+
}
98+
99+
bool TryGetNextValue (ref ReadOnlySpan<byte> value, out ReadOnlySpan<byte> next)
100+
{
101+
if (value.Length == 0) {
102+
next = default;
103+
return false;
104+
}
105+
int c = value.IndexOf ((byte) ',');
106+
if (c >= 0) {
107+
next = value.Slice (0, c);
108+
value = value.Slice (c + 1);
109+
}
110+
else {
111+
next = value;
112+
value = default;
113+
}
114+
return true;
115+
}
116+
}
117+
}
118+
119+
static partial class RuntimeNativeMethods {
120+
121+
[LibraryImport ("c", EntryPoint="__system_property_get")]
122+
static private partial int system_property_get (ReadOnlySpan<byte> name, Span<byte> value);
123+
124+
internal const int PROP_VALUE_MAX = 92;
125+
126+
internal static bool TryGetSystemProperty (ReadOnlySpan<byte> name, ref Span<byte> value)
127+
{
128+
int len = system_property_get (name, value);
129+
if (len <= 0) {
130+
return false;
131+
}
132+
133+
value = value.Slice (0, len);
134+
return true;
135+
}
136+
}

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Microsoft.Android.Runtime;
66

7-
static class JavaInteropRuntime
7+
static partial class JavaInteropRuntime
88
{
99
static JniRuntime? runtime;
1010

@@ -34,14 +34,17 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
3434
static void init (IntPtr jnienv, IntPtr klass)
3535
{
3636
try {
37+
var settings = new DiagnosticSettings ();
38+
settings.AddDebugDotnetLog ();
39+
3740
var typeManager = new NativeAotTypeManager ();
3841
var options = new NativeAotRuntimeOptions {
3942
EnvironmentPointer = jnienv,
4043
TypeManager = typeManager,
4144
ValueManager = new NativeAotValueManager (typeManager),
4245
UseMarshalMemberBuilder = false,
43-
JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
44-
JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),
46+
JniGlobalReferenceLogWriter = settings.GrefLog,
47+
JniLocalReferenceLogWriter = settings.LrefLog,
4548
};
4649
runtime = options.CreateJreVM ();
4750

src/Microsoft.Android.Runtime.NativeAOT/Microsoft.Android.Runtime.NativeAOT.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<SignAssembly>true</SignAssembly>
1212
<OutputPath>$(_MonoAndroidNETDefaultOutDir)</OutputPath>
1313
<RootNamespace>Microsoft.Android.Runtime</RootNamespace>
14+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1415
</PropertyGroup>
1516

1617
<ItemGroup>

0 commit comments

Comments
 (0)