• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2024-07-27 19:27 Aet 隐藏边栏 |   抢沙发  12 
文章评分 3 次,平均分 5.0

概述

  1. Breakpad 客户端库负责监控应用程序是否发生崩溃(异常)、在崩溃发生时通过生成转储来处理这些崩溃
  2. 并提供将转储上传到崩溃报告服务器的方法

客户端

概述

  1. Breakpad 客户端主要负责在应用程序崩溃时捕获崩溃信息,并生成 minidump 文件
  2. minidump 文件包含了崩溃时的堆栈、寄存器状态和内存信息,可以用来分析崩溃原因

主要组件

  1. Exception Handler
    1. 其实就相当于一个管理器
    2. 溃处理程序,负责捕获异常并生成 minidump 文件
  2. Minidump Writer
    1. 负责将崩溃时的内存信息写入 minidump 文件
  3. Client Interface
    1. 应用程序与 Breakpad 的接口,用于初始化和配置 Breakpad

设计与实现

  1. 初始化和配置:
    1. 在应用程序启动时,初始化 Breakpad 并设置崩溃处理程序
    2. 常见的初始化代码如下

  1. 捕获异常:
    1. 当应用程序发生崩溃时,Windows 会调用注册的异常处理程序
    2. Breakpad 的异常处理程序会生成一个 minidump 文件
  2. 生成 Minidump 文件:
    1. 崩溃处理程序调用 Minidump Writer,将当前进程的内存信息写入 minidump 文件
    2. 生成的 minidump 文件可以用来进行后续的崩溃分析

客户端的细节

HandlerType

  1. HANDLER_EXCEPTION = 1 << 0, // SetUnhandledExceptionFilter
  2. HANDLER_INVALID_PARAMETER = 1 << 1, // _set_invalid_parameter_handler
  3. HANDLER_PURECALL = 1 << 2, // _set_purecall_handler
  4. HANDLER_ALL = HANDLER_EXCEPTION |HANDLER_INVALID_PARAMETER |HANDLER_PURECALL

ExceptionHandler

  1. 构造函数里调用了Initialize这个函数,它是用于初始化 ExceptionHandler 对象的核心函数

重要的回调

  1. typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo,MDRawAssertionInfo* assertion);
    1. ExceptionHandler构造函数的第二个参数
    2. Breakpad 对异常进行任何实质性处理之前运行的回调函数
    3. 在写入小型转储之前会调用 FilterCallback
    4. context 是创建处理程序时用户作为 callback_context 提供的参数
    5. exinfo 指向异常记录(如果有)
    6. assertion 指向断言信息(如果有)
    7. 如果 FilterCallback 返回 trueBreakpad 将继续处理,并尝试写入小型转储
    8. 如果 FilterCallback 返回 falseBreakpad 将立即报告异常未处理,而不会写入小型转储,从而允许另一个处理程序处理它
  2. typedef bool (*MinidumpCallback)(const wchar_t* dump_path,const wchar_t* minidump_id,void* context,EXCEPTION_POINTERS* exinfo,MDRawAssertionInfo* assertion,bool succeeded);
    1. 写入 minidump 后运行的回调函数
    2. minidump_id 是转储的唯一 ID,因此 minidump文件为 <dump_path>\<minidump_id>.dmp
    3. context 是创建处理程序时用户作为回调上下文提供的参数
    4. exinfo指向异常记录,如果没有发生异常,则为 NULL
    5. successful 表示 minidump 文件是否已成功写入
    6. assertion 如果处理程序由断言调用,则指向有关断言的信息
    7. 如果发生异常并且回调返回 true,则 Breakpad 会将异常视为已完全处理,从而阻止任何其他处理程序收到异常通知
    8. 如果回调返回 false,则 Breakpad 将异常视为未处理,并允许另一个处理程序处理它
    9. 如果没有其他处理程序,Breakpad 会将异常报告给系统,作为未处理,从而允许调试器或本机崩溃对话框有机会处理异常
    10. 大多数回调实现通常应返回 succeeded 的值,或者当它们希望不报告已处理的异常时,返回 false
      1. 回调很少会直接返回 true(除非 succeededtrue
    11. 对于进程外转储生成,转储路径和小型转储 ID 将始终为 NULL
      1. 在进程外转储生成的情况下,转储路径和小型转储 ID 由服务器进程控制,不会传回崩溃进程

Initialize

  1. 参数
    1. dump_path: Minidump 文件的保存路径
    2. filter: 过滤回调函数,用于决定是否生成 minidump 文件
    3. callback: Minidump 回调函数,用于在生成 minidump 后执行自定义操作
    4. callback_context: 传递给回调函数的上下文信息
    5. handler_types: 要处理的异常类型
    6. dump_type: Minidump 文件的类型
    7. pipe_name: 用于 out-of-process 崩溃处理的管道名称
    8. pipe_handle: 用于 out-of-process 崩溃处理的管道句柄
    9. crash_generation_client: 崩溃生成客户端对象,用于 out-of-process 崩溃处理
    10. custom_info: 自定义客户端信息
  2. InterlockedIncrement
    1. 增加实例计数
  3. 保存回调,初始化其他成员
  4. 配置 out-of-process 崩溃处理

  1. client->Register()

  1. 检查一下有没有成功配置外部进程
    1. IsOutOfProcess
  2. 如果没有成功配置 out-of-process 崩溃处理,则设置 in-process 崩溃处理
    1. InitializeCriticalSection,临界区
    2. 创建两个信号量,一个用于开始,一个 用于结束
    3. 创建线程
    4. 加载dbghelp.dllrpcrt4.dll
    5. 设置指令内存信息

  1. 初始化关键段对象,设置异常处理程序
    1. 如果 handler_types 不为 HANDLER_NONE,则设置异常处理程序

CrashGenerationClient

  1. 初始化
    1. 如果提供了crash_generation_client,就用提供的
    2. 如果提供了pipe_namepipe_handle,就用提供的信息去生成一个CrashGenerationClient替换默认的
    3. 如果没有提供任何自定义信息,就用默认的CrashGenerationClient去注册
    4. 并且在注册成功后,把这个默认的CrashGenerationClient保存到管理器的成员变量中
  2. 所谓Register
    1. 先判断一下已经有没有注册,没注册过才注册
    2. 具体是调用ConnectToServer函数,尝试通过命名管道连接到服务器并进行通信

  1. 总结就是再次检查CrashGenerationClient能不能用,能用就用它去发请求
    1. 不能用就用别的手段去做事情
    2. CrashGenerationClient能用的情况,就是用它去通知服务端,真正最后做事情的,是在服务端

客户端和服务端的通信

客户端连接服务端

  1. 连接到具名管道(服务端)

  1. 连接成功后,顺便设置管道的读取模式为消息模式
    1. 这种模式下,读取操作将读取完整的消息。这意味着每次读取操作都会读取一个完整的消息单元,确保消息的边界得到维护
    2. 相比之下,字节模式(Byte Mode)则是以字节流的方式读取数据,不保证消息边界

  1. 成功连接到服务端后,向服务端注册客户端,并从服务端获取必要的同步对象和句柄,以便后续的崩溃处理
    1. 构建注册请求消息
    2. 通过命名管道发送注册请求并接收响应
    3. 验证响应消息
    4. 发送确认消息
    5. 存储从服务端获取的同步对象和句柄
    6. 返回注册结果

  1. 管理器在确认客户端初始化完成并连接到服务端之后设置一些额外的状态和处理程序

  1. 当需要生成dump时,管理器这边会调用相关写dump函数,并通过客户端的RequestDump函数去触发关键事件
    1. 客户端这边触发关键事件消息后,等待服务端的回复

  1. 服务器那边是先启动的
    1. 它在Start函数里创建了关键事件,以及具名管道
    2. 然后监听关键事件的触发,触发后在对应的回调函数OnPipeConnected里根据具体的状态进行处理
    3. 对于IPC_SERVER_STATE_READ_DONE这个状态,会通过RespondToClient这个函数去响应,成功响应后会把客户端添加服务端的容器里
    4. 成功添加也意味着,为该客户端进行了两个注册,一个绑定了生成dump的回调OnDumpRequest,一个绑定了客户端结束的回调OnClientEnd

服务端收到消息并处理

关于客户端-服务端模式

  1. breakpad里面客户端负责根据应用程序的状况生成消息,并发送给服务端
  2. 服务端收到消息后,根据消息的类型进行对应的操作

关于客户端的问题

异常函数的注册

  1. 分程序内,程序外两种,后续再研究

客户端是怎么监控的

  1. 注册完异常函数后,当崩溃发生后,系统会回调这些异常函数,在这些异常函数里面客户端触发关键事件,服务端那边监听到事件触发后,就根据具体的状态去处理生成,后续再研究

服务端

  1. 先于客户端启动

  1. Start函数里面初始化了关键事件,互斥量,用于后续和客户端交互
    1. 同时给对应的事件注册了回调函数OnPipeConnected

知识点

QueueUserWorkItem

  1. QueueUserWorkItem 函数用于将一个工作项添加到系统的全局工作队列中,该队列由 Windows 操作系统管理,并由操作系统维护的线程池处理
  2. 函数签名
    1. Function: 一个指向工作函数的指针
      breakpadtest工程例子中,是 AppendTextWorker
    2. `Context: 传递给工作函数的参数
      在这个例子中,是 text
    3. Flags: 指定工作项的执行选项
      WT_EXECUTEDEFAULT 表示使用默认的执行选项

  1. 工作流程
    1. 将工作项添加到工作队列:
      QueueUserWorkItem 函数被调用时,会将 AppendTextWorker 函数和 text 参数添加到系统的全局工作队列中
    2. 线程池中的线程处理工作项:
      系统的线程池会自动从工作队列中取出工作项,并在一个可用的线程中调用 AppendTextWorker 函数
      线程池中的线程会并行处理多个工作项,提高处理效率

QueueUserWorkItem相关问题

  1. 这个系统全局工作队列什么时候创建的?
    1. 系统全局工作队列是在操作系统启动时由 Windows 内核创建的
    2. 这是操作系统的一部分,始终存在,并由操作系统负责管理和维护
  2. 这个系统全局工作队列由谁管理的?
    1. 全局工作队列由 Windows 内核维护和管理
    2. 操作系统负责管理队列中的工作项,以及协调线程池线程来处理这些工作项
    3. 用户应用程序通过操作系统提供的 API(如 QueueUserWorkItem)与该队列进行交互
  3. 系统线程池什么时候创建的?
    1. 系统线程池是在操作系统启动时由 Windows 内核创建的
    2. 线程池的创建和管理是操作系统的一部分,系统会根据需要动态调整线程池的大小和配置
  4. 系统线程池由谁管理的?
    1. 系统线程池由 Windows 操作系统创建和维护
    2. Windows 提供了一个通用的线程池机制,允许操作系统高效地管理线程资源
    3. 操作系统会根据工作队列的负载动态调整线程池的大小,以优化性能和资源利用率
  5. 所有的应用程序都可以通过这个函数添加任务吗?
    1. 是的,所有用户模式的应用程序都可以使用 QueueUserWorkItem 函数将工作项添加到系统的全局工作队列中
    2. 这是一个公开的 API,设计用于简化多线程编程,提供一种方便的方式来将工作项异步提交给操作系统管理的线程池
  6. 尽管所有应用程序都可以使用 QueueUserWorkItem 函数,但它们必须遵守以下约束和限制:
    1. 权限:
      应用程序需要有足够的权限来调用此函数
      如果应用程序在受限环境中运行,可能会遇到权限问题
    2. 资源限制:
      系统线程池和工作队列是共享资源
      如果一个应用程序提交了大量的工作项,可能会影响系统的整体性能
      操作系统会进行一些调度和资源管理,但开发者仍应小心管理提交的工作项数量
    3. 错误处理:
      应用程序应正确处理 QueueUserWorkItem 函数可能返回的错误,例如资源不足或其他系统限制

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2024-09-03
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享