Skip to content

Commit 6cff8fb

Browse files
author
StArray
committed
feat(android): 新增日志捕获功能并优化本地函数解析
- 添加 LogcatCapture 类实现 logcat 日志异步捕获与写入文件 - 在 Managed.cs 中集成 LogcatCapture,启动日志捕获 - 优化 NativeFuncResolver,增加符号地址搜索和带验证器的字节模式搜索 - 改进 ImGuiInputHandler 中 initializeMotionEvent 地址获取逻辑,增加签名及上下文验证
1 parent 22f1758 commit 6cff8fb

5 files changed

Lines changed: 180 additions & 35 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
namespace StArray.ModManager.Android;
2+
using System;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
public class LogcatCapture
9+
{
10+
private Process _process;
11+
private readonly string _outputFilePath;
12+
private readonly string _logcatArguments;
13+
private CancellationTokenSource _cts;
14+
15+
/// <summary>
16+
/// 初始化 Logcat 捕获器
17+
/// </summary>
18+
/// <param name="outputFilePath">输出文件路径</param>
19+
/// <param name="logcatArguments">logcat 参数,默认为 "-v time"</param>
20+
public LogcatCapture(string outputFilePath, string logcatArguments = "-v time")
21+
{
22+
_outputFilePath = outputFilePath;
23+
_logcatArguments = logcatArguments;
24+
}
25+
26+
/// <summary>
27+
/// 启动 logcat 并异步写入文件
28+
/// </summary>
29+
public async Task StartAsync()
30+
{
31+
_cts = new CancellationTokenSource();
32+
await Task.Run(() => RunLogcat(_cts.Token));
33+
}
34+
35+
/// <summary>
36+
/// 停止 logcat 捕获
37+
/// </summary>
38+
public void Stop()
39+
{
40+
_cts?.Cancel();
41+
try
42+
{
43+
_process?.Kill();
44+
_process?.Dispose();
45+
}
46+
catch { }
47+
}
48+
49+
private void RunLogcat(CancellationToken token)
50+
{
51+
// 确保输出目录存在
52+
var dir = Path.GetDirectoryName(_outputFilePath);
53+
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
54+
Directory.CreateDirectory(dir);
55+
56+
var startInfo = new ProcessStartInfo
57+
{
58+
FileName = "logcat",
59+
Arguments = _logcatArguments,
60+
RedirectStandardOutput = true,
61+
RedirectStandardError = true,
62+
UseShellExecute = false,
63+
CreateNoWindow = true,
64+
StandardOutputEncoding = System.Text.Encoding.UTF8
65+
};
66+
67+
using (_process = new Process { StartInfo = startInfo })
68+
{
69+
_process.Start();
70+
71+
// 异步读取 stdout 并写入文件
72+
Task stdoutTask = ReadStreamAsync(_process.StandardOutput, token);
73+
Task stderrTask = ReadStreamAsync(_process.StandardError, token);
74+
75+
// 等待进程退出或取消
76+
Task.WaitAny(
77+
Task.Run(() => _process.WaitForExit(), token),
78+
Task.Delay(Timeout.Infinite, token)
79+
);
80+
81+
// 确保读取完成
82+
Task.WaitAll(stdoutTask, stderrTask);
83+
}
84+
}
85+
86+
private async Task ReadStreamAsync(StreamReader reader, CancellationToken token)
87+
{
88+
using var writer = new StreamWriter(_outputFilePath, append: true) { AutoFlush = true };
89+
while (!reader.EndOfStream && !token.IsCancellationRequested)
90+
{
91+
var line = await reader.ReadLineAsync();
92+
if (line != null)
93+
{
94+
await writer.WriteLineAsync(line);
95+
// 可选:同时输出到控制台调试
96+
// Console.WriteLine(line);
97+
}
98+
}
99+
}
100+
}

‎StArray.ModManager.Android/Managed.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public static int Entry(int argc, IntPtr argv)
4646
string modsPath = args.Length > 0
4747
? args[0]
4848
: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "..", "mods");
49+
var logcat = new LogcatCapture(Path.Combine(Path.GetDirectoryName(modsPath), "logcat-output.txt"));
50+
logcat.StartAsync();
4951
AssemblyPath = Path.Combine(Path.GetDirectoryName(modsPath), "manager", typeof(Managed).Namespace + ".dll");
5052
if (!Directory.Exists(modsPath))
5153
Directory.CreateDirectory(modsPath);

