iOS系统崩溃的捕获

这篇具有很好参考价值的文章主要介绍了iOS系统崩溃的捕获。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

iOS系统崩溃的捕获

相信大家在开发iOS程序的时候肯定写过各种Bug,而其中最为严重的Bug就是会导致崩溃的Bug(一般来说妥妥的P1级)。在应用软件大大小小的各种异常中,崩溃确实是最让人难以接受的行为。毕竟崩溃意味着用户将丢失应用程序运行中的所有上下文环境,丢失其所有未保存的数据,会带给用户最糟糕的使用体验。

所以在应用的开发阶段,我们一定要杜绝此类可能造成应用程序无法使用的崩溃。但是很多崩溃并不是自己在开发阶段就能预料到的,此时就需要一种能够线上获取崩溃日志并且上报的机制,这就是所谓的崩溃捕获和上报体系。

今天我们不研究SuperApp中的崩溃上报,主要研究一下崩溃捕获是如何实现的。

iOS系统中如何捕获崩溃

首先,iOS系统中,并没有通用的能够捕获所有崩溃的处理函数。捕获崩溃主要有以下三种方式:

  • NSSetUncaughtExceptionHandler
  • Unix Signal捕获函数
  • Mach(读音为[mʌk])异常捕获函数

关于如何用上述的方式捕获崩溃,不是本次分享的重点,大家可以自行查阅博客中的代码。我们主要需要理解的是这三者各自的原理和应用场景。

NSSetUncaughtExceptionHandler

首先我们写一个会导致崩溃的Objective-C代码片段:

NSDictionary *userinfo = @{
  @"username": @"TP-LINK",
  @"email": @"admin@tp-link.com.cn",
  @"tel": @"15015001500"
};
NSMutableArray<NSDictionary *> *memberarray = [NSMutableArray arrayWithArray:@[userinfo]];
for (NSDictionary *dic in memberarray) {
  if ([[dic valueForKey:@"username"] isEqualToString:@"TP-LINK"]) {
    [memberarray removeObject:dic];
  }
}

运行程序,不出意外的话,程序在执行到片段的时候就会立刻崩溃,然后我们会在控制台里面看到如下打印:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0xb550c30> was mutated while being enumerated.'

相信不少同学都会这串提示很熟悉,从字面上来看,程序崩溃是因为有个异常没有被捕获到,异常的类型是NSGenericException,导致异常的原因则是因为在遍历集合的时候尝试去修改里面的元素。

NSGenericException是一个继承自NSException的类,表示触发的是一种通用的异常,除了这个类,还有很多其他的子类,像是NSRangExceptionNSInvalidArgumentException等,基本上只要看到名称就知道异常大致是什么原因导致的。

NSException是可以被我们手动捕获的,例如:

