sentry 工作流和集成之调试信息文件(翻译)

3,098 阅读15分钟

调试信息文件

调试信息文件允许Sentry提取堆栈跟踪,并为大多数编译平台提供有关崩溃报告的更多信息。存储在调试文件中的信息包括原始函数名、源文件和行号的路径、源代码上下文或变量在内存中的位置。sentry可以使用其中的一些信息并将其显示在问题详细信息页面上。

每个主要平台使用不同的调试信息文件。我们目前支持以下格式:

  • 用于iOS、iPadOS、tvOS、watchOS和macOS的dSYM文件

  • Linux和Android的ELF符号(NDK)

  • Windows的PDB文件

  • 所有平台的防波堤符号

  • Java和Android的ProGuard映射

注意 源映射也是调试信息文件,但在Sentry中处理方式不同。有关更多信息,请参见sentry cli中的源地图。

Sentry需要访问应用程序的调试信息文件以及系统库,以提供完全符号化的崩溃报告。您可以将文件上载到Sentry,也可以将其放在兼容的符号服务器上,以便Sentry在需要时下载。

可以在“项目设置”的“调试文件”部分管理调试信息文件。此页列出所有上载的文件,并允许配置符号服务器以进行自动下载。

调试信息

sentry区分四种调试信息:

  • 展开信息:使Sentry能够从优化版本的小型转储和其他二进制崩溃格式中提取堆栈跟踪。此过程称为“堆栈展开”或“堆栈行走”。由于在C++中抛出异常时也需要这样做,所以该信息通常包含在可执行文件或库中。如果上载的文件包含此信息,则显示“展开”标记。

  • 调试信息:提供函数名、源文件路径、行号和内联框架。从指令地址解析此信息的过程称为“符号化”。与可执行文件相比,此信息相对较大,通常放在单独的文件中。在Sentry中,这些文件被指定为调试伙伴并显示调试标记。

  • 符号表:如果某个库没有调试信息,Sentry可以使用符号表作为回退来检索函数名。符号表通常包含在可执行文件和调试配套文件中。但是,它们不包含足够的信息来解析内联函数或文件名和行号。symtab标记表示符号表。

  • 源代码:通常,源代码不是常规调试信息文件的一部分。Sentry CLI可以捆绑应用程序的源代码并将其上载到Sentry中的堆栈跟踪中显示源上下文。这些包显示在sources标签中。

编译器将上述调试信息放在目标平台、体系结构、构建标志或优化级别上传递的不同文件中。因此,sentry可能不需要以上所有信息来处理车祸报告。不过,提供所有可用的调试信息始终是一个好主意。

sentry cli可用于列出受支持的调试文件的属性并验证其内容。有关详细信息,请参见sentry cli中的调试信息文件。

本节的其余部分将详细介绍文件格式。

MachO and dSYM

所有苹果平台上的可执行文件、动态库和调试伙伴都使用Mach对象或短MachO容器格式。这适用于iOS、iPadOS、tvOS、watchOS和macOS。

  • 可执行文件没有文件扩展名。对于桌面应用程序,它们通常放在带有.app后缀的app bundle结构中。除非手动剥离,否则可执行文件包含展开信息和符号表。调试信息从不存储在可执行文件中。
  • 动态库使用.dylib扩展名,否则其行为与可执行文件完全相同。
  • 调试伙伴放在扩展名为.dSYM的文件夹结构中,位于.dSYM/Contents/Resources/DWARF/。它们通常包含符号表和调试信息,但很少展开信息。

使用Xcode或clang编译器构建应用程序时,调试信息会自动放入dSYM文件中。但是,手动链接时,必须使用以下命令创建dSYM文件:

dsymutil /path/to/output[.dylib]

可执行和可链接格式(ELF)

在Linux发行版上,可执行文件和调试信息存储在ELF容器中。与其他平台不同,没有用于调试配套文件的专用容器或说明符。

调试信息是二进制文件(可执行文件或库)的一部分,在生成发布版本时由于其大小而被剥离。但是,有一种方法可以将它们保留在单独的文件中(在其他位置或使用.debug扩展名):

# There is an executable called "binary" in the CWD
objcopy --only-keep-debug binary binary.debug
objcopy --strip-debug --strip-unneeded binary
objcopy --add-gnu-debuglink=binary.debug binary

