马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
ULMK_LOG目次
一、 配景
二、GWP-Asan先容
2.1 什么是GWP-ASan
2.2 GWP-Asan与其他几类工具对比
2.3 GWP-ASan与别的内存分配器的兼容性
三、GWP-Asan怎样使用
3.1 app进程
3.2 native进程
四、GWP-Asan实现原理
4.1 进程启用GWP-Asan
4.2 初始化
4.3 内存分配
4.4 内存对齐
4.4.1 align up 8字节对齐原理
4.4.2 align down 8字节对齐原理
4.5 内存开释
4.6 随机抽样机制
4.6.1 进程随机抽样
4.6.2 malloc随机抽样
4.7 Metadata
4.8 触发tombstone网络日记
4.8.1 怎样触发tombstone网络日记
4.8.2 tombstone怎样网络日记
4.9 核心数据布局
4.10 UML
4.11 代码时序图
4.11.1 初始化
4.11.2 内存分配与开释
4.11.3 非常触发及tombstone网络日记
4.12 内存错误检测原理
4.12.1 BUFFER_OVERFLOW和BUFFER_UNDERFLOW检测
4.12.2 USE_AFTER_FREE检测
4.12.3 DOUBLE_FREE检测
4.12.4 INVALID_FREE检测
4.13 内核怎样设置内存页的访问权限
4.14 内核怎样检测非常访问
五、Debug测试
5.1 app demo测试
5.2 native proc demo测试
六、Android 12 与Android 14差别点
6.1 Android 14新增Recoverable GWP-Asan功能
6.1.1 Recoverable GWP-Asan初始化
6.1.2 app默认开启Recoverable GWP-Asan
6.1.3 Recoverable GWP-Asan内存开释
6.1.4 Tombstone
6.2 Android 14及以上版本默认app启用Recoverable GWP-Asan
6.3 启用GWP-Asan的方式不同等
6.4 Android 14上选项可设置
6.5 Recoverable实现原理
七、性能&稳固性影响
7.1 性能评估
八、GWP-Asan缺陷
九、总结
一、 配景
该文档是基于Android 12版本和Android 14版本做分析。
二、GWP-Asan先容
2.1 什么是GWP-ASan
根本用途:GWP-ASan是一种原生内存分配器功能,在内存分配时额外记录一些信息,用于检测DOUBLE_FREE、USE_AFTER_FREE、INVALID_FREE、BUFFER_OVERFLOW、BUFFER_UNDERFLOW五种内存错误题目。
GWP-ASan 分为可规复的GWP-ASan(Android 14上默认对全部app启用)与根本GWP-ASan,
可规复的 GWP-ASan(Recoverable GWP-ASan) 与根本 GWP-ASan 的差别之处体如今以下几个方面:
1)可规复 GWP-ASan 仅在约莫 1% 的应用启动时启用,而不是在每次应用启动时启用。
2)假如检测到“开释后堆使用”或“堆缓冲区溢出” bug,该 bug 会表现在瓦解陈诉 (tombstone) 中。此瓦解陈诉可通过 ActivityManager#getHistoricalProcessExitReasons API(与原始 GWP-ASan 雷同)获取。
3)可规复的 GWP-ASan 答应发生内存破坏,而且应用会继续运行,而不是在转储瓦解陈诉退却出。固然该过程大概会照常举行,但不会再指定应用的举动。由于内存破坏,应用大概会在将来的恣意时间点瓦解,大概大概会在没有效户可见的影响的情况下继续运行。
4)转储瓦解陈诉后,可规复的 GWP-ASan 会被停用。因此,应用在每次启动时只能获取一份可规复的 GWP-ASan 陈诉。
5)假如应用中安装了自界说信号处理惩罚步调,则体系永久不会调用它来哀求指示可规复 GWP-ASan 故障的 SIGSEGV 信号。
2.2 GWP-Asan与其他几类工具对比
相似点:gwp-asan(Google's Web Protector - AddressSanitizer)和 malloc_debug 都是用于检测用户指定进程的内存错误的工具,但也存在一些区别,如下:
| 检测范例
| 启用方式
| 实现原理
| 缺点
| GWP-Asan
| use-after-free
double-free
invalid-free(开释与分配的地点不同等)
heap-buffer-overflow
heap-buffer-underflow
| app进程:
详见3.1
Native进程:
详见3.2
| 接纳guard page机制,将guard page或已开释的page设置为不可访问,详见第四节。
|
| malloc_debug
| mem-leak
Out-of-bounds
use-after-free
| 为进程设置属性值: adb shell setprop libc.debug.malloc.program xxx_process
| 通过额外的内存来标志内存分配与开释的信息,根据该信息来举行检测与捕获内存错误。
| 1)相比GWP-Asan,斲丧的内存更大
2)被google apex,无法定制化
| HWASAN
| heap-buffer-overflow
Heap-use-after-free
stack-buffer-overflow
global-buffer-overflow
double-free
Use-after-return
Alloc-dealloc-mismatch
use-after-poision
| /
| /
| 1)需重新编译
2)内存开销大、性能影响大,比Asan小
3)依赖于ARMv8支持
4)影响App包体积
5)无法线上线下大规模使用
| Asan
| heap-buffer-overflow
Heap-use-after-free
stack-buffer-overflow
global-buffer-overflow
double-free
Use-after-return
Alloc-dealloc-mismatch
use-after-poision
| /
| /
| 1)需重新编译
2)内存开销大、性能影响大
3)影响App包体积
4)无法线上线下大规模使用
| coredump
| heap-buffer-overflow
Heap-use-after-free
stack-buffer-overflow
global-buffer-overflow
double-free
| /
| /
| 1)memory corruption题目栈非第一现场题目
2)必要相识内存管理的配景知识
3)app无法使用,需厂商客制化支持,默认关闭
| Valgrind
| heap-buffer-overflow
Heap-use-after-free
double-free
out-of-memory
| /
| /
| 1)性能斲丧大、线上无法使用
2)检测时会触发app瓦解
| 2.3 GWP-ASan与别的内存分配器的兼容性
假如先安装GWP-ASan,则GWP-ASan与heapprofd/malloc_debug/malloc_hooks兼容。假如已经安装了这些其他库之一,则不启用GWP-ASan。
三、GWP-Asan怎样使用
3.1 app进程
app进程启用gwp-asan,必要在AndroidManifest.xml中设置gwpAsanMode,如下:
- // AndroidManifest.xml中配置
- <application android:gwpAsanMode="always">
- </application>
- -----------------------------------------------------
- <application>
- <processes>
- <!-- Create the (empty) application process -->
- <process />
- <!-- Create subprocesses with GWP-ASan both explicitly enabled and disabled. -->
- <process android:process=":gwp_asan_enabled"
- android:gwpAsanMode="always" />
- <process android:process=":gwp_asan_disabled"
- android:gwpAsanMode="never" />
- </processes>
- <!-- Target services and activities to be run on either the GWP-ASan enabled or disabled processes. -->
- <activity android:name="android.gwpasan.GwpAsanEnabledActivity"
- android:process=":gwp_asan_enabled" />
- <activity android:name="android.gwpasan.GwpAsanDisabledActivity"
- android:process=":gwp_asan_disabled" />
- <service android:name="android.gwpasan.GwpAsanEnabledService"
- android:process=":gwp_asan_enabled" />
- <service android:name="android.gwpasan.GwpAsanDisabledService"
- android:process=":gwp_asan_disabled" />
- </application>
复制代码 apk安装阶段会调用com_android_internal_os_Zygote.cpp中的android_mallopt方法,举行启用GWP-Asan功能(和native进程启用GWP-Asan方式同等)
- // com_android_internal_os_Zygote.cpp
- static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
- jobjectArray rlimits, jlong permitted_capabilities,
- jlong effective_capabilities, jint mount_external,
- jstring managed_se_info, jstring managed_nice_name,
- bool is_system_server, bool is_child_zygote,
- jstring managed_instruction_set, jstring managed_app_data_dir,
- bool is_top_app, jobjectArray pkg_data_info_list,
- jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
- bool mount_storage_dirs) {
- ......
- bool forceEnableGwpAsan = false;
- switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
- default:
- case RuntimeFlags::GWP_ASAN_LEVEL_NEVER:
- break;
- case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS:
- forceEnableGwpAsan = true;
- [[fallthrough]];
- case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY:
- android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan));
- }
- ......
- }
复制代码 3.2 native进程
native进程启用gwp-asan,有两种方式:
1)在native进程的main函数中,将gwp-asan设置为开启状态
- cc_binary {
- name: "gwp-asan-test",
- srcs: [
- "gwpasantest.cpp",
- ],
- shared_libs: [
- "liblog",
- "libbase",
- "libutils",
- ],
-
- header_libs: [
- "bionic_libc_platform_headers",
- ],
- }
复制代码- #include <stdlib.h>
- #include <stdio.h>
- #include <bionic/malloc.h>
- #include <android/log.h>
- #include <log/log.h>
- #define LOG_TAG "gwp-asan-test"
- void heapUseAfterFree() {
- int *ptr1 = (int*)malloc(4);
- if(ptr1 != NULL) {
- *ptr1 = 0xcccc;
- free(ptr1);
- *ptr1 = 0xabcd;
- }
- }
- void doubleFree() {
- int *ptr = (int*)malloc(4);
- free(ptr);
- free(ptr);
- }
- void heapBufferOverflow() {
- int *ptr = (int*)malloc(sizeof(int)*100);
- *(ptr+101) = 101;
- *(ptr+102) = 102;
- *(ptr+103) = 103;
- *(ptr+104) = 104;
- free(ptr);
- }
- int main(int argc, char **argv) {
-
- // Android R以上版本,启用gwp-asan方式
-
- // 初始化 gwp-asan 选项
- android_mallopt_gwp_asan_options_t gwp_asan_options;
- // 调用 android_mallopt 函数,启用 gwp-asan
- android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options))
-
- // android R版本启用gwp-asan方式
- bool enableGwpAsan = true;
- int opcode = M_INITIALIZE_GWP_ASAN;
- void* arg = &enableGwpAsan;
- size_t arg_size = sizeof(bool);
- bool result = android_mallopt(opcode, arg, arg_size);
- //
- ALOG(LOG_DEBUG, LOG_TAG, "result: %d", result);
-
- heapUseAfterFree();
- doubleFree();
- heapBufferOverflow();
-
- // ...
-
- return 0;
- }
复制代码 2)libc初始化流程阶段的MaybeInitGwpAsan增长判定property属性(默以为true),动态打开或关闭指定的进程的gwp-asan
- bool MaybeInitGwpAsan(libc_globals* globals, const android_mallopt_gwp_asan_options_t& mallopt_options) {
- ......
- // 判断指定进程的设置的properity,返回true
- ......
- if (!ShouldGwpAsanSampleProcess(process_sample_rate)) {
- return false;
- }
- ......
- }
复制代码 native proc demo参考第5.2节.
四、GWP-Asan实现原理
根本原理:进程启用GWP-Asan功能后,malloc的内存分配以页对齐,控制页的读写权限来检测内存访问是否正当,如将某一页的权限改为不可读写,那么随后全部的读写访问都将产生SIGSEGV的错误(由内核发给题目进程)。从而对内存访问举动举行检测,当发生内存访问错误时,tombstone网络题目进程中的内存错误信息。
GWP-Asan筹划框架:
4.1 进程启用GWP-Asan
调用android_mallopt启用GWP-Asan功能,如下:
- // bionic/libc/platform/bionic/malloc.h
- extern "C" bool android_mallopt(int opcode, void* arg, size_t arg_size) {
- if (opcode == M_SET_ALLOCATION_LIMIT_BYTES) {
- return LimitEnable(arg, arg_size);
- }
- if (opcode == M_INITIALIZE_GWP_ASAN) {
- if (arg == nullptr || arg_size != sizeof(bool)) {
- errno = EINVAL;
- return false;
- }
- __libc_globals.mutate([&](libc_globals* globals) {
- return MaybeInitGwpAsan(globals, *reinterpret_cast<bool*>(arg));
- });
- }
- errno = ENOTSUP;
- return false;
- }
- // bionic/libc/bionic/gwp_asan_wrappers.cpp
- // Maybe initializes GWP-ASan. Called by android_mallopt() and libc's
- // initialisation. This should always be called in a single-threaded context.
- // android_mallopt方法和每个进程加载libc库时会调用
- bool MaybeInitGwpAsan(libc_globals* globals, bool force_init) {
- // 该进程中已经初始化GWP-ASan,直接返回
- if (GwpAsanInitialized) {
- error_log("GWP-ASan was already initialized for this process.");
- return false;
- }
-
- // If the caller hasn't forced GWP-ASan on, check whether we should sample
- // this process.
- // 如果该进程没有指定强制开启GWP-ASan,那么是否开启由ShouldGwpAsanSampleProcess决定,
- // 产生的随机数为128的倍数,返回为true。
- if (!force_init && !ShouldGwpAsanSampleProcess()) {
- return false;
- }
-
- // GWP-ASan is compatible with heapprofd/malloc_debug/malloc_hooks iff
- // GWP-ASan was installed first. If one of these other libraries was already
- // installed, we don't enable GWP-ASan. These libraries are normally enabled
- // in libc_init after GWP-ASan, but if the new process is a zygote child and
- // trying to initialize GWP-ASan through mallopt(), one of these libraries may
- // be installed. It may be possible to change this in future by modifying the
- // internal dispatch pointers of these libraries at this point in time, but
- // given that they're all debug-only, we don't really mind for now.
- if (GetDefaultDispatchTable() != nullptr) {
- // Something else is installed.
- return false;
- }
-
- // GWP-ASan's initialization is always called in a single-threaded context, so
- // we can initialize lock-free.
- // Set GWP-ASan as the malloc dispatch table.
- // 在原生的malloc\free阶段会做dispatch_table判断,调用gwp-asan malloc\free
- globals->malloc_dispatch_table = gwp_asan_dispatch;
- atomic_store(&globals->default_dispatch_table, &gwp_asan_dispatch);
-
- // If malloc_limit isn't installed, we can skip the default_dispatch_table
- // lookup.
- if (GetDispatchTable() == nullptr) {
- atomic_store(&globals->current_dispatch_table, &gwp_asan_dispatch);
- }
-
- #ifndef LIBC_STATIC
- SetGlobalFunctions(gwp_asan_gfunctions);
- #endif // LIBC_STATIC
-
- GwpAsanInitialized = true;
-
- gwp_asan_initialize(NativeAllocatorDispatch(), nullptr, nullptr);
-
- return true;
- }
- // This function handles initialisation as asked for by MallocInitImpl. This
- // should always be called in a single-threaded context.
- bool gwp_asan_initialize(const MallocDispatch* dispatch, bool*, const char*) {
- prev_dispatch = dispatch;
- // 默认选项配置
- Options Opts;
- Opts.Enabled = true;
- Opts.MaxSimultaneousAllocations = 32;
- Opts.SampleRate = 2500; // 采样率为2500ms
- Opts.InstallSignalHandlers = false;
- Opts.InstallForkHandlers = true;
- Opts.Backtrace = android_unsafe_frame_pointer_chase;
-
- // gwp-asan初始化,详见4.1
- GuardedAlloc.init(Opts);
- // TODO(b/149790891): The log line below causes ART tests to fail as they're
- // not expecting any output. Disable the output for now.
- // info_log("GWP-ASan has been enabled.");
- // 将分配器中两个指针赋值给全局变量,这样可以在生成tombstone时方便地获取它们,
- // 通过它们便可以得知重要的调试信息(metadata记录了alloc/free的调用栈信息)
- __libc_shared_globals()->gwp_asan_state = GuardedAlloc.getAllocatorState();
- __libc_shared_globals()->gwp_asan_metadata = GuardedAlloc.getMetadataRegion();
- return true;
- }
复制代码 MaxSimultaneousAllocations:Guarded Pool Memory中可用于分配的slots(pages)数目。当MaxSimultaneousAllocations为32时,Guarded Pool Memory必要分配65页,Metadata必要分配5页,FreeSlots必要分配1页,共泯灭284KiB(1Kib=1024bytes)。全部的分配都通过mmap举行,区别在于Guarded Pool Memory初始化后全部的页都不可读写,实际分配时再改变页的属性,而Metadata和FreeSlots在初始化后便可以读写了。
SampleRate:采样率,2500次分配会触发1次采样,进而从Guarded Pool Memory中分配。
Backtrace:获取调用栈时接纳的函数,这里接纳的是基于FP的栈回溯方案。通常而言,它只实用于64位场景。由于只有在64位场景下,FP才会默认存入栈中。
4.2 初始化
GWP-Asan初始化紧张做了以下事变:
1)盘算出GWP-Asan内存池中的内存页数目,N个slot page,N+1个guard page,1个internal detetor page,N幂个Metadata page,1个slotfree array page
2)申请内存,并将全部内存页状态属性设置为PORT_NONE(不可访问)
3)获取ProcessSample、SampleRate等属性值
4)根据SampleRate盘算出getThreadLocals()->NextSampleCounter(在malloc阶段中使用)
5)盘算出GWP-Asan内存池的起始地点与竣事地点
slot page:用于存放原始数据;
guard page:用于检测访问越界;
MetaData:用于存放GWP-Asan alloc/free的信息,包罗内存状态信息(内存分配的地点&巨细&slot内存状态&线程号、内存开释时slot内存状态&线程号)、函数调用栈;
free slot:用于记录空闲的slot page信息;
- void GuardedPoolAllocator::init(const options::Options &Opts) {
- // Note: We return from the constructor here if GWP-ASan is not available.
- // This will stop heap-allocation of class members, as well as mmap() of the
- // guarded slots.
- // 1. 判断是否启用 GWP-ASAN
- if (!Opts.Enabled || Opts.SampleRate == 0 ||
- Opts.MaxSimultaneousAllocations == 0)
- return;
- Check(Opts.SampleRate >= 0, "GWP-ASan Error: SampleRate is < 0.");
- Check(Opts.SampleRate < (1 << 30), "GWP-ASan Error: SampleRate is >= 2^30.");
- Check(Opts.MaxSimultaneousAllocations >= 0,
- "GWP-ASan Error: MaxSimultaneousAllocations is < 0.");
- SingletonPtr = this;
- Backtrace = Opts.Backtrace;
- State.VersionMagic = {
-
- {AllocatorVersionMagic::kAllocatorVersionMagic[0],
- AllocatorVersionMagic::kAllocatorVersionMagic[1],
- AllocatorVersionMagic::kAllocatorVersionMagic[2],
- AllocatorVersionMagic::kAllocatorVersionMagic[3]},
- AllocatorVersionMagic::kAllocatorVersion,
- 0};
- // slot数量,默认32
- State.MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations;
- // linux kernel一个page为4KB
- const size_t PageSize = getPlatformPageSize();
- // getPageAddr() and roundUpTo() assume the page size to be a power of 2.
- assert((PageSize & (PageSize - 1)) == 0);
- State.PageSize = PageSize;
- // Number of pages required =
- // + MaxSimultaneousAllocations * maximumAllocationSize (N pages per slot)
- // + MaxSimultaneousAllocations (one guard on the left side of each slot)
- // + 1 (an extra guard page at the end of the pool, on the right side)
- // + 1 (an extra page that's used for reporting internally-detected crashes,
- // like double free and invalid free, to the signal handler; see
- // raiseInternallyDetectedError() for more info)
- // guard pool byte = 4KB * (2 + 32) + 32 * 4KB,共64个page,包括slot page为32,guard page为33,
- // 还有额外的1个page,用于报告内部检测到的错误,如double free或invalid free的内存错误管理
- size_t PoolBytesRequired =
- PageSize * (2 + State.MaxSimultaneousAllocations) +
- State.MaxSimultaneousAllocations * State.maximumAllocationSize();
- assert(PoolBytesRequired % PageSize == 0);
- // 分配一块guard内存池,返回内存池的首地址,并将所有的内存页状态设置为PROT_NONE
- void *GuardedPoolMemory = reserveGuardedPool(PoolBytesRequired);
- // 为 Metadata分配5个page的内存,返回首地址
- // static constexpr const char *kGwpAsanMetadataName = "GWP-ASan Metadata";
- size_t BytesRequired =
- roundUpTo(State.MaxSimultaneousAllocations * sizeof(*Metadata), PageSize);
- Metadata = reinterpret_cast<AllocationMetadata *>(
- map(BytesRequired, kGwpAsanMetadataName));
- // Allocate memory and set up the free pages queue.
- // 为 free slot分配1个page的内存,返回首地址
- // static constexpr const char *kGwpAsanFreeSlotsName = "GWP-ASan Metadata";
- BytesRequired = roundUpTo(
- State.MaxSimultaneousAllocations * sizeof(*FreeSlots), PageSize);
- FreeSlots =
- reinterpret_cast<size_t *>(map(BytesRequired, kGwpAsanFreeSlotsName));
- // Multiply the sample rate by 2 to give a good, fast approximation for (1 /
- // SampleRate) chance of sampling.
- // 计算AdjustedSampleRatePlusOne,在gwsp-asan alloc中的shouldSample()判断条件中会用到
- // 如果采样率不为1,AdjustedSampleRatePlusOne = (Opts.SampleRate) * 2 + 1
- // 如果采样率为1,AdjustedSampleRatePlusOne = 2,在libc中读取 smapleRate prop属性值,属性值为1的话,说明被malloc
- if (Opts.SampleRate != 1)
- AdjustedSampleRatePlusOne = static_cast<uint32_t>(Opts.SampleRate) * 2 + 1;
- else
- AdjustedSampleRatePlusOne = 2;
- initPRNG();
- // 在GWP-Asan malloc时,会用到getThreadLocals()->NextSampleCounter,
- // 若AdjustedSampleRatePlusOne为2,NextSampleCounter为1,说明可以被GWP-Asan malloc
- getThreadLocals()->NextSampleCounter =
- ((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) &
- ThreadLocalPackedVariables::NextSampleCounterMask;
- // 内存池的起始地址与结束地址
- State.GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory);
- State.GuardedPagePoolEnd =
- reinterpret_cast<uintptr_t>(GuardedPoolMemory) + PoolBytesRequired;
- if (Opts.InstallForkHandlers)
- installAtFork();
- }
复制代码 4.3 内存分配
内存分配紧张做了以下几件事变:
1)探求空闲slot,并选择对齐方式。
2)通过mprotect改变该slot的访问状态属性,设置为PROT_READ | PROT_WRITE
3)将此次分配的调用栈记录到对应的Metadata布局体中。
- // bionic/libc/bionic/malloc_common.cpp
- extern "C" void* malloc(size_t bytes) {
- auto dispatch_table = GetDispatchTable();
- void *result;
- if (__predict_false(dispatch_table != nullptr)) {
- result = dispatch_table->malloc(bytes);
- } else {
- result = Malloc(malloc)(bytes);
- }
- if (__predict_false(result == nullptr)) {
- warning_log("malloc(%zu) failed: returning null pointer", bytes);
- return nullptr;
- }
- return MaybeTagPointer(result);
- }
复制代码- // bionic/libc/bionic/gwp_asan_wrappers.cpp
- void* gwp_asan_malloc(size_t bytes) {
- // 判断本次malloc是否被选中
- if (__predict_false(GuardedAlloc.shouldSample())) {
- // gwp-asan alloc分配内存,如果分配成功,返回内存地址
- if (void* result = GuardedAlloc.allocate(bytes)) {
- return result;
- }
- }
- return prev_dispatch->malloc(bytes);
- }
- // external/gwp_asan/gwp_asan/guarded_pool_allocator.h
- // Return whether the allocation should be randomly chosen for sampling.
- // NextSampleCounter为1,return true
- GWP_ASAN_ALWAYS_INLINE bool shouldSample() {
- // NextSampleCounter == 0 means we "should regenerate the counter".
- // == 1 means we "should sample this allocation".
- // AdjustedSampleRatePlusOne is designed to intentionally underflow. This
- // class must be valid when zero-initialised, and we wish to sample as
- // infrequently as possible when this is the case, hence we underflow to
- // UINT32_MAX.
- // 如果NextSampleCounter为0,需要生成一个随机数,赋值给NextSampleCounter
- if (GWP_ASAN_UNLIKELY(getThreadLocals()->NextSampleCounter == 0))
- getThreadLocals()->NextSampleCounter =
- ((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) &
- ThreadLocalPackedVariables::NextSampleCounterMask; // 0x80000000 - 1,即0x7FFFFFFF
- // 如果NextSampleCounter为1或生成的随机数NextSampleCounter为1,返回true
- return GWP_ASAN_UNLIKELY(--getThreadLocals()->NextSampleCounter == 0);
- }
- AdjustedSampleRatePlusOne = static_cast<uint32_t>(Opts.SampleRate) * 2 + 1;
复制代码 shouldSample:为每个线程随机天生一个NextSampleCounter,每调用一次malloc则减一。当NextSampleCounter为1时(--后为0),则shouldSample返回true,分配从Guarded Pool Memory中取,除此之外都返回false。当NextSampleCounter为0时,天生一个新的随机值。Android中默认的采样率为1/2500,因此AdjustedSampleRatePlusOne的值为5001。至于为什么要乘2,是由于此种采样战略的概率应该取全部随机数的均值。
- // external/gwp_asan/gwp_asan/guarded_pool_allocator.cpp
- void *GuardedPoolAllocator::allocate(size_t Size, size_t Alignment) {
- // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall
- // back to the supporting allocator.
- if (State.GuardedPagePoolEnd == 0) {
- getThreadLocals()->NextSampleCounter =
- (AdjustedSampleRatePlusOne - 1) &
- ThreadLocalPackedVariables::NextSampleCounterMask;
- return nullptr;
- }
-
- if (Size == 0)
- Size = 1;
- if (Alignment == 0)
- Alignment = alignof(max_align_t);
- // 1. Size是否超过了此分配区允许分配的最大内存State.maximumAllocationSize,默认4K
- if (!isPowerOfTwo(Alignment) || Alignment > State.maximumAllocationSize() ||
- Size > State.maximumAllocationSize())
- return nullptr;
-
- size_t BackingSize = getRequiredBackingSize(Size, Alignment, State.PageSize);
- if (BackingSize > State.maximumAllocationSize())
- return nullptr;
-
- // Protect against recursivity.
- if (getThreadLocals()->RecursiveGuard)
- return nullptr;
- ScopedRecursiveGuard SRG;
-
- size_t Index;
- {
- ScopedLock L(PoolMutex);
- Index = reserveSlot();
- }
-
- if (Index == kInvalidSlotID)
- return nullptr;
-
- uintptr_t SlotStart = State.slotToAddr(Index);
- AllocationMetadata *Meta = addrToMetadata(SlotStart);
- uintptr_t SlotEnd = State.slotToAddr(Index) + State.maximumAllocationSize();
- uintptr_t UserPtr;
- // Randomly choose whether to left-align or right-align the allocation, and
- // then apply the necessary adjustments to get an aligned pointer.
- // 内存以16字节对齐
- if (getRandomUnsigned32() % 2 == 0)
- UserPtr = alignUp(SlotStart, Alignment);
- else
- UserPtr = alignDown(SlotEnd - Size, Alignment);
-
- assert(UserPtr >= SlotStart);
- assert(UserPtr + Size <= SlotEnd);
-
- // If a slot is multiple pages in size, and the allocation takes up a single
- // page, we can improve overflow detection by leaving the unused pages as
- // unmapped.
- const size_t PageSize = State.PageSize;
- allocateInGuardedPool(
- reinterpret_cast<void *>(getPageAddr(UserPtr, PageSize)),
- roundUpTo(Size, PageSize));
-
- Meta->RecordAllocation(UserPtr, Size);
- {
- ScopedLock UL(BacktraceMutex);
- // 记录alloc的函数调用栈,就是调用android_unsafe_frame_pointer_chase
- Meta->AllocationTrace.RecordBacktrace(Backtrace);
- }
-
- return reinterpret_cast<void *>(UserPtr);
- }
- // 将 Ptr 向下舍入到最接近的 4096 的倍数,即找到包含该指针的页的起始地址
- uintptr_t getPageAddr(uintptr_t Ptr, uintptr_t PageSize) {
- return Ptr & ~(PageSize - 1);
- }
- // 将 Size 向上舍入到最接近的 4096 的倍数。
- // 若Size 是 1 ~ 4096,结果是 4096, Size 是 4097,结果是 8192。
- size_t roundUpTo(size_t Size, size_t Boundary) {
- return (Size + Boundary - 1) & ~(Boundary - 1);
- }
复制代码 4.4 内存对齐
1)malloc分配的首地点为什么必要概率性做align up和align down内存对齐的筹划?
对malloc分配的首地点概率性做align up和align down内存对齐,可以概率性检测出该slot page越界访问的内存错误。长处是镌汰检测工具对体系性能影响,缺点是存在漏检的大概性。
2)为什么向前对齐(align up)不必要做16字节对齐?而向后对齐(align down)必要做16字节对齐?
由于malloc返回的地点本来是16的倍数,因此向前对齐(align up)不必要做16字节对齐。而向后对齐(align down),传入的是SlotEnd - Size,不肯定是16倍数,因此必要做16字节对齐,cpu是16字节访问,可以提升访问服从。
核心代码段:
- void *allocate(size_t Size, size_t Alignment = alignof(max_align_t)); // 16字节
- void *GuardedPoolAllocator::allocate(size_t Size, size_t Alignment) {
- ...
- if (Alignment == 0)
- Alignment = alignof(max_align_t); // 16
-
- ...
- // 内存页的起始地址
- uintptr_t SlotStart = State.slotToAddr(Index);
- // 内存页的结束地址
- uintptr_t SlotEnd = State.slotToAddr(Index) + State.maximumAllocationSize();
- // 内存对齐后的存放数据的起始地址
- uintptr_t UserPtr;
- // Randomly choose whether to left-align or right-align the allocation, and
- // then apply the necessary adjustments to get an aligned pointer.
- if (getRandomUnsigned32() % 2 == 0)
- UserPtr = alignUp(SlotStart, Alignment);
- else
- UserPtr = alignDown(SlotEnd - Size, Alignment);
-
- ...
- }
- uintptr_t GuardedPoolAllocator::alignUp(uintptr_t Ptr, size_t Alignment) {
- assert(isPowerOfTwo(Alignment) && "Alignment must be a power of two!");
- assert(Alignment != 0 && "Alignment should be non-zero");
- // 若Ptr为16的倍数,Ptr & (Alignment - 1)为0,直接返回,无需做对齐
- if ((Ptr & (Alignment - 1)) == 0)
- return Ptr;
- Ptr += Alignment - (Ptr & (Alignment - 1));
- return Ptr;
- }
- uintptr_t GuardedPoolAllocator::alignDown(uintptr_t Ptr, size_t Alignment) {
- assert(isPowerOfTwo(Alignment) && "Alignment must be a power of two!");
- assert(Alignment != 0 && "Alignment should be non-zero");
- if ((Ptr & (Alignment - 1)) == 0)
- return Ptr;
- // Ptr & (Alignment - 1)不为0,向前平移n*16-size,如分配20字节,向前平移12个字节,
- // 这样的话,首地址距离slot page起始地址和slot page结束地址为16 bit的倍数
- Ptr -= Ptr & (Alignment - 1);
- return Ptr;
- }
复制代码 Slot page的起始地点记作:SlotStart
Slot page的起始地点记作:SlotEnd
malloc分配返回的首地点记作:UserPtr
4.4.1 align up 16字节对齐原理
举个例子分析align up对齐的实现原理。如,char* ptr = (char*) malloc(20);,分配20个字节的内存,页起始地点为SlotStart ,页竣事地点为SlotEnd = SlotStart + 4096。
首地点(存放真实数据的起始地点)为slot page的起始地点,即UserPtr = SlotStart.
align up对齐,检测slot page中20字节地区的左侧越界访问的demo案例,如下:
- void alignUpLeftCheck() {
- char* ptr = (char*)malloc(20); // char类型数据占1个字节
- std::cout << "ptr = " << ptr << std::endl;
- // 越界访问到左边的guard page区域,可以检测出来异常,发生crash
- *(ptr-1) = 'A';
- std::cout << "heapCorruptionLeftAdjacent *(ptr-1)" << std::endl;
- }
- /*void alignUpLeftCheck_2() {
- int* ptr = new int[3]; // int类型数据占4个字节
- std::cout << "ptr = " << ptr << std::endl;
-
- // 越界访问到左边的guard page区域,可以检测出来异常,发生crash
- ptr[-1] = 'A';
- std::cout << "heapCorruption left ptr[-1]" << std::endl;
- }*/
复制代码 align up对齐,检测slot page中20字节地区的右侧越界访问的demo案例,如下:
- void alignUpRightCheck() {
- char* ptr = (char*)malloc(20); // char类型数据占1个字节
- std::cout << "ptr = " << ptr << std::endl;
-
- // 越界访问slot page右边区域(4076)的第1个字节,检测不出异常,不发生crash
- int addr = 20;
- *(ptr+addr) = 'A';
- std::cout << "heapCorruptionRight *(ptr+20)" << std::endl;
-
- // 越界访问slot page右边区域(4076)的最后1个字节,检测不出异常,不发生crash
- addr = 4095;
- *(ptr+addr) = 'A';
- std::cout << "alignUpRightCheck *(ptr+4095)" << std::endl;
- // 越界访问到左边的guard page区域,可以检测出来异常,发生crash
- addr = 4096;
- *(ptr+addr) = 'A';
- std::cout << "alignUpRightCheck *(ptr+4096)" << std::endl;
- }
- /*void alignUpRightCheck_2() {
- int* ptr = new int[3]; // int类型数据占4个字节
- std::cout << "ptr = " << ptr << std::endl;
-
- // 越界访问slot page右边区域(4076)的前4个字节,检测不出异常,不发生crash
- ptr[3] = 'A';
- std::cout << "heapCorruption left ptr[3]" << std::endl;
-
- // 越界访问slot page最右边区域(4076)的最后4个字节,检测不出异常,不发生crash
- ptr[1021] = 'A';
- std::cout << "heapCorruption left ptr[1021]" << std::endl;
-
- // 越界访问到右边的guard page区域,可以检测出来异常,发生crash
- ptr[1022] = 'A';
- std::cout << "heapCorruption left ptr[1022]" << std::endl;
- }*/
复制代码
4.4.2 align down 16字节对齐原理
举个例子分析align down对齐的实现原理。如,char* ptr = (char*) malloc(20);,分配20个字节的内存,slot page起始地点为SlotStart ,页竣事地点为SlotEnd = SlotStart + 4096。
首地点(存放真实数据的起始地点)为slot page的竣事地点减去size后向前移12个字节,即UserPtr = SlotEnd - size - n (n:16倍数向前平移n后,首地点为16倍数。如,分配20个字节,向前平移12)
align down对齐且向前移12个字节,检测slot page中20字节地区的左侧越界访问的demo案例,如下:
- void alignDown16_LeftCheck() {
- char* ptr = (char*)malloc(20); // char类型数据占1个字节
- std::cout << "alignDown16_LeftCheck ptr = " << &ptr << std::endl;
- //malloc_test2(&ptr);
- int addr = 0;
- // 越界访问slot page左边区域(4064)的第1个字节,检测不出异常,不发生crash
- addr = 1;
- *(ptr-addr) = 'A';
- std::cout << "alignDown16_LeftCheck *(ptr-1)" << std::endl;
- addr = 4064;
- *(ptr-addr) = 'A';
- std::cout << "alignDown16_LeftCheck *(ptr-4064)" << std::endl;
- // crash occur if 32 bit
- addr = 4065;
- *(ptr-addr) = 'A';
- std::cout << "alignDown16_LeftCheck *(ptr-4065)" << std::endl;
- }
复制代码 检测slot page中20字节地区的右侧越界访问的demo案例,如下:
- void alignDown16_RightCheck() {
- char* ptr = (char*)malloc(20); // char类型数据占1个字节
- std::cout << "ptr = " << ptr << std::endl;
- int addr = 0;
- // 越界访问slot page右边区域(12)的第1个字节,检测不出异常,不发生crash
- addr = 20;
- *(ptr+addr) = 'A';
- std::cout << "heapCorruptionUpAdjacent *(ptr+20)" << std::endl;
-
- // 越界访问slot page右边区域(12)的第12个字节(最后1个字节),检测不出异常,不发生crash
- addr = 31;
- *(ptr-addr) = 'A';
- std::cout << "heapCorruptionLeftAdjacent *(ptr+31)" << std::endl;
-
- // 越界访问到右边的guard page区域,可以检测出来异常,发生crash
- // crash if 32 bit
- addr = 32;
- *(ptr+addr) = 'A';
- std::cout << "alignDown8_RightCheck *(ptr+32)" << std::endl;
- }
复制代码 4.5 内存开释
内存开释紧张做了以下几件事变:
1)当发生INVALID_FREE或DOUBLE_FREE时,自动触发一个SIGSEGV段错误信号
2)内存标志:GWP-ASan 会标志开释的内存页状态属性设置为PROT_NONE,防止后续的非法访问
3)内存池管理:开释的内存页会重新参加到内存池中,以便后续重新分配
- // bionic/libc/bionic/malloc_common.cpp
- extern "C" void free(void* mem) {
- auto dispatch_table = GetDispatchTable();
- mem = MaybeUntagAndCheckPointer(mem);
- if (__predict_false(dispatch_table != nullptr)) {
- dispatch_table->free(mem);
- } else {
- Malloc(free)(mem);
- }
- }
复制代码- // bionic/libc/bionic/gwp_asan_wrappers.cpp
- void gwp_asan_free(void* mem) {
- // 判断是否通过gwp-asan alloc分配的内存
- if (__predict_false(GuardedAlloc.pointerIsMine(mem))) {
- GuardedAlloc.deallocate(mem);
- return;
- }
- prev_dispatch->free(mem);
- }
复制代码- // external/gwp_asan/gwp_asan/guarded_pool_allocator.h
- // Returns whether the provided pointer is a current sampled allocation that
- // is owned by this pool.
- GWP_ASAN_ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const {
- return State.pointerIsMine(Ptr);
- // This holds the state that's shared between the GWP-ASan allocat
复制代码 |