@try {
    for (NSDictionary *dic in memberarray) {
        if ([[dic valueForKey:@"username"] isEqualToString:@"TP-LINK"]) {
            [memberarray removeObject:dic];
        }
}
@catch (NSException *exception) {
    NSLog(@"Caught %@: %@", [exception name], [exception reason]);
}

但是在写实际项目的时候,我们通常不会手动写这类处理异常的代码,Objective-C也并没有强制要求我们写此类的异常处理程序。

其实,这主要是因为异常代表的通常是我们编写的程序存在逻辑错误,通常不可恢复,需要我们在发布给用户使用之前由开发者进行处理,所以NSException又被称为应用级异常。NSSetUncaughtExceptionHandler实际上是给我们提供了一个手段,对这些我们未捕获的异常进行一个最终的处理,但如果这些错误是在用户使用的时候发生的,我们也无法立刻进行处理。

或许也是因为这个原因,Swift语言抛弃了NSException,而只保留了Error。

由于NSSetUncaughtExceptionHandler不是万能的,比如我们写一段Swift的强制解包代码:

var userName= fetchUserName()
printUserName(userName!)

上述代码假设fetchUserName()函数返回nil,并且printUserName()函数只接受非空参数,那么在程序运行时,由于强制解包失败,应用程序会崩溃并且NSSetUncaughtExceptionHandler也无法捕获此类崩溃,这时候就需要其他的机制来捕获此类异常。

Mach异常

要了解Mach异常,首先要了解什么是Mach!首先上一张mac OS X的架构图:

iOS系统崩溃的捕获

mac OS X的核心操作系统被称为“Darwin”,其由系统组件和内核构成。其中内核被称为"xnu",他是一个混合型的内核,包括了Mach和BSD两个部分,其中BSD实现了文件系统、网络、NKE(Network Kernel Extension,实现注入通信加密、虚拟网络接口等网络方面的扩展功能)、POSIX接口等功能,而Mach则实现了I/O组件和驱动程序。xnu内核是开源的。

从图里面可以看到,内核的下面就是硬件,所以由Mach内核抛出的异常也被称为是最底层的异常,造成异常的原因通常是硬件导致的异常,比如:

  • 试图访问不存在的内存
  • 试图访问违反地址空间保护的内存
  • 由于非法或未定义的操作代码或操作数而无法执行指令
  • 产生算术错误,例如被零除、上溢、或者下溢
  • ……

关于Mach抛出异常的流程,我们可以结合以下图来理解:

iOS系统崩溃的捕获

如果出错的线程触发了一个硬件级别的错误,处于内核的陷阱处理程序就会调用exception_deliver()函数依次尝试将异常投递到thread、task和host。

这里插入一个小话题,在Mach内核中,为了和thread、task和host打交道,或者他们互相之间打交道,提供了一种基于端口的IPC手段,这个手段在Cocoa上层也有对应的抽象,就是NSMachPort。这个mach port大家可能听说过,不知大家是否有印象?

当异常发生的时候,一条包含异常的mach message,例如异常类型、发生异常的线程等等,都会被发送到一个异常端口。而thread、task、host都会维护一组异常端口,当Mach Exception机制传递异常消息的时候,它会按照thread → task → host 的顺序传递异常消息。这是通过上面的mach_exc_raise()类函数来实现的。

如果thread、task都没有处理异常,那么就会由host也就是操作系统内核来处理异常,操作系统处理异常的方式就是上图Exception Handler中的流程,可以看到,handler是一个循环处理消息的机制,mach_msg_receive()函数负责接受消息;mach_exc_server()函数内有catch_mach_exception_raise()函数,这个函数通过ux_exception()将mach异常转换为Unix的Signal,并通过threadsignal()将其发送到对应的线程上去。

这一系列过程中,我们可控的部分是thread,我们可以新建一条thread并且通过mach port监听异常端口来实现崩溃的捕获。

有时候,Debugger会在程序崩溃的时候,给出Mach异常的类型:

iOS系统崩溃的捕获

上述代码试图给一个assign类型的property赋值,由于引用计数为0,对象在赋值之后就被立刻释放了,所以这行代码就崩溃了

Debugger给出的标红信息,可以这么理解:

iOS系统崩溃的捕获

一些其他常见的Mach异常类型及其对应的原因如下表:

Exception Notes
EXC_BAD_ACCESS 访问了不该访问的内存
EXC_BAD_INSTRUCTION 线程执行非法指令
EXC_ARITHMETIC 算术异常
EXC_SOFTWARE 软件生成的异常
EXC_BREAKPOINT 跟踪或者断点

关于code大家可能会存在疑惑,它代表的其实是内核函数的返回值,其中,code=1代表的是地址不可用,其定义如下:

#define KERN_INVALID_ADDRESS            1

由于code的种类有很多,其他code对应的含义,可以翻阅kern_return.h头文件进行查阅

以下为苹果的崩溃日志,里面也包含类似信息:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000000000b8

将两者进行结合,一般就可以判断崩溃的原因究竟是什么。了解以上知识相信会对大家日后解决Bug带来一定的帮助。

Unix Signal

Signal是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。信号的作用有很多,比如可以用来进程间通信(IPC)、用于Debugger调试等,当然也可以用来报告异常。

既然Mach已经实现了硬件导致的异常,为什么还需要将其转化为Unix Signal,继续报告一次呢?

原因很简单,因为xnu包含了BSD和Mach,为了实现POSIX兼容,让用户可以使用BSD提供的POSIX API,就需要做这样一层转换。

Mach异常和Unix Signal两者的转换关系如下表:

Mach 异常 Unix Signal 原因
EXC_BAD_INSTRUCTION SIGILL 非法指令,比如数组越界,强制解包可选形等等
EXC_BAD_ACCESS SIGSEVG、SIGBUS SIGSEVG、SIGBUS两者都是错误内存访问,但是两者之间是有区别的:SIGBUS(总线错误)是内存映射有效,但是不允许被访问,比如访问一个结构体但是起始地址有误; SIGSEVG(段地址错误)是内存地址映射都失效,比如野指针
EXC_ARIHMETIC SIGFPE 运算错误,比如浮点数运算异常
EXC_BREAKPOINT SIGTRAP trace、breakpoint等等,比如说使用Xcode的断点
EXC_SOFTWARE SIGABRT、SIGPIPE、SIGSYS、SIGKILL 软件错误,其中SIGABRT最为常见。

问1:既然Mach异常可以转换为unix异常,而signal也是可以由我们自由处理的,那是否可以不处理Mach异常,只处理unix的signal就可以了?

答案是不行,因为某些异常,比如EXC_GUARD 异常(这是一种违反了受保护资源的防护而导致的异常,比如访问SQLite文件的时候关闭了它的文件描述符),是没有映射到Unix Signal的,这种异常就没法通过signal处理。

问2:那是不是处理了Mach异常,就不需要处理signal异常了呢?

答案是也不行,因为如果底层有些异常类型只能通过signal处理,比如直接调用了 __pthread_kill函数直接向某条线程发送了SIGABRT这个signal,这类异常不能被Mach所捕获

为什么没有通用的异常处理函数

现在我们可以回答这个问题了。总结一下,iOS系统中,崩溃有可能是以下两种方式产生的:

  • 应用级异常,比如NSException
  • 硬件级异常,比如野指针访问

对于前者,我们只能使用NSSetUncaughtExceptionHandler进行捕获,对于后者,我们需要使用以下机制:

  • Mach异常处理机制
  • Unix Signal异常处理机制

因为以上两者作用域也无法互相覆盖,所以以上两者也需要结合使用。

正是因为这三种处理机制覆盖了不同的领域,并且处理机制也不尽相同,因此iOS中没有通用的异常处理函数。

然而,事情没有那么简单

上述三个函数的功能十分强大,但是实际上设计一个崩溃捕获系统没有那么容易。一般来说,捕获系统除了捕获崩溃,还需要记录崩溃时的现场信息,比如崩溃时的iOS系统版本、应用版本、崩溃时间、异常信息、程序堆栈等等:

{"app_name":"TP-LINK物联","timestamp":"2023-02-16 15:40:40.00 +0800","app_version":"4.12.1","slice_uuid":"d146125f-f904-3e39-940a-0f7dd32d6071","adam_id":0,"build_version":"41201","platform":2,"bundleID":"net.tplink.surveillancesystem","share_with_app_devs":0,"is_first_party":0,"bug_type":"109","os_version":"iPhone OS 14.0.1 (18A393)","incident_id":"70E8ABFF-6F0F-4094-BF31-EE929EFA78DD","name":"TP-LINK物联"}
Incident Identifier: 70E8ABFF-6F0F-4094-BF31-EE929EFA78DD
CrashReporter Key:   8c905de38d4cd4ff6ad692cc4ca4f6b1f41a50af
Hardware Model:      iPhone12,8
Process:             TP-LINK物联 [2002]
Path:                /private/var/containers/Bundle/Application/F17C1188-8ED4-4C72-8E46-FE7ABE28DDA1/TP-LINK物联.app/TP-LINK物联
Identifier:          net.tplink.surveillancesystem
Version:             41201 (4.12.1)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           net.tplink.surveillancesystem [564]


Date/Time:           2023-02-16 15:40:39.7011 +0800
Launch Time:         2023-02-16 15:37:15.6262 +0800
OS Version:          iPhone OS 14.0.1 (18A393)
Release Type:        User
Baseband Version:    2.00.01
Report Version:      104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000000000b8
VM Region Info: 0xb8 is not in any region.  Bytes before following region: 4375183176
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                        104c80000-1077ac000        [ 43.2M] r-x/r-x SM=COW  ...app/TP-LINK物联

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [2002]
Triggered by Thread:  15

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib        	0x00000001d0bbfdd0 0x1d0bbc000 + 15824
1   libsystem_kernel.dylib        	0x00000001d0bbf184 0x1d0bbc000 + 12676
2   CoreFoundation                	0x00000001a4bb6cf8 0x1a4b19000 + 646392
3   CoreFoundation                	0x00000001a4bb0ea8 0x1a4b19000 + 622248
4   CoreFoundation                	0x00000001a4bb04bc 0x1a4b19000 + 619708
5   GraphicsServices              	0x00000001bb635820 0x1bb632000 + 14368
6   UIKitCore                     	0x00000001a7554734 0x1a69d7000 + 12048180
7   UIKitCore                     	0x00000001a7559e10 0x1a69d7000 + 12070416
8   TP-LINK物联                     	0x0000000104c89ff0 0x104c80000 + 40944
9   libdyld.dylib                 	0x00000001a4877e60 0x1a4877000 + 3680
……

在iOS系统中,如果直接在上述的崩溃处理函数中进行这些信息的记录,并不安全,这主要是因为iOS中App被限制在一个进程中运行,如果应用崩溃,那崩溃的线程将会立刻暂停执行,那就会导致如下问题:

  • 内存可能被破坏(比如某些数值溢出导致的崩溃,内存会被溢出的数据覆盖)
  • 锁可能正在被暂停执行的线程持有着
  • 数据结构可能只更新一半

这样的不稳定环境,大部分函数都不能保证能够正确运行,导致崩溃处理程序能够调用的库函数非常有限,你将无法做到:

  • 通过malloc等函数分配堆内存
  • 通过backtrace函数获取调用栈信息

如果破解这些限制?我们不妨研究下SuperApp中集成的Breakpad是怎么操作的。

Breakpad的整体构成

iOS系统崩溃的捕获

如上图所示,Breakpad主要由三部分构成:

  • symbol dumper:符号提取器。应用程序在构建的时候会包含debug相关的信息,它能够提取这些信息并生成专属的符号文件。
  • client:客户端是一种包含在你应用程序里面的第三方库,它能够捕获当前各线程的状态以及当前加载的共享库等信息,将其写入minidump文件中。
  • processor:处理器主要用来读取minidump文件和符号文件,将其翻译为人类可读的格式

符号文件是程序编译的产物,里面会包含函数或数据的名称、地址、大小、类型等。由于Breakpad是一个跨平台的方案,因此没有采用XCode编译产生的符号表文件,而是使用了自定义的格式。minidump则是一种微软开发的文件格式,它被用在微软的崩溃上传体系中,包含了可执行文件和共享库的列表、进程中的各线程列表信息、调用栈信息等。

Breakpad如何解决上述问题

1.如何安全分配内存

以下为Breakpad启动代码:

ProtectedMemoryAllocator* gMasterAllocator = NULL;
ProtectedMemoryAllocator* gKeyValueAllocator = NULL;
ProtectedMemoryAllocator* gBreakpadAllocator = NULL;

BreakpadRef BreakpadCreate(NSDictionary* parameters) {
  try {
    gMasterAllocator =
        new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);

    gKeyValueAllocator =
        new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
            ProtectedMemoryAllocator(sizeof(LongStringDictionary));
   
    int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
    if (mutexResult == 0) {
      int breakpad_pool_size = 4096;

      gBreakpadAllocator =
          new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
              ProtectedMemoryAllocator(breakpad_pool_size);

      NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
      Breakpad* breakpad = Breakpad::Create(parameters);

      if (breakpad) {
        gMasterAllocator->Protect();
        gKeyValueAllocator->Protect();
        gBreakpadAllocator->Protect();

        [pool release];
        return (BreakpadRef)breakpad;
      }

      [pool release];
    }
  } catch(...) { 
    fprintf(stderr, "BreakpadCreate() : error\n");
  }
  ...
}