这将产生以下结构:

  • 可执行文件没有文件扩展名。如果像上面那样剥离,可执行文件包含一个符号表,但没有调试信息。如果生成运行忽略了帧指针,则展开信息也将保留。这两个都可以使用像--strip all这样的标志进一步剥离。

  • 共享库使用.so扩展名,否则其行为与可执行文件完全相同。

  • 调试伙伴不带标准文件扩展名,但通常命名为.Debug。如果像上面那样剥离,这些文件包含展开信息、调试信息和符号表。

      # Note the --compress-debug-sections option
      objcopy --only-keep-debug --compress-debug-sections=zlib binary binary.debug
    

这可以通过检查readelf中对应于SHF_COMPRESSED的C标志来验证:

$ readelf -S path/to/file
  ...
  [21] .debug_info       PROGBITS         0000000000000000  00000370
       000000000000e133  0000000000000000   C       0     0     1

PE and PDB

在Microsoft Windows上,可执行文件和动态库使用可移植可执行容器(简称PE)。调试信息存储在程序数据库文件中,通常称为PDB。

  • 可执行文件使用.exe文件扩展名。只有在为64位体系结构编译时,它们才包含展开信息。否则,它们不包含任何可用的信息,并且将被忽略以便上载到Sentry。

  • 动态库使用.dll文件扩展名,否则其行为与可执行文件完全相同。

  • 调试伙伴存储在.pdb文件中。它们通常包含调试信息,在大多数情况下还包含符号表。对于32位程序,它们还包含展开信息。在极少数情况下,它们的文件名可能与相应的可执行文件名不同。

注意 目前,Sentry只支持本地PDB。不支持.NET平台的PDB。

防波堤符号(Breakpad Symbols)

Google Breakpad库已经建立了一个独立于平台的ASCII格式来存储调试信息。这些文件通常是为使用Breakpad、Crashpad或Electron框架的应用程序生成的。

Breakpad存储库包括每个平台的dump-syms工具,这些工具可以从本机调试文件转换为Breakpad符号。这些转换器将所有可用信息捆绑到一个文件中,这样只需要上载一个文件。

与本机调试文件不同,Breakpad符号丢弃了许多处理小型转储不需要的信息。最值得注意的是,内联函数没有声明,因此Sentry无法在堆栈跟踪中显示内联帧。

前置映射(ProGuard Mappings)

ProGuard映射文件允许Sentry将模糊的Java类路径和方法名解析为其原始形式。从这个意义上说,它们充当Java和Android应用程序的调试信息文件。

调试标识符

每个调试信息文件指定一个唯一的标识符。崩溃报告声明这些标识符,以允许调试器和崩溃报告系统解析正确的文件。sentry区分两种标识符:

  • 代码标识符:可执行或动态库的唯一标识符——代码文件。这个标识符的内容依赖于平台:MachO文件使用UUID,ELF文件使用SHA散列,PE文件使用某些头属性的连接。

  • 调试标识符:调试伴随文件的唯一标识符。与代码标识符不同的是,Sentry在所有平台上执行相同的结构。在Windows上,这是PDB文件的实际唯一id;在所有其他平台上,这是代码标识符的有损转换。

将调试信息文件上载到Sentry时,CLI和服务器将始终为每个上载的文件计算调试标识符。此标识符与可执行文件、库以及调试伙伴相关联,以确保它们可以通过一种通用机制唯一定位。

注意 调试信息不必与版本关联。唯一的调试标识符确保Sentry可以为每个崩溃报告选择正确的文件。但是,仍然建议在客户机中配置版本以从其他功能中获益。

对于本机事件,“问题详细信息”页显示已加载图像的列表。此列表包含可执行文件和所有加载的动态库,包括它们的调试标识符。可以复制此标识符,并在“调试文件设置”屏幕中搜索与其匹配的确切文件。

sentry cli可以帮助打印调试信息文件的属性,比如它们的调试标识符。有关详细信息,请参见检查调试信息文件。

GNU生成标识符

对于Linux上的ELF文件,Sentry使用GNU构建标识符来计算调试标识符。所有最近的编译器和链接器都支持生成id的发布,但有时它们可能需要额外的配置。gcc默认情况下会这样做,对于clang,请使用以下标志之一:

  • --build id=uuid,用于快速但不可重复的随机标识符。

  • --build id=sha1,用于通过散列代码段的第一页生成的较慢但可重复的标识符。

在二进制文件和剥离的调试信息文件中,标识符必须存在且相同。如果由于某种原因缺少ID,请在剥离之前上载文件,以便sentry cli可以从未剥离的文件中计算稳定的标识符。

