Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support symbolicating native call stacks with --split-debug-info #43612

Closed
xster opened this issue Sep 29, 2020 · 13 comments · Fixed by flutter/flutter#101586
Closed

Support symbolicating native call stacks with --split-debug-info #43612

xster opened this issue Sep 29, 2020 · 13 comments · Fixed by flutter/flutter#101586
Assignees
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends.

Comments

@xster
Copy link
Contributor

xster commented Sep 29, 2020

Downstream feature request: flutter/flutter#60189

#35851 now supports translating obfuscated Dart stacks into the original stack, but as far as I understand, after applying --split-debug-info, we no longer have enough symbols left in the binary to then generate Xcode Debugging Symbols (dSym) from the binary/framework. This means users can no longer symbolize native call stacks into Dart call stacks.

Would it be possible for --split-debug-info to output both a obfuscated Dart -> Dart map as well as a native -> Dart map?

cc @sstrickl and @mraleph

@mit-mit mit-mit added the area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. label Sep 30, 2020
@mkustermann
Copy link
Member

/cc @sstrickl

@sstrickl
Copy link
Contributor

So the separate debugging information (which is in ELF format) contains both the symbols and the DWARF information to symbolicate non-symbolic Dart stack traces. Currently, one way to do so is to use pkg/native_stack_traces.

However, I'm not sure if an easy way to convert ELF files containing static symbol tables and DWARF information to a dSym file (which I'm guessing is the Mach-O equivalent of having a separate file with just debugging information similar to our ELF one) already exists. I'll look into this.

(I'd be happy to just make a Mach-O output instead, but it seems like documentation of the Mach-O format was pulled at some point, so I'm not sure how stable it is outside of "what XCode produces" nowadays.)

@jan-auer
Copy link

jan-auer commented Nov 26, 2020

@sstrickl are you saying that you're generating ELF containers even for iOS / macOS builds?

File formats like MachO and ELF are usually meant to be platform-dependent, so I'm not sure if it makes sense to even convert an ELF file into a MachO file. Many debuggers and symbolizers, including llvm-symbolizer and addr2line are able to read both file formats, but for their respective platform (ELF for Linux and MachO for Apple). The DWARF information contained in the debug sections is equal in its format.

Usually, on iOS builds you would output the entire debugging information into the __debug_* sections of the binary and then have the build toolchain run dsymutil to split it out into the debug companion. This takes care of creating a reference in the binary and moves all relevant sections and load commands over. Note that the convention for section naming on macOS is with two leading underscores instead of a dot.

Another important point is to create a LC_UUID load command before invoking dsymutil. It is essentially the same as the .gnu.build-id section in ELF and allows debuggers to identify a debug companion.

Edit: I just had a look at an iOS build and it is indeed emitting ELF for iOS builds. It would definitely be better to follow the above approach, since that allows tools like XCode to use the debug information. The standard command line utilities for Darwin development by Apple are not able to process ELF.

@fzyzcjy
Copy link
Contributor

fzyzcjy commented Mar 7, 2021

Hi is there any updates? I want to obfuscate my flutter code in release build, and I definitely want to see error stack trace

@mraleph
Copy link
Member

mraleph commented Mar 8, 2021

We are not actively working on this at the moment, because high priority uses cases are all covered to the best of our knowledge.

I want to obfuscate my flutter code in release build, and I definitely want to see error stack trace

You can still manually symbolicate and deobfuscate your crashes, we emit enough information for you to do it.

@mraleph
Copy link
Member

mraleph commented May 17, 2021

I just want to follow up on this given that @bruno-garcia indicated on getsentry/sentry-dart#444 that this is an important issue for Sentry team to resolve.

To the best of my knowledge you have all necessary pieces to make this work, though it would require some changes to the Flutter CLI. We generate Mach-O through XCode toolchain, so I suggest we go the same route for dSYMs. Here is what I suggest we do: instead of using gen_snapshot's --save-debugging-info we instead shell out to dsymtool to do the job. The code that needs to be altered resides around these lines.

Here is quick and dirty prototype, which would produce necessary dSYM files:

diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 74e40a37a9..7c9c5285ce 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -138,9 +138,15 @@ class AOTSnapshotter {
       '--deterministic',
     ];
 