上述代码片段已经包含了对内存分配问题的解决,其 核心思路是:既然崩溃时无法分配内存,那么只要在崩溃前提前分配好崩溃处理程序所需的内存并将其保护起来避免被崩溃所破坏即可

ProtectedMemoryAllocator这个类相当于一个内存池,它允许分配内存,但是分配内存无法被回收。此外,它还提供了一个Protect()方法用于将内存池设置为只读,这样一来这块内存就不会在崩溃发生的时候被各种原因覆盖。

通过源码,我们可以一窥其实现的原理,首先是构造函数:

ProtectedMemoryAllocator::ProtectedMemoryAllocator(vm_size_t pool_size) 
  : pool_size_(pool_size),
    next_alloc_offset_(0),
    valid_(false) {
  
  kern_return_t result = vm_allocate(mach_task_self(),
                                     &base_address_,
                                     pool_size,
                                     TRUE
                                     );
  
  valid_ = (result == KERN_SUCCESS);
  assert(valid_);
}

vm_allocate是一个内核函数,用于申请虚拟内存,由于Breakpad需要直接申请一块较大的内存,用于整个模块的内存使用,因此它直接使用了该函数,而不是malloc。该类申请的内存大小是由参数pool_size决定的,内存分配之后,base_address_指向内存池的起始地址。

