Bug 473695 - Heaptrack fails with dart executable
Summary: Heaptrack fails with dart executable
Status: RESOLVED FIXED
Alias: None
Product: Heaptrack
Classification: Applications
Component: general (show other bugs)
Version: 1.4.0
Platform: Other Linux
: NOR normal
Target Milestone: ---
Assignee: Milian Wolff
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-08-23 18:47 UTC by Sergio Martins
Modified: 2023-08-24 07:35 UTC (History)
0 users

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sergio Martins 2023-08-23 18:47:49 UTC
When running dart under heaptrack, or even just a dart compiled executable, heaptrack bails out with:

> starting application, this might take some time...
> terminate called after throwing an instance of '(anonymous namespace)::HeapTrack::LockCheckFailed'
> terminate called recursively

To reproduce:

1. Have a main.dart with minimal contents such as:

void main(List<String> args) { print("Hello"); }

2. Download flutter from: https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.10.3-stable.tar.xz

3. Unpack somewhere

4. Just to make sure dart works, let's run:
~/flutter-3.13/bin/cache/dart-sdk/bin/dart main.dart

5. Run under heaptrack:
heaptrack ~/flutter-3.13/bin/cache/dart-sdk/bin/dart main.dart

optionally, you can get a main.exe executable with:
$ flutter-3.13/bin/cache/dart-sdk/bin/dart compile exe main.dart
$ heaptrack main.exe

Which results in the same crash.
Comment 1 Milian Wolff 2023-08-23 19:39:25 UTC
with dart installed from the arch package I get this:

```
$ heaptrack dart main.dart
heaptrack output will be written to "/home/milian/heaptrack.dart.8966.zst"
starting application, this might take some time...
Hello
# it hangs here
```

it hangs because it waits for $something to write to the pipe and then close it, but that never happens - our injection works but nothing ever calls any of the functions we intercept.

it seems that's b/c dart uses `tc_malloc` instead of malloc, which somehow messes with our interception logic. I.e. with `gdb` I get:

```
$ gdb main.exe
(gdb) break malloc
(gdb) r
Breakpoint 1.1, 0x0000555555a28944 in tc_malloc ()
(gdb) bt
#0  0x0000555555a28944 in tc_malloc ()
#1  0x00007ffff7c43bc0 in ?? () from /usr/lib/libc.so.6
#2  0x0000555555783604 in MallocExtension::Initialize() ()
#3  0x0000555555778e13 in ?? ()
#4  0x00007ffff7c27dfe in __libc_start_main () from /usr/lib/libc.so.6
#5  0x0000555555751be5 in _start ()
```

and indeed, this is pretty strange:

```
$ nm -aD main.exe  | grep malloc
00000000004d4940 T __libc_malloc
00000000004d4940 T malloc
00000000004d4940 T tc_malloc
```

note how these _all_ have the same address. I tried to intercept `tc_malloc` like we do for `mi_malloc` but that doesn't work apparently...
Comment 2 Milian Wolff 2023-08-23 19:52:49 UTC
ah, I found something that works:

```
$ heaptrack --use-inject ./main.exe 
heaptrack output will be written to "/home/milian/heaptrack.main.exe.19150.zst"
starting application, this might take some time...
NOTE: heaptrack detected DEBUGINFOD_URLS but will disable it to prevent 
unintended network delays during recording
If you really want to use DEBUGINFOD, export HEAPTRACK_ENABLE_DEBUGINFOD=1
Hello
heaptrack stats:
        allocations:            46
        leaked allocations:     24
        temporary allocations:  4
```

I don't understand why the LD_PRELOAD mechanism doesn't work here...
Comment 3 Milian Wolff 2023-08-24 07:35:50 UTC
The std::terminate call got fixed with this:

commit 74fa950160892b06e26d3358b502a6331c2041bd (HEAD -> master, origin/master, origin/HEAD)
Author: Milian Wolff <mail@milianw.de>
Date:   Thu Aug 24 09:29:09 2023 +0200

    Don't use exceptions when timer thread fails to lock
    
    This is a bit unweildy without C++17 and std::optional, but it gets
    the job done. Apparently throwing exceptions doesn't work from within
    heaptrack when it's injected into a flutter binary, for unclear
    reasons. Maybe due to [1] but that's supposedly fixed in newer flutter
    versions already. But as soon as we throw, we would terminate in the
    context of a terminate.
    
    [1]: https://github.com/flutter/flutter/issues/66575