-    // We strip snapshot by default, but allow to suppress this behavior
-    // by supplying --no-strip in extraGenSnapshotOptions.
-    bool shouldStrip = true;
+    final bool targetingApplePlatform =
+        platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64;
+    _logger.printTrace("targetingApplePlatform = ${targetingApplePlatform}");
+    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
+
+    // We strip snapshot by default unless we need to split a dSYM out, but
+    // we allow to suppress stripping by supplying --no-strip in
+    // extraGenSnapshotOptions.
+    bool shouldStrip = !(targetingApplePlatform && shouldSplitDebugInfo);
 
     if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
       _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
@@ -186,7 +192,6 @@ class AOTSnapshotter {
     // multiple debug files.
     final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
     final String debugFilename = 'app.$archName.symbols';
-    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
     if (shouldSplitDebugInfo) {
       _fileSystem.directory(splitDebugInfo)
         .createSync(recursive: true);
@@ -197,7 +202,11 @@ class AOTSnapshotter {
       // Faster async/await
       if (shouldSplitDebugInfo) ...<String>[
         '--dwarf-stack-traces',
-        '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
+        // --save-debugging-info produces non-native symbol format.
+        // NOTE: this will  break `flutter symbolize` so it needs to be changed
+        // to use `atos`.
+        if (!targetingApplePlatform)
+          '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
       ],
       if (dartObfuscation)
         '--obfuscate',
@@ -218,7 +227,7 @@ class AOTSnapshotter {
 
     // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
     // end-developer can link into their app.
-    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
+    if (targetingApplePlatform) {
       final RunResult result = await _buildFramework(
         appleArch: darwinArch,
         isIOS: platform == TargetPlatform.ios,
@@ -227,6 +236,7 @@ class AOTSnapshotter {
         outputPath: outputDir.path,
         bitcode: bitcode,
         quiet: quiet,
+        splitDebugInfo: shouldSplitDebugInfo ? splitDebugInfo : null,
       );
       if (result.exitCode != 0) {
         return result.exitCode;
@@ -244,7 +254,8 @@ class AOTSnapshotter {
     @required String assemblyPath,
     @required String outputPath,
     @required bool bitcode,
-    @required bool quiet
+    @required bool quiet,
+    @required String splitDebugInfo,
   }) async {
     final String targetArch = getNameForDarwinArch(appleArch);
     if (!quiet) {
@@ -298,8 +309,17 @@ class AOTSnapshotter {
     final RunResult linkResult = await _xcode.clang(linkArgs);
     if (linkResult.exitCode != 0) {
       _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
+      return linkResult;
+    }
+    if (splitDebugInfo == null) {
+      return linkResult;
+    }
+    final String dSYMPath = _fileSystem.path.join(splitDebugInfo, targetArch, 'App.dSYM');
+    final RunResult dsymutilResult = await _xcode.dsymutil(['-o', dSYMPath, appLib]);
+    if (dsymutilResult.exitCode != 0) {
+      _logger.printError('Failed to generate dSYM. dsymutil terminated with exit code ${compileResult.exitCode}');
     }
-    return linkResult;
+    return dsymutilResult;
   }
 
   bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart
index a181927b74..be869d61d5 100644
--- a/packages/flutter_tools/lib/src/macos/xcode.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode.dart
@@ -192,6 +192,15 @@ class Xcode {
     );
   }
 
+
+  Future<RunResult> dsymutil(List<String> args) {
+    return _processUtils.run(
+      <String>[...xcrunCommand(), 'dsymutil', ...args],
+      throwOnError: true,
+    );
+  }
+
+
   Future<String> sdkLocation(EnvironmentType environmentType) async {
     assert(environmentType != null);
     final RunResult runResult = await _processUtils.run(

This breaks flutter symbolize though which only works with ELF inputs, so that probably needs to be updated to shell out to atos (which probably requires some minor refactoring work in native_stack_traces package to make DwarfStackTraceDecoder into something which takes a debug-info format agnostic symboliser).

@bruno-garcia do you think you (or somebody else from Sentry) will be able to take over this from here and land necessary changes in Flutter CLI and Dart's native_stack_traces package?

@TeeMoYan
Copy link

I just want to follow up on this given that @bruno-garcia indicated on getsentry/sentry-dart#444 that this is an important issue for Sentry team to resolve.

To the best of my knowledge you have all necessary pieces to make this work, though it would require some changes to the Flutter CLI. We generate Mach-O through XCode toolchain, so I suggest we go the same route for dSYMs. Here is what I suggest we do: instead of using gen_snapshot's --save-debugging-info we instead shell out to dsymtool to do the job. The code that needs to be altered resides around these lines.

Here is quick and dirty prototype, which would produce necessary dSYM files:

diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 74e40a37a9..7c9c5285ce 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -138,9 +138,15 @@ class AOTSnapshotter {
       '--deterministic',
     ];
 
-    // We strip snapshot by default, but allow to suppress this behavior
-    // by supplying --no-strip in extraGenSnapshotOptions.
-    bool shouldStrip = true;
+    final bool targetingApplePlatform =
+        platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64;
+    _logger.printTrace("targetingApplePlatform = ${targetingApplePlatform}");
+    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
+
+    // We strip snapshot by default unless we need to split a dSYM out, but
+    // we allow to suppress stripping by supplying --no-strip in
+    // extraGenSnapshotOptions.
+    bool shouldStrip = !(targetingApplePlatform && shouldSplitDebugInfo);
 
     if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
       _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
@@ -186,7 +192,6 @@ class AOTSnapshotter {
     // multiple debug files.
     final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
     final String debugFilename = 'app.$archName.symbols';
-    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
     if (shouldSplitDebugInfo) {
       _fileSystem.directory(splitDebugInfo)
         .createSync(recursive: true);
@@ -197,7 +202,11 @@ class AOTSnapshotter {
       // Faster async/await
       if (shouldSplitDebugInfo) ...<String>[
         '--dwarf-stack-traces',
-        '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
+        // --save-debugging-info produces non-native symbol format.
+        // NOTE: this will  break `flutter symbolize` so it needs to be changed
+        // to use `atos`.
+        if (!targetingApplePlatform)
+          '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
       ],
       if (dartObfuscation)
         '--obfuscate',
@@ -218,7 +227,7 @@ class AOTSnapshotter {
 
     // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
     // end-developer can link into their app.
-    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
+    if (targetingApplePlatform) {
       final RunResult result = await _buildFramework(
         appleArch: darwinArch,
         isIOS: platform == TargetPlatform.ios,
@@ -227,6 +236,7 @@ class AOTSnapshotter {
         outputPath: outputDir.path,
         bitcode: bitcode,
         quiet: quiet,
+        splitDebugInfo: shouldSplitDebugInfo ? splitDebugInfo : null,
       );
       if (result.exitCode != 0) {
         return result.exitCode;
@@ -244,7 +254,8 @@ class AOTSnapshotter {
     @required String assemblyPath,
     @required String outputPath,
     @required bool bitcode,
-    @required bool quiet
+    @required bool quiet,
+    @required String splitDebugInfo,
   }) async {
     final String targetArch = getNameForDarwinArch(appleArch);
     if (!quiet) {
@@ -298,8 +309,17 @@ class AOTSnapshotter {
     final RunResult linkResult = await _xcode.clang(linkArgs);
     if (linkResult.exitCode != 0) {
       _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
+      return linkResult;
+    }
+    if (splitDebugInfo == null) {
+      return linkResult;
+    }
+    final String dSYMPath = _fileSystem.path.join(splitDebugInfo, targetArch, 'App.dSYM');
+    final RunResult dsymutilResult = await _xcode.dsymutil(['-o', dSYMPath, appLib]);
+    if (dsymutilResult.exitCode != 0) {
+      _logger.printError('Failed to generate dSYM. dsymutil terminated with exit code ${compileResult.exitCode}');
     }
-    return linkResult;
+    return dsymutilResult;
   }
 
   bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart
index a181927b74..be869d61d5 100644
--- a/packages/flutter_tools/lib/src/macos/xcode.dart
+++ b/packages/flutter_tools/lib/src/macos/xcode.dart
@@ -192,6 +192,15 @@ class Xcode {
     );
   }
 
+
+  Future<RunResult> dsymutil(List<String> args) {
+    return _processUtils.run(
+      <String>[...xcrunCommand(), 'dsymutil', ...args],
+      throwOnError: true,
+    );
+  }
+
+
   Future<String> sdkLocation(EnvironmentType environmentType) async {
     assert(environmentType != null);
     final RunResult runResult = await _processUtils.run(

This breaks flutter symbolize though which only works with ELF inputs, so that probably needs to be updated to shell out to atos (which probably requires some minor refactoring work in native_stack_traces package to make DwarfStackTraceDecoder into something which takes a debug-info format agnostic symboliser).

@bruno-garcia do you think you (or somebody else from Sentry) will be able to take over this from here and land necessary changes in Flutter CLI and Dart's native_stack_traces package?

Modified Flutter CLI, build does not generate App.dSYM file?

@scott-the-brewer
Copy link

From the dart side, this is being worked on at https://dart-review.googlesource.com/c/sdk/+/242108

@mraleph
Copy link
Member

mraleph commented Jun 22, 2022

Modified Flutter CLI, build does not generate App.dSYM file?

You mean you patched your Flutter with similar patch, run it and did not get dSYM? Did you make sure that Flutter CLI is recompiled by deleting bin/cache/flutter_tools.snapshot? If you did - could you provide verbose build output (with -v).

@TeeMoYan
Copy link

Modified Flutter CLI, build does not generate App.dSYM file?

You mean you patched your Flutter with similar patch, run it and did not get dSYM? Did you make sure that Flutter CLI is recompiled by deleting bin/cache/flutter_tools.snapshot? If you did - could you provide verbose build output (with -v).

Cool, clearing the cache this worked. already have a dSYM. thanks

@vaind
Copy link

vaind commented Jul 5, 2022

A flutter tool PR draft that uses the native_stack_traces support for dSYM is at flutter/flutter#101586

copybara-service bot pushed a commit that referenced this issue Jul 12, 2022
TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag

Bug: #43612
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
Change-Id: Icda21bb14dcc0cf4784cea118e6ba7dd4edd35aa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250381
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
copybara-service bot pushed a commit that referenced this issue Jul 13, 2022
…mation."

This reverts commit 08c13f1.

Reason for revert: Causing failures on simarm, simarm64, etc

Original change's description:
> [pkg/native_stack_traces] Support Mach-O dSYM debugging information.
>
> TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag
>
> Bug: #43612
> Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
> Change-Id: Icda21bb14dcc0cf4784cea118e6ba7dd4edd35aa
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250381
> Commit-Queue: Tess Strickland <sstrickl@google.com>
> Reviewed-by: Slava Egorov <vegorov@google.com>

# Not skipping CQ checks because original CL landed > 1 day ago.

Bug: #43612
Change-Id: I020c29f7329e9b53a8fe0f4f4a4de4070fca0ec3
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251242
Reviewed-by: Ben Konyi <bkonyi@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
copybara-service bot pushed a commit that referenced this issue Jul 22, 2022
…mation."

This is a reland of commit 08c13f1

Fixes test failures on non-x64 architectures, both in the test
harness and due to DWARF5 line number program headers having a
non-backwards compatible format. (We generate DWARF2 in the
ELF snapshot writer, but the assembler used for assembly snapshots
may generate DWARF5.)

TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag

Original change's description:
> [pkg/native_stack_traces] Support Mach-O dSYM debugging information.
>
> TEST=vm/dart{,_2}/use_dwarf_stack_traces_flag
>
> Bug: #43612
> Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
> Change-Id: Icda21bb14dcc0cf4784cea118e6ba7dd4edd35aa
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250381
> Commit-Queue: Tess Strickland <sstrickl@google.com>
> Reviewed-by: Slava Egorov <vegorov@google.com>

Bug: #43612
Change-Id: I8a9cb70e78bc8594bcae004809c5a1be778d691d
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-dwarf-linux-product-x64-try,vm-kernel-precomp-linux-product-x64-try,vm-kernel-precomp-mac-product-x64-try,pkg-mac-release-arm64-try,vm-kernel-mac-release-arm64-try,vm-kernel-precomp-nnbd-mac-release-arm64-try,vm-kernel-precomp-linux-debug-x64c-try,vm-kernel-nnbd-linux-release-simarm64-try,vm-kernel-precomp-linux-release-simarm_x64-try,vm-kernel-precomp-nnbd-mac-release-simarm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251464
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
@sstrickl
Copy link
Contributor

Version 0.5.0 of pkg/native_stack_traces, which both handles dSYM and has better support for DWARF5 (which has some non-backwards compatible changes from earlier DWARF versions) has relanded in the Dart tree and been pushed to pub.

@mraleph
Copy link
Member

mraleph commented Sep 12, 2022

I think @sstrickl has pushed it over the finish line and everything is working now as it should.

@mraleph mraleph closed this as completed Sep 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants