In the realm of C/C++ programming, it's considered essential to run projects under memory leak detectors throughout the development process. This practice is frequently automated by configuring your Continuous Integration (CI) pipeline to execute tests using LSAN, promptly terminating the process if even a single memory leak is detected.
Similar principles hold true for Flutter. Despite Dart benefiting from garbage collection, sizeable applications often incorporate a significant volume of C code through FFI. Even if you're not using C directly, chances are you're using some pub.dev package that calls into a 3rd party native library.
The importance of fixing memory leaks is particularly relevant in the world of embedded devices. Given their prolonged uptime, addressing memory leaks becomes a critical concern to maintain the device's performance and stability over time. Conversely, in the mobile landscape, where apps are often restarted daily, the impact of leaks is usually barely noticed.
Requirements
For the purpose of this article, we'll be using Leak Sanitizer on Linux and Flutter 3.10.
Be sure to have /usr/lib/libasan.so
or similar installed. Usually it's included with gcc
. Clang with its static ASAN runtime should be fine as well.
You'll need dart
and flutter
in $PATH
.
Clone this repo, which contains the samples we'll work on.
Overview of Common Challenges
If you're working with different platforms or using alternative memory tools, I've distilled the essence of our research into this section. Uncovering these hurdles took quite some time, so I'll get straight to the point and present them here. This compilation should hopefully accelerate your investigation, if you're employing different tools.
The problems were mostly related to symbolization of stack frames. While C/C++ function names and line numbers should be printed out of the box, stack frames containing dart code would just show hexadecimal addresses instead of proper names.
- Make sure you're building in AOT mode, as JIT traces will be unreadable
- Make sure your AOT snapshot has debug symbols. While
dart compile
includes debug symbols by default, this is not the case forflutter run --release
, you'll need to pass--no-strip
all the way to dart'sgen_snapshot
binary. - When using pure dart, be sure to use
dart compile aot-snapshot
and notdart compile exe
. While the latter is technically still AOT, it's using non-standard ELF hacks, basically embedding an ELF inside an ELF instead of linking. - LSAN sometimes doesn't symbolize the stack traces, in that case try passing it to asan_symbolize.py.
- Sometimes Dart will unmap the AOT snapshot early, before LSAN gets a chance to print it. Resulting in a trace that while accurate does not show function names. Try ending your program with an exception, like
throw Null;
which seems to prevent this. This is not needed with Flutter, only pure Dart. - When using GDB, to get nice backtraces, you'll need to load the AOT snapshot, usually named
libapp.so
, manually. Otherwise GDB doesn't know about it. Maybe because it's dlopened at runtime.
Creating a leak
Just as event organizers might discreetly place garbage on a beach before participants clean it up during company team-building activities, in a similar vein, we'll deliberately introduce memory leaks before illustrating how to identify them. Keep in mind that neither of these actions should be done in a production environment :).
A simple malloc.call()
from dart:fii
is enough for demonstration purposes, but a more common leak is when passing strings from Dart to C, as toNativeUtf8()
actually allocates memory and requires a manual free()
eventually, which you might forget.
Flutter
Here we'll search for leaks in a flutter application, as opposed to pure dart, which involves a slightly different process.
Our sample is based on the default app template from flutter create
, but with a memory leak.
Leave a Comment
Your Email address will not be published
KDAB is committed to ensuring that your privacy is protected.
For more information about our Privacy Policy, please read our privacy policy