‎StArray.ModManager.Android/UI/ImGuiInputHandler.cs‎

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,55 @@ public unsafe static bool OnInitializeMotionEvent(void* @event, void* message)
9999

100100
private static nint GetInitializeMotionEventAddress()
101101
{
102-
NativeFuncResolver resolver = new("/system/lib64/libinput.so");
103-
string sigHex = "e8 0f 19 fc fd 7b 01 a9 fc 6f 02 a9 fa 67 03 a9 " +
104-
"f8 5f 04 a9 f6 57 05 a9 f4 4f 06 a9 " +
105-
"fd 43 00 91 ?? ?? ?? ?? " + // add x29, sp + sub sp (栈帧大小可变)
106-
"58 d0 3b d5 " + // mrs x24, tpidr_el0
107-
"?? ?? ?? ?? " + // ldr x8, [x24, #off]
108-
"?? ?? ?? ?? " + // stur x8, [x29, #off]
109-
"39 0c 40 b9 " + // ldr w25, [x1, #0xc]
110-
"?? ?? ?? ?? " + // cbz w25
111-
"37 f3 7d d3"; // lsl x23, x25, #3
112-
113-
var addr = resolver.Resolve("_ZN7android13InputConsumer21initializeMotionEventEPNS_11MotionEventEPKNS_12InputMessageE",
114-
NativeFuncResolver.ParseHexPattern(sigHex));
115-
Logger.Error(nameof(ImGuiInputHandler), $"GetInitializeMotionEventAddress: {addr}");
116-
return addr;
102+
byte?[] sig = NativeFuncResolver.ParseHexPattern(
103+
"e8 0f 19 fc fd 7b 01 a9 fc 6f 02 a9 fa 67 03 a9 " +
104+
"f8 5f 04 a9 f6 57 05 a9 f4 4f 06 a9 fd 43 00 91");
105+
using var r = new NativeFuncResolver("/system/lib64/libinput.so");
106+
long rva = r.FindSymbolRva("_ZN7android13InputConsumer21initializeMotionEventEPNS_11MotionEventEPKNS_12InputMessageE");
107+
try
108+
{
109+
if (rva < 0)
110+
{
111+
var text = r.TextBytes;
112+
long textAddr = r.TextBaseAddress;
113+
int pos = 0;
114+
while ((pos = NativeFuncResolver.Search(text, sig, pos)) >= 0)
115+
{
116+
int ctxEnd = Math.Min(pos + 60, text.Length);
117+
bool hasMrs = false, hasLdr = false;
118+
for (int j = pos + 32; j <= ctxEnd - 4; j += 4)
119+
{
120+
int inst = BitConverter.ToInt32(text.AsSpan(j));
121+
if (!hasMrs && (inst & 0xffffffe0) == 0xd53bd040)
122+
hasMrs = true;
123+
if (!hasLdr && (inst & 0xffe003e0) == 0xb9400020)
124+
{
125+
int imm12 = (inst >> 10) & 0xfff;
126+
if (imm12 * 4 == 0xc) hasLdr = true;
127+
}
128+
if (hasMrs && hasLdr) break;
129+
}
130+
if (hasMrs && hasLdr)
131+
{
132+
rva = textAddr + pos;
133+
if (pos >= 4)
134+
{
135+
int prev = BitConverter.ToInt32(text.AsSpan(pos - 4));
136+
if (prev == unchecked((int)0xd503233f)) rva -= 4;
137+
}
138+
break;
139+
}
140+
pos++;
141+
}
142+
if (rva < 0) throw new KeyNotFoundException("initializeMotionEvent not found by signature.");
143+
}
144+
}
145+
catch (Exception ex)
146+
{
147+
Logger.Error(nameof(ImGuiInputHandler), ex.ToString());
148+
}
149+
r.Load();
150+
return r.GetFuncPtr(rva);
117151
}
118152

119153
private static JavaClass? s_utilsClass;

‎StArray.ModManager/Runtime/NativeFuncResolver.cs‎

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
2+
namespace StArray.ModManager.Runtime;
13
using System.Runtime.InteropServices;
24
using System.Text;
35
using StArray.ModManager.Native;
46

