本文介绍chromium在不同平台上 malloc/new 是如何封装调用的。
从代码中很容易发现,chromium的基础代码并不是仅仅使用“malloc”来分配内存
例如:
renderer(Blink)大部分都是用chromium单独设计的PartitionAlloc和BlinkGC(Oilpan)
像javascript引擎V8这样比较独立的子系统使用自己的内存管理机制
还有部分模块会使用抽象化的内存管理模块如 ShareMemory或者DiscardMemory,和上面的内存管理器类似,他们也有自己的页面级内存管理器
背景:
allocator分配器的目标是在程序编译阶段实现定义malloc/new 在特定的平台上实现哪些任务。与此相关的编译选项包括 “use_allocator”和“win_use_allocator_shim”.
默认的实现方式是:
**windows平台**
use_allocator :winheap, 即windows系统默认的heap;
另外,static_libary(即 外部库)通过设置win_use_allocator_shim会对malloc/new编译出一个封装层,该层对外提供安全的API调用,例如防止第三方模块分配过大的内存而导致出现各种bug。
**linux平台/CrOS**
use_allocator :tcmalloc, 这个是基于third_party/tcmalloc/chromium下的tcmalloc拉出来的。设置use_allocator为none,默认就会编译系统(glibc)的符号表。
**Android平台**
use_allocator :none, 使用的allocator符号就是Android的libc(Bionic)。Bionic作为系统的一部分,对于small devices或者内存紧张的设备来说就是最优的选择。当前Bionic的malloc 符号取决于board的配置并且是可以
修改的(一般情况下Nexus使用dlmalloc或jemalloc)
**Mac/iOS**
use_allocator :none,通常使用系统的分配器实现。
另外,当编译为asan/msan/syzyasan/valgrind 等内存调试模式的时候,allocator shim等是禁用的。
架构模型
allocator的设计目标是既能够为tcmalloc(使用该管理器的平台)提供源文件,又可以为windows shim layer提供linker flags。base是唯一一个依赖于allocator的模块。除了少数可执行程序或动态链接库,没有其他模块再依赖
于allocator模块,这些可执行程序或动态链接库直接或间接使用base模块。
重要的一点,base模块之外,不应该再有使用特殊的allocator分配的地方(如直接使用 third_party/tcmalloc),如果需要使用分配器功能的话,应使用base模块里面的抽象类来完成。(参见/base/allocator/allocator_exrension.hhe /base/memory/)
**为什么base要依赖于allocator**
这是因为他要依赖于具体的allocator来提供服务。以前 base 被当作allocator是不可知的而被其他层用来引入分配属性。这导致了使用上的混淆。
详细参见[allocator cleanuo doc][url-allocator-cleanup]
链接单元(可执行程序或共享库)通过某种方式依赖于base(大部分在代码里面)来自动获取linker flag,以此来引入tcmalloc或者windows shim layer
**source code**
这个目录下仅仅包含了allocator layer(即 shim),该层实现了在不同具体内存分配器之间转换的功能。
tcmalloc的库来自于chromium之外且放置在../../third_party/tcmalloc(目前,实际的路径定义在allocator.gyp 文件)。third party 的源码通过提供vendor-branch SCM的方式来跟踪
chromium的特殊修改,以此来与上游部分的修改独立出来。这样做的目的是促使上游模块来做适配修改这样我们就不需要随证时间的推移而重新拉出新的分支了。
**统一的allocator shim层**
在很多平台上,chrome都会重写 malloc /new 等方法,(包括相应的 free/delete 和其他一些方法)这是为了强制的安全检查以及最近启用的新功能[memory-infra heap profiler][rul-memory-heap-profiler]
以前,不同的平台上对于allocator 在代码处理的地方方法都有自己不同的实现逻辑。统一的allocator shim模块的设计目标就是在中间层定义一个标准的allocator 实现逻辑。
-- 文档:[Allocator shim design doc][url-allocator-shim]
-- 进展: Linux CrOS和android上目前可用
-- bug跟踪:[https://crbug.com/550886][crbug.com/550886].
-- 编译标签: ues_experimental_allocator_shim
**allocator shim模块概览**
allocator shim 模块包含了三部分:
malloc & friends symbols definition |
shimlayer implementation |
Routing to allocator |
-- libc symbols (malloc,calloc, free, ...) -- C++ symbols (operator new, delete, ...) -- glibc weak symbols (__libc_malloc, ...) |
-- Security checks -- Chain of dispatchers that can intercept and override allocations |
-- tcmalloc -- glibc -- Android bionic -- WinHeap
|
**1. malloc 的定义**
这部分主要重写 malloc、 free 、operator new 、operator delete及friends并在allocator shim 内部实现这些方法的调用方式(next point)
这些在头文件allocator_shim_override_* 中描述
*Linux和CrOS上*:这些内存操作方法在allocator_shim_override_libc_symbos.h(包括malloc free friends)和allocator_shim_override_cpp_symbos.h(包括operator new、operator delete、friends)中被声明为外部全局方法
这样可以使主可执行程序所使用的malloc方法与任何第三方内存管理库都可以适当的匹配。Linux上的符号解析是从跟链接单元开始的广度优先搜索,这个是可执行程序(参见 EXECUTABLE AND LINKABLE FORMAT(ELF)- 便携式格式规范))
另外,当tcmalloc作为默认内存分配器的时候,一些其他的glibc 方法在allocator_shim_override_glibc_weak_symbos.h也会有定义。详细的原因参见:The Linux/CrOS shim was introduced by
[crrev.com/1675143004](https://crrev.com/1675143004).
**android上**:(不同于linux和CrOS上的实现)在加载阶段插入实现方法是不可能的,因为android的进程都是android aygote fork出来的,zygote进程会预加载libc.so,而后面本地代码的加载
使用过dlopen完成的(分配的方法是来自于dlopen所加载的库实现的,加载在另外的空间)
这种情况下,该方案通过--Wl,-wrap,malloc 等链接flag代替链接时(即编译时)包装符号表的解决方案。
使用wrap flag会导致:
chrome中所有引用allocator symbos的代码都要重写为引用__wrap_malloc和friends 。__wrap_malloc定义在文件allocator_shim_override_linker_wraped_symbos.h中,实现在allocator shim层中。
最原始的malloc(系统库libc.so中定义的)引用可以通过特定的__real_malloc和friends来实现(它将在加载的时候被重置来对应 malloc)
总之,这种方法对动态加载是透明的,动态加载看到的仍然是未定义的malloc引用。这些方法将会对应libc.so的方法。
详见: [crrev.com/1719433002](https://crrev.com/1719433002).
**2.shim layer层实现**
这部分主要是shim layer层的实现部分。包含了:
--一个独立的链接调度表(指向malloc的函数指针结构体)。调度表可以在运行的时候动态插入(通过API:InsertAllocatorDispatch)。
它们可以拦截和重写allocator方法。
--安全检查(同过std::new_handler在malloc ~ failure过程中 释放等等)
这些都在allocator_shim.cc内实现。
**3.最终的allocator 实现**
上面提到的调度结构链的最终实现单元是在编译的时候定义死的,并最终将allocator指引到最终的实际分配器allocator(如上面背景部分描述的)。这个在allocator_shim_default_dispatch_to_*文件中有引述。
附录:
略......
附:
linux下C的程序可以用wrap的方式替换系统malloc
编译加上选项:gcc -Wl,-wrap,malloc
可以做到对malloc这个函数,linker会调用__wrap_malloc代替之, 若要调用原来的malloc函数__real_malloc;
例如,想把所有的malloc函数都作修改,以便让malloc出的内存都是32字节对齐的。
我们可以给ld传选项“wrap=malloc”, 告诉ld,我们将替换名称为malloc的函数。
接着再如下定义一个新的malloc函数:
void * __wrap_malloc( size_t size) {
void* result;
result=memalign(32, size);
return result;
}
可以看到,程序中有个类似库函数名称的__wrap_malloc。
ld在遇到__wrap选项后,会使用__wrap_malloc函数名替换所有对malloc的调用。
并以此实现替换的作用。
那麽,如果还向调用原函数怎么办呢?
可以使用__real_malloc,这个函数名称就对应了原来的malloc。
每次调用malloc时都打印生成的指针地址。
void * __wrap_malloc( size_t size) {
void* result;
result= __real_malloc( size);
printf("Malloc: allocate %d byte mem, start at %p", size, result);
return result;
}
在chromium的allocator上:
malloc->__wrap_malloc_ShimMalloc->alloc_function->RealMalloc->__real_malloc(android bionic)
libc上:
__real_malloc->je_malloc
/bionic/libc/bionic/malloc_common.cpp
#define Malloc(function) je_ ## function
extern "C" void* malloc(size_t bytes) {
auto _malloc = __libc_globals->malloc_dispatch.malloc;
if (__predict_false(_malloc != nullptr)) {
return _malloc(bytes);
}
return Malloc(malloc)(bytes); //=je_malloc()
}
Jemalloc的路径:external/jemalloc/
#0 je_bitmap_set (bitmap=
176 external/jemalloc/include/jemalloc/internal/bitmap.h: No such file or directory.
(gdb) bt
#0 je_bitmap_set (bitmap=
#1 je_bitmap_sfu (bitmap=
at external/jemalloc/include/jemalloc/internal/bitmap.h:228
#2 arena_run_reg_alloc (run=0x90f82654, bin_info=
#3 arena_malloc_small (tsdn=
#4 je_arena_malloc_hard (tsdn=0x90f6d688, arena=
at external/jemalloc/src/arena.c:2694
#5 0xadbcf302 in je_arena_malloc (arena=
slow_path=
at external/jemalloc/include/jemalloc/internal/arena.h:1365
#6 je_iallocztm (size=
is_metadata=
slow_path=
at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:1061
#7 je_ialloc (tsd=
slow_path=
#8 ialloc_body (slow_path=
usize=
#9 je_malloc (size=
#10 0x97836480 in ShimCppNew () at ../../base/allocator/allocator_shim.cc:171
#11 operator new () at ../../base/allocator/allocator_shim_override_cpp_symbols.h:19
#12 0x98a28576 in re2::Compiler::AllocInst () at ../../third_party/re2/src/re2/compile.cc:288
#13 0x98a287ec in re2::Compiler::ByteRange () at ../../third_party/re2/src/re2/compile.cc:403
#14 0x98a28a92 in re2::Compiler::Literal () at ../../third_party/re2/src/re2/compile.cc:846
#15 0x98a29970 in re2::Compiler::PostVisit () at ../../third_party/re2/src/re2/compile.cc:919
#16 0x98a29b24 in re2::Compiler::PostVisit () at ../../third_party/re2/src/re2/compile.cc:864
#17 0x98a29d34 in re2::Regexp::Walker
#18 0x98a29e3c in WalkExponential () at ../../third_party/re2/src/re2/walker-inl.h:243
#19 re2::Compiler::Compile () at ../../third_party/re2/src/re2/compile.cc:1157
#20 0x98a32a58 in re2::RE2::Init () at ../../third_party/re2/src/re2/re2.cc:214
#21 0x98a32bba in re2::RE2::RE2 () at ../../third_party/re2/src/re2/re2.cc:112
#22 0x97bed83c in StringMismatch () at ../../gpu/config/gpu_control_list.cc:104
#23 0x97bef30e in gpu::GpuControlList::GpuControlListEntry::Contains () at ../../gpu/config/gpu_control_list.cc:1308
#24 0x97bef6a8 in gpu::GpuControlList::GpuControlListEntry::Contains () at ../../gpu/config/gpu_control_list.cc:1354
#25 0x97bf1a10 in gpu::GpuControlList::MakeDecision () at ../../gpu/config/gpu_control_list.cc:1523
#26 0x97fcfee2 in content::GpuDataManagerImplPrivate::UpdateGpuInfoHelper () at ../../content/browser/gpu/gpu_data_manager_impl_private.cc:639
#27 0x97fcd972 in content::GpuDataManagerImpl::UpdateGpuInfo () at ../../content/browser/gpu/gpu_data_manager_impl.cc:154
#28 0x97fd3ab8 in content::GpuProcessHost::OnInitialized () at ../../content/browser/gpu/gpu_process_host.cc:815
#29 0x97fd3002 in DispatchToMethodImpl
#30 DispatchToMethod
#31 DispatchToMethod
() at ../../ipc/ipc_message_templates.h:26
#32 IPC::MessageT
#33 0x97fd30b2 in content::GpuProcessHost::OnMessageReceived () at ../../content/browser/gpu/gpu_process_host.cc:702
#34 0x9813795c in content::ChildProcessHostImpl::OnMessageReceived () at ../../content/common/child_process_host_impl.cc:245
#35 0x97c9215a in IPC::ChannelMojo::OnMessageReceived () at ../../ipc/ipc_channel_mojo.cc:414
#36 0x97c9540e in IPC::internal::MessagePipeReader::Receive () at ../../ipc/ipc_message_pipe_reader.cc:110
#37 0x97c9af82 in IPC::mojom::ChannelStubDispatch::Accept () at gen/ipc/ipc.mojom.cc:268
#38 0x97c9e218 in mojo::InterfaceEndpointClient::HandleValidatedMessage () at ../../mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:411
#39 0x97c97d08 in Accept () at ../../ipc/ipc_mojo_bootstrap.cc:755
#40 0x97c9cf64 in mojo::Connector::ReadSingleMessage () at ../../mojo/public/cpp/bindings/lib/connector.cc:284
#41 0x97c9d0a6 in mojo::Connector::ReadAllAvailableMessages () at ../../mojo/public/cpp/bindings/lib/connector.cc:311
#42 0x97b949e0 in Run () at ../../base/callback.h:80
#43 mojo::SimpleWatcher::OnHandleReady () at ../../mojo/public/cpp/system/simple_watcher.cc:262
#44 0x977dcbde in Run () at ../../base/callback.h:91
#45 base::debug::TaskAnnotator::RunTask () at ../../base/debug/task_annotator.cc:59
#46 0x977ed280 in base::MessageLoop::RunTask () at ../../base/message_loop/message_loop.cc:423
#47 0x977ed790 in base::MessageLoop::DeferOrRunPendingTask () at ../../base/message_loop/message_loop.cc:434