From 86ce76c7edfafc2bc8dd0a5c2eef928e5a589d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=8B=A4=E6=B0=91?= Date: Mon, 22 Jun 2026 18:23:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(proguard):=20=E6=94=AF=E6=8C=81=20R8=20?= =?UTF-8?q?=E5=86=85=E8=81=94=E8=A1=8C=E6=98=A0=E5=B0=84=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=20r8-map-id=20=E5=A0=86=E6=A0=88?= =?UTF-8?q?=E5=B8=A7=E6=97=A0=E6=B3=95=E5=8F=8D=E6=B7=B7=E6=B7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 解析器新增 R8 内联格式支持:start:end:returnType OrigClass.method(args):origStart:origEnd -> obf 保存原始类名和方法名,而非只保存行号。 2. deobfuscateStack 正则从 [\w.]+ 改为 [^:)]+ 以匹配 r8-map-id-xxx 格式的 source 文件字段。 3. deobfuscateFrame 优先按行号范围查找 R8 内联条目,从中还原真实的类名、方法名和行号。 Co-Authored-By: Claude Sonnet 4.6 --- src/parsers/proguard.ts | 111 ++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 32 deletions(-) diff --git a/src/parsers/proguard.ts b/src/parsers/proguard.ts index efd4e0a..3f1e1b2 100644 --- a/src/parsers/proguard.ts +++ b/src/parsers/proguard.ts @@ -1,11 +1,12 @@ /** - * Android ProGuard mapping.txt 解析器 + * Android ProGuard / R8 mapping.txt 解析器 * - * mapping.txt 格式示例: + * 标准 ProGuard 格式: * com.example.MyClass -> a.b.c: - * int myField -> a * void myMethod(int) -> a - * 42:42:void otherMethod():100:100 -> b + * + * R8 内联格式(行号范围 + 原始类名): + * 13:22:java.lang.Object com.example.Foo.bar():141:141 -> invokeSuspend */ export interface ProguardMapping { @@ -23,13 +24,17 @@ export interface MethodMapping { } export interface LineMapping { + obfuscatedLineStart: number + obfuscatedLineEnd: number originalLine: number - obfuscatedLine: number originalFile: string + // R8 内联:原始类名和方法名(与 obfuscatedClass 可能不同) + originalClass?: string + originalMethod?: string } /** - * 解析 ProGuard mapping.txt 文件 + * 解析 ProGuard / R8 mapping.txt 文件 */ export function parseProguardMapping(content: string): Map { const mappings = new Map() @@ -37,14 +42,11 @@ export function parseProguardMapping(content: string): Map a.b.c: @@ -65,20 +67,43 @@ export function parseProguardMapping(content: string): Map b + // R8 内联行:startLine:endLine:returnType OrigClass.method(args):origStart:origEnd -> obfuscatedMethod + // 示例: 13:22:java.lang.Object com.szyx.app.ywx.Foo$Bar.invokeSuspend(java.lang.Object):141:141 -> invokeSuspend + const r8InlineMatch = line.match( + /^\s+(\d+):(\d+):(\S+)\s+([\w$.]+)\.([\w$]+)\(([^)]*)\):(\d+):(\d+)\s+->\s+(\S+)$/ + ) + if (r8InlineMatch) { + const obfStart = parseInt(r8InlineMatch[1], 10) + const obfEnd = parseInt(r8InlineMatch[2], 10) + const originalClass = r8InlineMatch[4] + const originalMethod = r8InlineMatch[5] + const origLine = parseInt(r8InlineMatch[7], 10) + currentClass.lines.push({ + obfuscatedLineStart: obfStart, + obfuscatedLineEnd: obfEnd, + originalLine: origLine, + originalFile: currentOriginalClass, + originalClass, + originalMethod, + }) + continue + } + + // ProGuard 行号映射(无原始类信息):startLine:endLine:desc:origStart:origEnd -> obfuscatedMethod const lineMatch = line.match( /^\s+(\d+):(\d+):([^:]+):(\d+):(\d+)\s+->\s+(\S+)$/ ) if (lineMatch) { currentClass.lines.push({ + obfuscatedLineStart: parseInt(lineMatch[1], 10), + obfuscatedLineEnd: parseInt(lineMatch[2], 10), originalLine: parseInt(lineMatch[4], 10), - obfuscatedLine: parseInt(lineMatch[1], 10), originalFile: currentOriginalClass, }) continue } - // 方法映射行:void myMethod(int) -> a + // 方法映射行:[startLine:endLine:]returnType methodName(args) -> obfuscatedName const methodMatch = line.match( /^\s+(?:(\d+):(\d+):)?(\S+)\s+(\S+)\(([^)]*)\)\s+->\s+(\S+)$/ ) @@ -89,7 +114,6 @@ export function parseProguardMapping(content: string): Map m.obfuscatedMethod === obfuscatedMethod) - - // 查找行号映射 - let originalLine = obfuscatedLine - const lineMapping = mapping.lines.find(l => l.obfuscatedLine === obfuscatedLine) - if (lineMapping) { - originalLine = lineMapping.originalLine - } else if (methodMapping) { - // 使用方法的行号范围估算 - originalLine = methodMapping.originalLine + // 优先:在行号范围内查找 R8 内联条目 + const r8Entry = mapping.lines.find( + l => + l.originalClass !== undefined && + l.originalMethod === obfuscatedMethod && + obfuscatedLine >= l.obfuscatedLineStart && + obfuscatedLine <= l.obfuscatedLineEnd + ) + if (r8Entry && r8Entry.originalClass) { + const cls = r8Entry.originalClass + return { + class: cls, + method: r8Entry.originalMethod!, + line: r8Entry.originalLine, + file: `${cls.split('.').pop()}.kt`, + } } + // 次选:行号范围条目(无内联类信息) + const lineEntry = mapping.lines.find( + l => obfuscatedLine >= l.obfuscatedLineStart && obfuscatedLine <= l.obfuscatedLineEnd + ) + if (lineEntry) { + const methodEntry = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod) + const cls = mapping.originalClass + return { + class: cls, + method: methodEntry?.originalMethod || obfuscatedMethod, + line: lineEntry.originalLine, + file: `${cls.split('.').pop()}.kt`, + } + } + + // 兜底:仅匹配方法名 + const methodMapping = mapping.methods.find(m => m.obfuscatedMethod === obfuscatedMethod) + const cls = mapping.originalClass return { - class: mapping.originalClass, + class: cls, method: methodMapping?.originalMethod || obfuscatedMethod, - line: originalLine, - file: `${mapping.originalClass.split('.').pop()}.java`, + line: methodMapping?.originalLine || obfuscatedLine, + file: `${cls.split('.').pop()}.kt`, } } @@ -141,9 +187,10 @@ export function deobfuscateStack( const result: string[] = [] for (const line of lines) { - // 匹配 "at com.example.a.b(MyFile.java:42)" 格式 + // 匹配 "at ClassName.method(File:line)" 格式。 + // File 部分允许连字符,以兼容 R8 的 r8-map-id-xxx 格式。 const match = line.match( - /at\s+([\w.$]+)\.([\w$]+)\(([\w.]+):(\d+)\)/ + /at\s+([\w.$]+)\.([\w$]+)\(([^:)]+):(\d+)\)/ ) if (match) { @@ -160,7 +207,7 @@ export function deobfuscateStack( if (deobfuscated) { result.push( - `at ${deobfuscated.class}.${deobfuscated.method}(${deobfuscated.file}:${deobfuscated.line})` + `\tat ${deobfuscated.class}.${deobfuscated.method}(${deobfuscated.file}:${deobfuscated.line})` ) continue }