PDB年龄不匹配

Microsoft PDB的标识符由两部分组成:唯一签名和年龄字段。签名是在最初编写PDB时生成的,通常会随着每次构建而更改。age是一个计数器,每次修改PDB时都会递增。

PE文件(如可执行文件和动态库)在其头中指定相应PDB的完整标识符。这包括年龄。然而,如果PDB在PE生成之后被修改,它的年龄可能会发生变化。这可能导致不同的标识符:

PE:  3003763b-afcb-4a97-aae3-28de8f188d7c-2
PDB: 3003763b-afcb-4a97-aae3-28de8f188d7c-

sentry cli可以在上载过程中检测这些差异,并将相同的标识符与两个文件关联起来。但是,这需要在upload命令的同一调用中上载这两个文件。否则,标识符发散,sentry可能无法解析符号化的正确文件。

前卫uuid

与其他调试信息文件不同,ProGuard文件没有内在的唯一标识符。Sentry CLI根据文件的校验和为它们分配SHA1 UUID。您可以对ProGuard文件使用sentry cli difutil check来查看生成的UUID。

如果需要自己生成UUID,可以使用以下算法(Python代码供参考):

import uuid

NAMESPACE = uuid.uuid5(uuid.NAMESPACE_DNS, "guardsquare.com")

def get_proguard_uuid(filename):
    with open(filename, 'rb') as f:
        return uuid.uuid5(NAMESPACE, f.read())

上传文件

向Sentry提供调试信息文件的最直接的方法是使用Sentry cli上载它们。根据您的工作流,您可能希望作为生成管道的一部分上载,或者在部署和发布应用程序时上载:

可以使用upload dif命令上载文件。此命令将递归扫描给定文件夹中的文件并将其上载到Sentry:

$ sentry-cli upload-dif -o <org> -p <project> /path/to/files

> Found 2 debug information files
> Prepared debug information files for upload
> Uploaded 2 missing debug information files
> File processing complete:

  PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 executable)
  PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 debug companion)

有关所有可用选项和更多信息,请参阅上载调试信息。

重要的 始终确保在部署或释放应用程序之前上载调试文件,以便可以处理崩溃报告。对于手动测试,请使用sentry cli upload dif——在发送第一个本机崩溃或错误事件之前等待。
如果你上传的文件是sentry在事故报告中报告丢失的,可能需要一个小时,直到sentry开始使用该文件进行新的事故报告。不再处理现有事件和问题。

在Sentry中,上载的文件与项目关联。您可以在“项目设置>调试文件”中查看和管理上载。此屏幕显示调试文件的最重要属性:

  • 调试标识符。如果调试信息跨多个文件拆分,则可能有多个条目共享同一标识符。

  • 调试文件的名称。使用Sentry cli上载文件时,Sentry使用文件系统上的名称。

  • 文件的体系结构和类型。如果文件的调试标识符和名称匹配,这可用于将文件区分为可执行文件和调试伙伴。

  • 这些文件中可用的调试信息。这可以包括展开、调试、符号表和源代码。

  • 元数据,如上传的大小和时间。

  • 如果多个项目需要相同的文件,则需要重新上载。Sentry不访问存储在其他项目中的调试文件来表示崩溃报告。

如果多个项目需要相同的文件,则需要重新上载。Sentry不访问存储在其他项目中的调试文件来表示崩溃报告。

再加工(Reprocessing)

sentry可以暂停传入的崩溃报告,直到所有必需的调试信息文件都已上载。此功能称为重新处理。它可以在“项目设置>处理问题”中配置。默认情况下,此功能被禁用。

如果启用,问题流中将不会显示缺少调试文件的崩溃报告。相反,您将收到一个警告,在上载所有调试文件之前,无法处理事件。

一旦问题显示在问题流中,它就不再被处理。即使启用了重新处理,新文件上载也不会影响此类事件。

目前,此功能仅适用于Cocoa SDK发送的iOS崩溃,与符号服务器不兼容。

符号服务器

Sentry可以从外部存储库下载调试信息文件。这允许您停止上载调试文件,而是配置公共符号服务器或运行自己的。也可以同时配置外部存储库和上载调试文件。

要配置外部存储库,请转到“项目设置>调试文件”。在上载的文件列表上方,有两个配置外部存储库的设置:

  • 自定义存储库:配置包含调试文件的自定义存储库。您可以选择配置HTTP符号服务器、Amazon S3存储桶或Google云存储桶。这需要一个商业或企业计划。

  • 内置存储库:允许从预先配置的符号服务器列表中进行选择。默认情况下,iOS和Microsoft已启用。