再看看Protect()方法的实现:

kern_return_t  ProtectedMemoryAllocator::Protect() {
  kern_return_t result = vm_protect(mach_task_self(),
                                    base_address_,
                                    pool_size_,
                                    FALSE,
                                    VM_PROT_READ);
  
  return result;
}

其同样调用了内核函数vm_protect,将申请的虚拟内存设置为只读,这样就实现了内存的保护。

2.如何获取调用栈信息

Breakpad内部有一个MinidumpGenerator类专门用于写入minidump,其中包括了我们关心的线程调用栈信息。由于涉及到minidump格式问题,我们不深入分析这个类,只是简单介绍下原理。

首先,我们需要理解线程调用栈的结构:

iOS系统崩溃的捕获

线程的调用栈分为若干栈帧(stack frame),每个栈帧对应一个函数调用。上图包含了两个栈帧,DrawLine和DrawSquare。

栈帧主要由三部分组成:函数参数、返回地址、帧内的本地变量。上述DrawSquare函数调用DrawLine函数的时候,首先函数的参数入栈,然后把返回地址入栈,最后是函数内部本地变量。

这里要注意的是,有两个特殊的指针:Stack Pointer指向了调用栈的栈顶,Frame Pointer则指向了当前栈帧。

此外,我们还需要了解一下iOS系统中虚拟内存的相关知识:

