misc: remote anr util

This commit is contained in:
yicheng 2021-10-04 12:27:46 +08:00
parent 32b8569c7e
commit f4614a3738
No known key found for this signature in database
GPG Key ID: 7CF411A6623B1C0A
6 changed files with 1 additions and 530 deletions

View File

@ -7,9 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
0AE14FEF25A35685007EF89B /* BSBacktraceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14FEC25A35685007EF89B /* BSBacktraceLogger.m */; };
0AE14FF025A35685007EF89B /* AnrDetectUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14FED25A35685007EF89B /* AnrDetectUtil.swift */; };
0AE14FF125A35685007EF89B /* AnrDetectThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14FEE25A35685007EF89B /* AnrDetectThread.swift */; };
12E1BA6D1DA59D9C2D48A175 /* libPods-ClashX Pro.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D4E9113CB2DD62F13AD213C /* libPods-ClashX Pro.a */; };
4913C82321157D0200F6B87C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913C82221157D0200F6B87C /* Notification.swift */; };
491E6203258A424D00313AEF /* CommonUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 491E61FD258A424500313AEF /* CommonUtils.m */; };
49228457270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49228456270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift */; };
@ -125,10 +123,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0AE14FEB25A35685007EF89B /* BSBacktraceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSBacktraceLogger.h; sourceTree = "<group>"; };
0AE14FEC25A35685007EF89B /* BSBacktraceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSBacktraceLogger.m; sourceTree = "<group>"; };
0AE14FED25A35685007EF89B /* AnrDetectUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnrDetectUtil.swift; sourceTree = "<group>"; };
0AE14FEE25A35685007EF89B /* AnrDetectThread.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnrDetectThread.swift; sourceTree = "<group>"; };
4913C82221157D0200F6B87C /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
491E61FC258A424500313AEF /* CommonUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonUtils.h; sourceTree = "<group>"; };
491E61FD258A424500313AEF /* CommonUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommonUtils.m; sourceTree = "<group>"; };
@ -246,25 +240,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0AE14FE925A35685007EF89B /* ANR */ = {
isa = PBXGroup;
children = (
0AE14FEA25A35685007EF89B /* Backtrace */,
0AE14FED25A35685007EF89B /* AnrDetectUtil.swift */,
0AE14FEE25A35685007EF89B /* AnrDetectThread.swift */,
);
path = ANR;
sourceTree = "<group>";
};
0AE14FEA25A35685007EF89B /* Backtrace */ = {
isa = PBXGroup;
children = (
0AE14FEB25A35685007EF89B /* BSBacktraceLogger.h */,
0AE14FEC25A35685007EF89B /* BSBacktraceLogger.m */,
);
path = Backtrace;
sourceTree = "<group>";
};
4913C82021157CEB00F6B87C /* Macro */ = {
isa = PBXGroup;
children = (
@ -369,7 +344,6 @@
isa = PBXGroup;
children = (
4929F683258CE07500A435F6 /* UserDefaultWrapper.swift */,
0AE14FE925A35685007EF89B /* ANR */,
F976275D23634F18000EDEFE /* LoginServiceKit */,
49722FE9211F338B00650A41 /* Witness */,
);
@ -706,7 +680,6 @@
files = (
49ABB749236B0F9E00535CD7 /* UnsafePointer+bridge.swift in Sources */,
499A485522ED707300F6C675 /* RemoteConfigViewController.swift in Sources */,
0AE14FF125A35685007EF89B /* AnrDetectThread.swift in Sources */,
49CF3B5C20CE8068001EBF94 /* ClashResourceManager.swift in Sources */,
4952C3D02117027C004A4FA8 /* ConfigFileManager.swift in Sources */,
49BC061C212931F4005A0FE7 /* AboutViewController.swift in Sources */,
@ -733,12 +706,10 @@
492C4869210EE6B9004554A0 /* ApiRequest.swift in Sources */,
49CF3B6520CEE06C001EBF94 /* ConfigManager.swift in Sources */,
F9E754D0239CC21F00CEE7CC /* WebPortalManager.swift in Sources */,
0AE14FF025A35685007EF89B /* AnrDetectUtil.swift in Sources */,
495BFB8821919B9800C8779D /* RemoteConfigManager.swift in Sources */,
4982F51F2344A216008804B0 /* Cgo+Convert.swift in Sources */,
49722FF1211F338B00650A41 /* Witness.swift in Sources */,
49722FF0211F338B00650A41 /* EventStream.swift in Sources */,
0AE14FEF25A35685007EF89B /* BSBacktraceLogger.m in Sources */,
499A486522EEA3FD00F6C675 /* Array+Safe.swift in Sources */,
F92D0B24236BC12000575E15 /* SavedProxyModel.swift in Sources */,
F92D0B2A236C759100575E15 /* NSTextField+Vibrancy.swift in Sources */,

View File

@ -815,17 +815,6 @@ extension AppDelegate {
}
}
// MARK: ANR
extension AppDelegate {
private func startAnrDetect() {
#if DEBUG
return
#else
AnrDetectUtil.shared.start()
#endif
}
}
// MARK: Memory
extension AppDelegate {

View File

@ -1,81 +0,0 @@
//
// ANRDetectorThread.swift
// MacAnrDemo
//
// Created by miniLV on 2021/1/4.
// Copyright © 2020 miniLV. All rights reserved.
//
public typealias AnrDetectCallBack = (_ report: String) -> Void
class AnrDetectThread: Thread {
private var threshold: Double = 0
private let maxThreadToPrint = 25
private var handler: AnrDetectCallBack?
private var mainThreadId: mach_port_t!
private let semaphore = DispatchSemaphore(value: 0)
private var isMainThreadBlock = false
func start(threshold: Double, handler: @escaping AnrDetectCallBack) {
if Thread.isMainThread {
mainThreadId = mach_thread_self()
} else {
DispatchQueue.main.sync {
mainThreadId = mach_thread_self()
}
}
self.handler = handler
self.threshold = threshold
name = "com.west2online.ClashX.anrDetectThread"
start()
}
override func main() {
while !isCancelled {
isMainThreadBlock = true
DispatchQueue.main.async {
self.isMainThreadBlock = false
self.semaphore.signal()
}
usleep(useconds_t(threshold * 1000000))
if isMainThreadBlock {
didAnr()
}
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
}
}
func didAnr() {
let allThreadTrace = traceAllThread(max: maxThreadToPrint)
let report = "-----------\n main thread is \(Int(mainThreadId))\n\(allThreadTrace)\n---------"
handler?(report)
}
func traceMainThread() -> String {
return BSBacktraceLogger.backtrace(ofMachthread: mainThreadId)
}
func traceAllThread(max: Int = Int.max) -> String {
var threads: thread_act_array_t?
var totalThreadCount = mach_msg_type_number_t()
if task_threads(mach_task_self_, &threads, &totalThreadCount) != KERN_SUCCESS || threads == nil {
return ""
}
let targetCount = min(max, Int(totalThreadCount))
var resultString = "Call Backtrace of \(targetCount)/\(totalThreadCount) threads:\n"
for i in 0..<targetCount {
let index = Int(i)
if let bt = BSBacktraceLogger.backtrace(ofMachthread: threads![index]) {
resultString.append(bt)
}
}
return resultString
}
}

View File

@ -1,30 +0,0 @@
//
// AnrDetectUtil.swift
// Rooms
//
// Created by miniLV on 2021/1/4.
// Copyright © 2020 miniLV. All rights reserved.
//
import Cocoa
class AnrDetectUtil {
static let shared = AnrDetectUtil()
private init() {}
lazy var thread = AnrDetectThread()
func start(threshold: Double = 10) {
thread.start(threshold: threshold) {
[weak thread] allThreadBackTrace in
Logger.log("[ANR] \(allThreadBackTrace)", level: .error)
thread?.cancel()
}
}
func stop() {
thread.cancel()
}
}

View File

@ -1,25 +0,0 @@
//
// BSBacktraceLogger.h
// BSBacktraceLogger
//
// Created by 张星宇 on 16/8/27.
// Copyright © 2016年 bestswifter. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <mach/mach.h>
#include <dlfcn.h>
#include <pthread.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <mach-o/dyld.h>
#include <mach-o/nlist.h>
@interface BSBacktraceLogger : NSObject
+ (NSString *)backtraceOfMachthread:(thread_t)thread;
@end

View File

@ -1,353 +0,0 @@
//
// BSBacktraceLogger.m
// BSBacktraceLogger
//
// Created by on 16/8/27.
// Copyright © 2016 bestswifter. All rights reserved.
//
#import "BSBacktraceLogger.h"
#import <mach/mach.h>
#include <dlfcn.h>
#include <pthread.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <mach-o/dyld.h>
#include <mach-o/nlist.h>
#pragma -mark DEFINE MACRO FOR DIFFERENT CPU ARCHITECTURE
#if defined(__arm64__)
#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(3UL))
#define BS_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT
#define BS_THREAD_STATE ARM_THREAD_STATE64
#define BS_FRAME_POINTER __fp
#define BS_STACK_POINTER __sp
#define BS_INSTRUCTION_ADDRESS __pc
#elif defined(__arm__)
#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(1UL))
#define BS_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT
#define BS_THREAD_STATE ARM_THREAD_STATE
#define BS_FRAME_POINTER __r[7]
#define BS_STACK_POINTER __sp
#define BS_INSTRUCTION_ADDRESS __pc
#elif defined(__x86_64__)
#define DETAG_INSTRUCTION_ADDRESS(A) (A)
#define BS_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT
#define BS_THREAD_STATE x86_THREAD_STATE64
#define BS_FRAME_POINTER __rbp
#define BS_STACK_POINTER __rsp
#define BS_INSTRUCTION_ADDRESS __rip
#elif defined(__i386__)
#define DETAG_INSTRUCTION_ADDRESS(A) (A)
#define BS_THREAD_STATE_COUNT x86_THREAD_STATE32_COUNT
#define BS_THREAD_STATE x86_THREAD_STATE32
#define BS_FRAME_POINTER __ebp
#define BS_STACK_POINTER __esp
#define BS_INSTRUCTION_ADDRESS __eip
#endif
#define CALL_INSTRUCTION_FROM_RETURN_ADDRESS(A) (DETAG_INSTRUCTION_ADDRESS((A)) - 1)
#if defined(__LP64__)
#define TRACE_FMT "%-4d%-31s 0x%016lx %s + %lu"
#define POINTER_FMT "0x%016lx"
#define POINTER_SHORT_FMT "0x%lx"
#define BS_NLIST struct nlist_64
#else
#define TRACE_FMT "%-4d%-31s 0x%08lx %s + %lu"
#define POINTER_FMT "0x%08lx"
#define POINTER_SHORT_FMT "0x%lx"
#define BS_NLIST struct nlist
#endif
typedef struct BSStackFrameEntry{
const struct BSStackFrameEntry *const previous;
const uintptr_t return_address;
} BSStackFrameEntry;
@implementation BSBacktraceLogger
+ (NSString *)backtraceOfMachthread:(thread_t)thread {
return _bs_backtraceOfThread(thread);
}
#pragma -mark Get call backtrace of a mach_thread
NSString *_bs_backtraceOfThread(thread_t thread) {
uintptr_t backtraceBuffer[50];
int i = 0;
NSMutableString *resultString = [[NSMutableString alloc] initWithFormat:@"Backtrace of Thread %u:\n", thread];
_STRUCT_MCONTEXT machineContext;
if(!bs_fillThreadStateIntoMachineContext(thread, &machineContext)) {
return [NSString stringWithFormat:@"Fail to get information about thread: %u", thread];
}
const uintptr_t instructionAddress = bs_mach_instructionAddress(&machineContext);
backtraceBuffer[i] = instructionAddress;
++i;
uintptr_t linkRegister = bs_mach_linkRegister(&machineContext);
if (linkRegister) {
backtraceBuffer[i] = linkRegister;
i++;
}
if(instructionAddress == 0) {
return @"Fail to get instruction address";
}
BSStackFrameEntry frame = {0};
const uintptr_t framePtr = bs_mach_framePointer(&machineContext);
if(framePtr == 0 ||
bs_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) {
return @"Fail to get frame pointer";
}
for(; i < 50; i++) {
backtraceBuffer[i] = frame.return_address;
if(backtraceBuffer[i] == 0 ||
frame.previous == 0 ||
bs_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {
break;
}
}
int backtraceLength = i;
Dl_info symbolicated[backtraceLength];
bs_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0);
for (int i = 0; i < backtraceLength; ++i) {
[resultString appendFormat:@"%@", bs_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])];
}
[resultString appendFormat:@"\n"];
return [resultString copy];
}
#pragma -mark GenerateBacbsrackEnrty
NSString* bs_logBacktraceEntry(const int entryNum,
const uintptr_t address,
const Dl_info* const dlInfo) {
char faddrBuff[20];
char saddrBuff[20];
const char* fname = bs_lastPathEntry(dlInfo->dli_fname);
if(fname == NULL) {
sprintf(faddrBuff, POINTER_FMT, (uintptr_t)dlInfo->dli_fbase);
fname = faddrBuff;
}
uintptr_t offset = address - (uintptr_t)dlInfo->dli_saddr;
const char* sname = dlInfo->dli_sname;
if(sname == NULL) {
sprintf(saddrBuff, POINTER_SHORT_FMT, (uintptr_t)dlInfo->dli_fbase);
sname = saddrBuff;
offset = address - (uintptr_t)dlInfo->dli_fbase;
}
return [NSString stringWithFormat:@"%-30s 0x%08" PRIxPTR " %s + %lu\n" ,fname, (uintptr_t)address, sname, offset];
}
const char* bs_lastPathEntry(const char* const path) {
if(path == NULL) {
return NULL;
}
char* lastFile = strrchr(path, '/');
return lastFile == NULL ? path : lastFile + 1;
}
#pragma -mark HandleMachineContext
bool bs_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) {
mach_msg_type_number_t state_count = BS_THREAD_STATE_COUNT;
kern_return_t kr = thread_get_state(thread, BS_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count);
return (kr == KERN_SUCCESS);
}
uintptr_t bs_mach_framePointer(mcontext_t const machineContext){
return machineContext->__ss.BS_FRAME_POINTER;
}
uintptr_t bs_mach_stackPointer(mcontext_t const machineContext){
return machineContext->__ss.BS_STACK_POINTER;
}
uintptr_t bs_mach_instructionAddress(mcontext_t const machineContext){
return machineContext->__ss.BS_INSTRUCTION_ADDRESS;
}
uintptr_t bs_mach_linkRegister(mcontext_t const machineContext){
#if defined(__i386__) || defined(__x86_64__)
return 0;
#else
return machineContext->__ss.__lr;
#endif
}
kern_return_t bs_mach_copyMem(const void *const src, void *const dst, const size_t numBytes){
vm_size_t bytesCopied = 0;
return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied);
}
#pragma -mark Symbolicate
void bs_symbolicate(const uintptr_t* const backtraceBuffer,
Dl_info* const symbolsBuffer,
const int numEntries,
const int skippedEntries){
int i = 0;
if(!skippedEntries && i < numEntries) {
bs_dladdr(backtraceBuffer[i], &symbolsBuffer[i]);
i++;
}
for(; i < numEntries; i++) {
bs_dladdr(CALL_INSTRUCTION_FROM_RETURN_ADDRESS(backtraceBuffer[i]), &symbolsBuffer[i]);
}
}
bool bs_dladdr(const uintptr_t address, Dl_info* const info) {
info->dli_fname = NULL;
info->dli_fbase = NULL;
info->dli_sname = NULL;
info->dli_saddr = NULL;
const uint32_t idx = bs_imageIndexContainingAddress(address);
if(idx == UINT_MAX) {
return false;
}
const struct mach_header* header = _dyld_get_image_header(idx);
const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);
const uintptr_t addressWithSlide = address - imageVMAddrSlide;
const uintptr_t segmentBase = bs_segmentBaseOfImageIndex(idx) + imageVMAddrSlide;
if(segmentBase == 0) {
return false;
}
info->dli_fname = _dyld_get_image_name(idx);
info->dli_fbase = (void*)header;
// Find symbol tables and get whichever symbol is closest to the address.
const BS_NLIST* bestMatch = NULL;
uintptr_t bestDistance = ULONG_MAX;
uintptr_t cmdPtr = bs_firstCmdAfterHeader(header);
if(cmdPtr == 0) {
return false;
}
for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {
const struct load_command* loadCmd = (struct load_command*)cmdPtr;
if(loadCmd->cmd == LC_SYMTAB) {
const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;
const BS_NLIST* symbolTable = (BS_NLIST*)(segmentBase + symtabCmd->symoff);
const uintptr_t stringTable = segmentBase + symtabCmd->stroff;
for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) {
// If n_value is 0, the symbol refers to an external object.
if(symbolTable[iSym].n_value != 0) {
uintptr_t symbolBase = symbolTable[iSym].n_value;
uintptr_t currentDistance = addressWithSlide - symbolBase;
if((addressWithSlide >= symbolBase) &&
(currentDistance <= bestDistance)) {
bestMatch = symbolTable + iSym;
bestDistance = currentDistance;
}
}
}
if(bestMatch != NULL) {
info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);
info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
if(*info->dli_sname == '_') {
info->dli_sname++;
}
// This happens if all symbols have been stripped.
if(info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) {
info->dli_sname = NULL;
}
break;
}
}
cmdPtr += loadCmd->cmdsize;
}
return true;
}
uintptr_t bs_firstCmdAfterHeader(const struct mach_header* const header) {
switch(header->magic) {
case MH_MAGIC:
case MH_CIGAM:
return (uintptr_t)(header + 1);
case MH_MAGIC_64:
case MH_CIGAM_64:
return (uintptr_t)(((struct mach_header_64*)header) + 1);
default:
return 0; // Header is corrupt
}
}
uint32_t bs_imageIndexContainingAddress(const uintptr_t address) {
const uint32_t imageCount = _dyld_image_count();
const struct mach_header* header = 0;
for(uint32_t iImg = 0; iImg < imageCount; iImg++) {
header = _dyld_get_image_header(iImg);
if(header != NULL) {
// Look for a segment command with this address within its range.
uintptr_t addressWSlide = address - (uintptr_t)_dyld_get_image_vmaddr_slide(iImg);
uintptr_t cmdPtr = bs_firstCmdAfterHeader(header);
if(cmdPtr == 0) {
continue;
}
for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) {
const struct load_command* loadCmd = (struct load_command*)cmdPtr;
if(loadCmd->cmd == LC_SEGMENT) {
const struct segment_command* segCmd = (struct segment_command*)cmdPtr;
if(addressWSlide >= segCmd->vmaddr &&
addressWSlide < segCmd->vmaddr + segCmd->vmsize) {
return iImg;
}
}
else if(loadCmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64* segCmd = (struct segment_command_64*)cmdPtr;
if(addressWSlide >= segCmd->vmaddr &&
addressWSlide < segCmd->vmaddr + segCmd->vmsize) {
return iImg;
}
}
cmdPtr += loadCmd->cmdsize;
}
}
}
return UINT_MAX;
}
uintptr_t bs_segmentBaseOfImageIndex(const uint32_t idx) {
const struct mach_header* header = _dyld_get_image_header(idx);
// Look for a segment command and return the file image address.
uintptr_t cmdPtr = bs_firstCmdAfterHeader(header);
if(cmdPtr == 0) {
return 0;
}
for(uint32_t i = 0;i < header->ncmds; i++) {
const struct load_command* loadCmd = (struct load_command*)cmdPtr;
if(loadCmd->cmd == LC_SEGMENT) {
const struct segment_command* segmentCmd = (struct segment_command*)cmdPtr;
if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) {
return segmentCmd->vmaddr - segmentCmd->fileoff;
}
}
else if(loadCmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64* segmentCmd = (struct segment_command_64*)cmdPtr;
if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) {
return (uintptr_t)(segmentCmd->vmaddr - segmentCmd->fileoff);
}
}
cmdPtr += loadCmd->cmdsize;
}
return 0;
}
@end