9.9 KiB
This document describes how malloc / new calls are routed in the various Chrome platforms.
Bare in mind that the chromium codebase does not always just use malloc()
.
Some examples:
- Large parts of the renderer (Blink) use two home-brewed allocators, PartitionAlloc and BlinkGC (Oilpan).
- Some subsystems, such as the V8 JavaScript engine, handle memory management autonomously.
- Various parts of the codebase use abstractions such as
SharedMemory
orDiscardableMemory
which, similarly to the above, have their own page-level memory management.
Background
The allocator
target defines at compile-time the platform-specific choice of
the allocator and extra-hooks which services calls to malloc/new. The relevant
build-time flags involved are use_allocator
and use_allocator_shim
.
The default choices are as follows:
Windows
use_allocator: winheap
, the default Windows heap.
Additionally, static_library
(i.e. non-component) builds have a shim
layer wrapping malloc/new, which is controlled by use_allocator_shim
.
The shim layer provides extra security features, such as preventing large
allocations that can hit signed vs. unsigned bugs in third_party code.
Linux Desktop / CrOS
use_allocator: tcmalloc
, a forked copy of tcmalloc which resides in
third_party/tcmalloc/chromium
. Setting use_allocator: none
causes the build
to fall back to the system (Glibc) symbols.
Android
use_allocator: none
, always use the allocator symbols coming from Android's
libc (Bionic). As it is developed as part of the OS, it is considered to be
optimized for small devices and more memory-efficient than other choices.
The actual implementation backing malloc symbols in Bionic is up to the board
config and can vary (typically dlmalloc or jemalloc on most Nexus devices).
Mac/iOS
use_allocator: none
, we always use the system's allocator implementation.
In addition, when building for asan
/ msan
both the allocator and the shim
layer are disabled.
Layering and build deps
The allocator
target provides both the source files for tcmalloc (where
applicable) and the linker flags required for the Windows shim layer.
The base
target is (almost) the only one depending on allocator
. No other
targets should depend on it, with the exception of the very few executables /
dynamic libraries that don't depend, either directly or indirectly, on base
within the scope of a linker unit.
More importantly, no other place outside of /base
should depend on the
specific allocator (e.g., directly include third_party/tcmalloc
).
If such a functional dependency is required that should be achieved using
abstractions in base
(see /base/allocator/allocator_extension.h
and
/base/memory/
)
Why base
depends on allocator
?
Because it needs to provide services that depend on the actual allocator
implementation. In the past base
used to pretend to be allocator-agnostic
and get the dependencies injected by other layers. This ended up being an
inconsistent mess.
See the allocator cleanup doc for more context.
Linker unit targets (executables and shared libraries) that depend in some way
on base
(most of the targets in the codebase) get automatically the correct
set of linker flags to pull in tcmalloc or the Windows shim-layer.
Source code
This directory contains just the allocator (i.e. shim) layer that switches between the different underlying memory allocation implementations.
The tcmalloc library originates outside of Chromium and exists in
../../third_party/tcmalloc
(currently, the actual location is defined in the
allocator.gyp file). The third party sources use a vendor-branch SCM pattern to
track Chromium-specific changes independently from upstream changes.
The general intent is to push local changes upstream so that over time we no longer need any forked files.
Unified allocator shim
On most platforms, Chrome overrides the malloc / operator new symbols (and corresponding free / delete and other variants). This is to enforce security checks and lately to enable the memory-infra heap profiler. Historically each platform had its special logic for defining the allocator symbols in different places of the codebase. The unified allocator shim is a project aimed to unify the symbol definition and allocator routing logic in a central place.
- Full documentation: Allocator shim design doc.
- Current state: Available and enabled by default on Android, CrOS, Linux, Mac OS and Windows.
- Tracking bug: [https://crbug.com/550886][crbug.com/550886].
- Build-time flag:
use_allocator_shim
.
Overview of the unified allocator shim The allocator shim consists of three stages:
+-------------------------+ +-----------------------+ +----------------+
| malloc & friends | -> | shim layer | -> | Routing to |
| symbols definition | | implementation | | allocator |
+-------------------------+ +-----------------------+ +----------------+
| - libc symbols (malloc, | | - Security checks | | - tcmalloc |
| calloc, free, ...) | | - Chain of dispatchers| | - glibc |
| - C++ symbols (operator | | that can intercept | | - Android |
| new, delete, ...) | | and override | | bionic |
| - glibc weak symbols | | allocations | | - WinHeap |
| (__libc_malloc, ...) | +-----------------------+ +----------------+
+-------------------------+
1. malloc symbols definition
This stage takes care of overriding the symbols malloc
, free
,
operator new
, operator delete
and friends and routing those calls inside the
allocator shim (next point).
This is taken care of by the headers in allocator_shim_override_*
.
On Linux/CrOS: the allocator symbols are defined as exported global symbols
in allocator_shim_override_libc_symbols.h
(for malloc
, free
and friends)
and in allocator_shim_override_cpp_symbols.h
(for operator new
,
operator delete
and friends).
This enables proper interposition of malloc symbols referenced by the main
executable and any third party libraries. Symbol resolution on Linux is a breadth first search that starts from the root link unit, that is the executable
(see EXECUTABLE AND LINKABLE FORMAT (ELF) - Portable Formats Specification).
Additionally, when tcmalloc is the default allocator, some extra glibc symbols
are also defined in allocator_shim_override_glibc_weak_symbols.h
, for subtle
reasons explained in that file.
The Linux/CrOS shim was introduced by
crrev.com/1675143004.
On Android: load-time symbol interposition (unlike the Linux/CrOS case) is not
possible. This is because Android processes are fork()
-ed from the Android
zygote, which pre-loads libc.so and only later native code gets loaded via
dlopen()
(symbols from dlopen()
-ed libraries get a different resolution
scope).
In this case, the approach instead of wrapping symbol resolution at link time
(i.e. during the build), via the --Wl,-wrap,malloc
linker flag.
The use of this wrapping flag causes:
- All references to allocator symbols in the Chrome codebase to be rewritten as
references to
__wrap_malloc
and friends. The__wrap_malloc
symbols are defined in theallocator_shim_override_linker_wrapped_symbols.h
and route allocator calls inside the shim layer. - The reference to the original
malloc
symbols (which typically is defined by the system's libc.so) are accessible via the special__real_malloc
and friends symbols (which will be relocated, at load time, againstmalloc
).
In summary, this approach is transparent to the dynamic loader, which still sees undefined symbol references to malloc symbols. These symbols will be resolved against libc.so as usual. More details in crrev.com/1719433002.
2. Shim layer implementation This stage contains the actual shim implementation. This consists of:
- A singly linked list of dispatchers (structs with function pointers to
malloc
-like functions). Dispatchers can be dynamically inserted at runtime (using theInsertAllocatorDispatch
API). They can intercept and override allocator calls. - The security checks (suicide on malloc-failure via
std::new_handler
, etc). This happens insideallocator_shim.cc
3. Final allocator routing
The final element of the aforementioned dispatcher chain is statically defined
at build time and ultimately routes the allocator calls to the actual allocator
(as described in the Background section above). This is taken care of by the
headers in allocator_shim_default_dispatch_to_*
files.
Appendixes
How does the Windows shim layer replace the malloc symbols?
The mechanism for hooking LIBCMT in Windows is rather tricky. The core
problem is that by default, the Windows library does not declare malloc and
free as weak symbols. Because of this, they cannot be overridden. To work
around this, we start with the LIBCMT.LIB, and manually remove all allocator
related functions from it using the visual studio library tool. Once removed,
we can now link against the library and provide custom versions of the
allocator related functionality.
See the script preb_libc.py
in this folder.