Sentry按配置顺序查询外部存储库中的调试信息文件。如果配置了自定义存储库,则首先探测这些存储库。只有在其中一个自定义存储库中找不到的调试信息文件才会从内置存储库中查询。

内置存储库

要启用内置存储库,请从下拉列表中选择它。这将立即添加存储库并使用其调试信息文件来表示新的崩溃报告。同样,单击名称旁边的X可以禁用任何内置存储库。

警告 添加或删除外部存储库将立即应用。因此,事件可能会与新信息不同地分组并产生新问题。请注意,这些会导致向您的团队成员发出通知。

自定义存储库

注意 自定义存储库可用于业务和企业计划中的组织。

与内部格式无关,Sentry支持三种自定义存储库:

  • HTTP符号服务器:在可配置路径上提供调试文件的HTTP服务器。服务器中的查找通常应不区分大小写,尽管可以在设置中配置显式大小写。

  • Amazon S3 Bucket:一个完整的S3 Bucket或一个子目录。这需要对配置的访问密钥具有s3:GetObject和可选的s3:ListBucket权限。bucket中的查找是区分大小写的,因此我们建议将所有文件存储为小写。

  • Google云存储桶:一个完整的GCS存储桶或一个子目录。这需要存储.objects.get以及存储.objects.list配置的服务帐户的权限。bucket中的查找是区分大小写的,因此我们建议将所有文件存储为小写。

除了身份验证配置之外,所有类型都有公共配置参数:

  • Name:用于标识存储库的名称。

  • Path Casing:重写哨兵用来查询调试信息文件的大小写。默认情况是一个混合案例,它将使用下一节中描述的案例。重写时,所有访问都是小写或大写的。默认为“混合大小写”。

  • Directory Layout目录布局:bucket的内部结构,或者symbol服务器的协议。有三种布局可供选择,下一节将讨论这些布局。默认为“平台特定”。

目录布局

Sentry支持外部存储库的多种布局。根据所选的布局和文件类型,我们尝试在特定路径下载文件。

下表包含从支持的布局到应用于特定文件的文件路径架构的映射:

上表中的路径模式定义如下:

防波堤(Breakpad)

路径://

Breakpad始终使用Breakpad ID来存储符号。通过删除破折号并应用以下大小写规则,可以从调试标识符计算这些标识符:

  • id的签名部分(前32个字符)是大写的。
  • id的年龄部分(剩余字符)为小写。

符号文件的名称依赖于平台。在Windows上,文件扩展名(可以是.exe、.dll或.pdb)替换为.sym。在所有其他平台上,.sym扩展名会附加到完整的文件名中,包括潜在的扩展名。

示例:

  • wkernel32.pdb/ff9f9f7841db88f0cdeda9e1e9bf3b51/wkernel32.sym

  • MyFramework.dylib/5E012A646CC536F19B4DA0564049169B/MyFramework.动态同步

LLDB

路径:XXXX/XXXX/XXXX/XXXX/XXXX/xxxxxxxxxxx[.app]

macOS上的LLDB调试器可以从文件映射的UUID目录中读取调试符号。UUID通过将前20个十六进制数字分成4个字符块来分解,并为每个块创建一个目录。在最后一个目录中,LLDB通常需要一个由最后12个十六进制数字命名的符号链接,该符号链接跟随实际的dSYM文件。

这实际上不是LLVM特性。这实际上是CoreFoundation的一个特性,专门在spotlight之上的macOS上实现。Spotlight索引这些路径,lldb使用私有DBGCopyFullDSYMURLForUUID API来定位符号。macOS使用这些位置的符号链接。

由于可执行文件或库与dSYM文件共享相同的UUID,因此前者以.app后缀区分。

十六进制数字为大写,应用程序后缀为小写。

示例:

5E01/2A64/6CC5/36F1/9B4D/A0564049169B(调试伴侣)

5E01/2A64/6CC5/36F1/9B4D/A0564049169B.app(可执行文件或库)

BuildID

路径:nn/nnnnnnnnnnnnnnnn…[.debug]

GDB支持多种查找方法,这取决于指定调试信息文件的方式。Sentry使用Build ID方法:假设GNU Build ID注释或节已写入ELF文件,则指定可执行文件的唯一标识符,该标识符也保留在调试文件中。

GNU构建ID是一个可变长度的二进制字符串,通常由代码段(.text)的20字节SHA1散列组成。查找路径是pp/nnnnnn.debug,其中pp是生成ID位字符串的前2个十六进制字符,nnnnnn是十六进制字符串的其余部分。要查找可执行文件,将省略.debug后缀。

示例:

b5/381a457906d279073822a5ceb24bfef94ddb(可执行文件或库)

b5/381A 457906D279073822A5CEB24C4BEF94号调试(剥离的调试文件)

SSQP

路径:<file\u name>/-/<file\u name>

SSQP密钥约定是原始Microsoft Symbol Server protocol for.NET的扩展。它指定PE、PDB、MachO和ELF文件的查找路径。除了PDB标识符的age字段应该是大写之外,所有查找路径的大小写通常都是小写的。

对于MachO文件和ELF文件,SSQP分别指定使用LLDB和GNU build id方法中使用的相同标识符。有关详细信息,请参阅以上各节。这将为所有可能的文件类型生成以下路径:

<code_name>/<size_of_image>/<code_name>(PE文件)

<debug_name>//<debug_name>(PDB文件)

<code\u name>/elf buildid-/<code\u name>(elf二进制)

_.debug/elf buildid sym-/.debug(elf调试文件)

<code\u name>/mach uuid-/<code\u name>(MachO二进制)

_.dwarf/mach uuid sym-/.dwarf(MachO二进制)

SSQP通过对文件内容的SHA1校验和指定一个附加的查找方法,通常用于源文件查找。Sentry不支持此查找方法。

示例:

  • wkernel32.pdb/ff9f9f7841db88f0cdeda9e1e9bff3b5A/wkernel32.pdb
  • kernel32.dll/590285e9e0000/kernel32.dll
  • libc-2.23.so/elf-buildid-b5381a457906d279073822a5ceb24c4bfef94ddb/libc-2.23.so
  • .debug/elf-buildid-sym-b5381a457906d279073822a5ceb24c4bfef94ddb/.debug
  • CoreFoundation/mach-uuid-36385a3a60d332dbbf55c6d8931a7aa6/CoreFoundation
  • .dwarf/mach-uuid-sym-36385a3a60d332dbbf55c6d8931a7aa6/.dwarf

SymStore

路径://

Microsoft提供的公共符号服务器过去仅用于承载Windows平台的PDB。除了文件名之外,它们还使用签名期限调试标识符来定位符号。文件路径与SSQP相同,但默认大小写规则除外:

文件名如下

PDB标识符的签名和期限为大写。

PE标识符的时间戳为大写,但大小写为小写。

由于原始Microsoft Symbol服务器不提供ELF或MachO文件,因此我们不建议对这些类型使用此约定。但是,当选择此布局时,Sentry将支持SSQP约定,并使用调整后的大小写规则。

示例:

wkernel32.pdb/ff9f9f7841db88f0cdeda9e1e9bf3b5a/wkernel32.pdb

KERNEL32.dll/590285E9e0000/KERNEL32.dll

Index2

路径:///

此布局与SymStore相同,只是文件名的前两个字符作为附加文件夹附加在路径前面。

示例:

wk/wkernel32.pdb/ff9f9f7841db88f0cdeda9e1e9bf3b5a/wkernel32.pdb

KE/KERNEL32.dll/590285E9e0000/KERNEL32.dll

调试文件的压缩(Compression of Debug Files)

Sentry在从外部源下载调试信息文件时支持以下压缩方法:Gzip、zlib(带或不带头)、Zstandard和CAB。

Microsoft符号服务器协议的约定是存储这样的文件,文件扩展名的最后一个字符替换为。一个完整的例子是:KERNEL32.dll/590285E9e0000/KERNEL32.dl。这在您自己的存储库中是不需要的,因为Sentry会检测所有路径上的压缩。

源上下文)(Source Context)

如果Sentry可以访问应用程序源代码,它可以在堆栈帧的位置周围显示代码片段。某些SDK可以自动解析这个源上下文,比如pythonsdk,因为它们可以在运行时访问未混淆的源代码。

要获取本机应用程序的源上下文,需要将源代码与调试信息文件一起上载。推荐的方法是使用sentry cli。有关详细信息,请参阅创建源包。

源包在“调试文件设置”页上显示为常规调试文件。它们被指定为“源包”,并具有源标记。为了使它们与崩溃报告相匹配,它们携带的调试文件与从中创建的相应调试信息文件相同。