我们知道,操作系统会对虚拟内存进行分页。在iOS系统中,为了更好的管理内存页,系统会将一组连续的内存页关联到一个VMObject上,也称为VM Region。我们可以通过XCode的Instruments工具,查看当前App的虚拟内存分配情况,其中就包含了VM Region的相关信息:

iOS系统崩溃的捕获

可以看到,VM Region被分为不同的Category,其中有一种Category叫做VM Stack,其包含的就是线程调用栈的信息。

为了获取VM Stack中的信息,Breakpad大致做了以下操作:

  1. 通过内核函数task_threads获取当前进程的所有线程

  2. 通过内核函数thread_get_state获取目标线程的thread_state_t,这个结构中包含了该线程调用栈的栈顶指针Stack Point

    _STRUCT_ARM_THREAD_STATE64
    {
    	__uint64_t __x[29]; /* General purpose registers x0-x28 */
    	__uint64_t __fp;    /* Frame pointer x29 */
    	__uint64_t __lr;    /* Link register x30 */
    	__uint64_t __sp;    /* Stack pointer x31 */
    	__uint64_t __pc;    /* Program counter */
    	__uint32_t __cpsr;  /* Current program status register */
    	__uint32_t __pad;   /* Same size for 32-bit or 64-bit clients */
    };
    
  3. 由Stack Pointer的地址作为起始地址,获取下一个VM Region,如果其Category为VM Stack,将此块内存的信息记录下来,写入minidump文章来源地址https://www.toymoban.com/news/detail-850234.html

