支持平台: - RN/Electron/Vue3 (JS Sourcemap) - Android (ProGuard mapping) - iOS (dSYM) - Flutter (Debug Symbols) Co-Authored-By: Claude <noreply@anthropic.com>
86 行
2.2 KiB
TypeScript
86 行
2.2 KiB
TypeScript
/**
|
|
* iOS dSYM 符号化解析器
|
|
*
|
|
* 使用 atos 命令进行符号化:
|
|
* atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -arch arm64 -l 0x102345000 0x102345678
|
|
*/
|
|
|
|
import { execSync } from 'child_process'
|
|
import { StackFrame } from './stack-parser'
|
|
|
|
export interface DsymConfig {
|
|
/** dSYM 文件路径 */
|
|
dsymPath: string
|
|
/** 应用加载地址 */
|
|
loadAddress: string
|
|
/** 架构 (arm64, x86_64) */
|
|
arch?: string
|
|
}
|
|
|
|
/**
|
|
* 使用 atos 命令符号化 iOS 堆栈
|
|
*/
|
|
export function symbolicateWithDsym(
|
|
frames: StackFrame[],
|
|
config: DsymConfig
|
|
): string[] {
|
|
const { dsymPath, loadAddress, arch = 'arm64' } = config
|
|
|
|
// 提取需要符号化的地址
|
|
const addresses = frames
|
|
.filter(f => f.file === '<binary>')
|
|
.map(f => `0x${f.column.toString(16)}`)
|
|
|
|
if (addresses.length === 0) {
|
|
return frames.map(f => `${f.function} (${f.file}:${f.line})`)
|
|
}
|
|
|
|
try {
|
|
// 调用 atos 命令
|
|
const cmd = `atos -o "${dsymPath}" -arch ${arch} -l ${loadAddress} ${addresses.join(' ')}`
|
|
const output = execSync(cmd, { encoding: 'utf-8', timeout: 30000 })
|
|
|
|
const symbolicated = output.trim().split('\n')
|
|
let addrIndex = 0
|
|
|
|
return frames.map(f => {
|
|
if (f.file === '<binary>' && addrIndex < symbolicated.length) {
|
|
return symbolicated[addrIndex++]
|
|
}
|
|
return `${f.function} (${f.file}:${f.line})`
|
|
})
|
|
} catch (err) {
|
|
console.error('atos symbolication failed:', err)
|
|
return frames.map(f => `${f.function} (${f.file}:${f.line})`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 解析 iOS 崩溃日志中的线程帧
|
|
*
|
|
* 格式示例:
|
|
* 0 MyApp 0x0000000102345678 myFunction + 123
|
|
* 1 MyApp 0x0000000102345678 myFunction + 123
|
|
*/
|
|
export function parseIOSCrashFrames(crashLog: string): StackFrame[] {
|
|
const frames: StackFrame[] = []
|
|
const lines = crashLog.split('\n')
|
|
|
|
for (const line of lines) {
|
|
const match = line.trim().match(
|
|
/(\d+)\s+(\S+)\s+(0x[\da-f]+)\s+(\S+)\s+\+\s+(\d+)/
|
|
)
|
|
|
|
if (match) {
|
|
frames.push({
|
|
function: match[4],
|
|
file: match[2],
|
|
line: 0,
|
|
column: parseInt(match[5], 10),
|
|
})
|
|
}
|
|
}
|
|
|
|
return frames
|
|
}
|