5-
namespace StArray.ModManager.Runtime;
7+
/// <summary>搜索回调:在指定位置验证上下文,返回是否接受此匹配</summary>
8+
public delegate bool MatchValidator(int offsetInText, byte[] textData, long textAddr);
69

710
public class NativeFuncResolver : IDisposable
811
{
@@ -31,8 +34,11 @@ public NativeFuncResolver(string elfPath)
3134
out _dynStrAddr, out _dynStrOffset, out _dynStrSize);
3235
}
3336

34-
private ReadOnlySpan<byte> TextSpan =>
37+
public byte[] TextBytes =>
3538
_textBytes ??= _fileData.AsSpan((int)_textOffset, (int)_textSize).ToArray();
39+
public long TextBaseAddress => _textAddr;
40+
41+
private ReadOnlySpan<byte> TextSpan => TextBytes;
3642

3743
// ===================== 统一入口 =====================
3844

@@ -153,31 +159,34 @@ public long[] FindAllRva(params byte?[] pattern)
153159
return result.ToArray();
154160
}
155161

162+
/// <summary>带验证器的搜索:找到 byte 模式后调用 validator 决定是否接受</summary>
163+
public long[] FindAllRva(byte?[] pattern, MatchValidator validator)
164+
{
165+
var text = TextSpan;
166+
var result = new List<long>();
167+
int pos = 0;
168+
while ((pos = Search(text, pattern, pos)) >= 0)
169+
{
170+
if (validator(pos, _fileData, _textAddr))
171+
result.Add(_textAddr + pos);
172+
pos++;
173+
}
174+
return result.ToArray();
175+
}
176+
156177
// ===================== 库加载 =====================
157178

158179
public void Load()
159180
{
160181
if (_loadedHandle != IntPtr.Zero) return;
161-
if (OperatingSystem.IsWindows()) _loadedHandle = NativeLibrary.Load(FilePath);
162-
else
163-
{
164-
var filename = Path.GetFileName(FilePath);
165-
_loadedHandle = DL.GetBaseAddress(filename);
166-
if (_loadedHandle == IntPtr.Zero)
167-
{
168-
DL.Open(filename, DL.RTLDFlags.RTLD_LAZY);
169-
_loadedHandle = DL.GetBaseAddress(filename);
170-
}
171-
if (_loadedHandle == IntPtr.Zero)
172-
throw new DllNotFoundException($"Failed to get base address of '{FilePath}'");
173-
}
182+
_loadedHandle = DL.Open(FilePath, DL.RTLDFlags.RTLD_NOW | DL.RTLDFlags.RTLD_LOCAL);
174183
}
175184

176185
public IntPtr GetFuncPtr(long rva)
177186
{
178187
if (_loadedHandle == IntPtr.Zero)
179188
throw new InvalidOperationException("Library not loaded. Call Load() first.");
180-
return new IntPtr((long)_loadedHandle + rva);
189+
return IntPtr.Add(_loadedHandle, (int)rva);
181190
}
182191

183192
public void Dispose()
@@ -202,7 +211,7 @@ public void Dispose()
202211

203212
// ===================== 私有方法 =====================
204213

205-
private static int Search(ReadOnlySpan<byte> data, byte?[] pattern, int start = 0)
214+
public static int Search(ReadOnlySpan<byte> data, byte?[] pattern, int start = 0)
206215
{
207216
for (int i = start; i <= data.Length - pattern.Length; i++)
208217
{
@@ -264,4 +273,4 @@ string SecName(int idx)
264273
if (dynSymSize == 0) throw new InvalidDataException(".dynsym section not found");
265274
if (dynStrSize == 0) throw new InvalidDataException(".dynstr section not found");
266275
}
267-
}
276+
}

‎version.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"version": "1.0.5",
3-
"versionCode": 8,
3+
"versionCode": 9,
44
"manager": "https://github.com/StArraySharp/StArray.ModManager/releases/download/1.0.5/modmanager.zip",
5-
"sha256": "5ed3af90040e4f69e1bf03a007120c042e385e795c97686f8b2ad40c66f7cbbe",
5+
"sha256": "3f435642aaeaaf78aa0fa379249c55358433946d2a1b8ee07d6f69131d03c65d",
66
"entryAssembly": "StArray.ModManager.Android.dll",
77
"entryMethod": "StArray.ModManager.Android.Managed::Entry"
88
}

0 commit comments

Comments
 (0)