到了这里,关于iOS系统崩溃的捕获的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • iOS 17.0 UIGraphicsBeginImageContextWithOptions 崩溃处理

    在升级到iOS17后你会发现,之前版本运行的很好,这个版本突然会出现一个运行闪退。报错日志为 *** Assertion failure in void _UIGraphicsBeginImageContextWithOptions(CGSize, BOOL, CGFloat, BOOL)(), UIGraphics.m:410 跟踪到具体的报错位置如下所示: UIGraphicsBeginImageContextWithOptions(size, NO , 0); 根据查阅的

    2024年04月11日
    浏览(42)
  • 记录一个iOS UITableView 正在刷新的时候修改数据源导致的崩溃

    首先看一下崩溃堆栈信息 由于tableview 调用layoutsubViews 执行到代理方法 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 由于是崩溃在系统方法里面的,我们无法直接看到是因为调用哪个方法导致的崩溃 后来经过多次调试,发现是因为调用了reloadData 出

    2024年01月17日
    浏览(57)
  • Unified-IO 2 模型: 通过视觉、语言、音频和动作扩展自回归多模态模型。给大家提前预演了GPT5?

      每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领域的领跑者。点击订阅,与

    2024年01月22日
    浏览(40)
  • 取个对象值导致系统崩溃

    想必各位小伙经常在项目中遇到一些错误,取 对象值的时候 ,经常 报错 ,又或者某些项目突然就 挂 经常都是出现在一些 对象取值上面 ,然后就被领导一顿训斥 例如: 下面这个报错大家想必不会陌生, 就是读取不存在对象的变量 那么为什么我们会遇到这个问题呢? 下面

    2024年02月13日
    浏览(37)
  • ubuntu系统崩溃通过日志查看原因

    里面的日志包括: /var/log/syslog Syslog是一种标准的日志记录工具。它收集包括内核在内的各种程序和服务的消息,并根据设置将它们存储在通常位于 /var/log . 在某些数据中心设置中,有 数百个 设备,每个设备都有自己的日志; syslog 在这里也很方便。一个人只需设置一个专用

    2024年02月05日
    浏览(40)
  • 电脑崩溃蓝屏问题如何重装系统

    电脑是我们日常生活和工作中必不可少的工具,但在使用过程中,难免会遇到各种问题,例如系统崩溃、蓝屏、病毒感染等,这些问题会严重影响我们的使用体验和工作效率。而小白一键重装系统可以帮助我们快速解决这些问题,本文将介绍如何使用小白一键重装系统解决电

    2024年02月09日
    浏览(46)
  • 记一次 .NET某防伪验证系统 崩溃分析

    昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障,因为看小伙子昨天在群里问了一天也没搞定,干脆自己亲自上阵吧,抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式,省去了很多沟通成本。 windbg有一个非常强大的点就是当你双击打开后,会自动帮你切换到

    2024年03月28日
    浏览(63)
  • 记一次 .NET 某企业内部系统 崩溃分析

    前些天有位朋友找到我,说他的程序跑着跑着就崩溃了,让我看下怎么回事,其实没怎么回事,抓它的 crash dump 就好,具体怎么抓也是被问到的一个高频问题,这里再补一下链接: [.NET程序崩溃了怎么抓 Dump ? 我总结了三种方案] https://www.cnblogs.com/huangxincheng/p/14811953.html ,采用

    2024年02月10日
    浏览(53)
  • Windows平台崩溃转储系统crashrpt的使用

    CrashRpt 是一个免费的、轻量级的开源错误报告库开源库,旨在拦截C++程序中的异常,收集有关崩溃的技术信息并通过互联网向软件供应商发送错误报告,用于在 Microsoft Visual Studio IDE 中创建并在 Windows 中运行C++应用程序。(不跨平台) 官网为:CrashRpt - A crash reporting system for

    2024年02月05日
    浏览(36)
  • 使用近10年,说说向大家推荐Linux系统的理由

    使用linux已经快十年了,一直想推荐身边的使用linux,但是一直没有分享。但是现在我想分享推荐了。下面我们一起聊聊为什么我向大家推荐linux。 为什么现在我想推荐了呢?是因为我自认为相对于同龄人来说,我使用桌面版的时间算是挺长的了,而且我也想纠正一部分人不看

    2024年02月10日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包