(LastCommitID=fed41342a82f5a3a9201819a82bf7a48313e296b)
ASAN RT初始化前的Routine:_start
→
\rightarrow
→__libc_start_main
→
\rightarrow
→__libc_csu_init
→
\rightarrow
→asan.module_ctor
(ctor节在main
函数前调用,用来执行构造函数、完成变量初始化等逻辑)
→
\rightarrow
→__asan_init
0 关于ASAN初始化
AsanInitInternal
可在main
函数之前__asan_init
中调用,也可在运行过程中由AsanInitFromRtl
动态初始化。
AsanActivate
若检查发现ASAN Deactive,就激活ASAN。(初始默认无需激活)
// compiler-rt/lib/asan/asan_rtl.cpp
static void AsanInitInternal() {...}
...
// Initialize as requested from some part of ASan runtime library (interceptors,
// allocator, etc).
void AsanInitFromRtl() {
AsanInitInternal();
}
...
// Initialize as requested from instrumented application code.
// We use this call as a trigger to wake up ASan from deactivated state.
void __asan_init() {
AsanActivate();
AsanInitInternal();
}
AsanInitInternal
的逻辑包含如下:
1 CacheBinaryName
ReadBinaryName
通过readlink
系统调用从/proc/self/exe
读取二进制文件镜像名存储在binary_name_cache_str
,如/home/leone/文档/test/a.out
。
ReadProcessName
将/proc/self/cmdline
文件中存储的进程名读取到process_name_cache_str
,如a.out
。
// compiler-rt/lib/sanitizer_common/sanitizer_common.cpp
// Call once to make sure that binary_name_cache_str is initialized
void CacheBinaryName() {
...
ReadBinaryName(binary_name_cache_str, sizeof(binary_name_cache_str));
ReadProcessName(process_name_cache_str, sizeof(process_name_cache_str));
}
2 InitializeFlags
根据sanitizer_flags.inc
、asan_flags.inc
、lsan_flags.inc
和ubsan_flags.inc
初始化并(使用各自的Parser)注册各自的Flag结构体(内存分配操作用到了LowLevelAllocator::Allocate
)。
Parser再进一步解析额外的环境变量选项。
确保 R e d z o n e S i z e ≥ 16 RedzoneSize\geq16 RedzoneSize≥16且 R e d z o n e S i z e = 2 n RedzoneSize=2^n RedzoneSize=2n。
配置Quarantine大小(Quarantine过小会导致漏报)。配置线程局部Quarantine大小(不建议小于64KB)。
// compiler-rt/lib/asan/asan_flags.cpp
// Initialize flags. This must be done early, because most of the
// initialization steps look at flags().
void InitializeFlags() {
// Set the default values and prepare for parsing ASan and common flags.
SetCommonFlagsDefaults();
...
Flags *f = flags();
f->SetDefaults();
FlagParser asan_parser;
RegisterAsanFlags(&asan_parser, f);
RegisterCommonFlags(&asan_parser);
// Set the default values and prepare for parsing LSan and UBSan flags
// (which can also overwrite common flags).
...
__lsan::Flags *lf = __lsan::flags();
lf->SetDefaults();
...
__ubsan::Flags *uf = __ubsan::flags();
uf->SetDefaults();
...
// Ensure that redzone is at least SHADOW_GRANULARITY.
if (f->redzone < (int)SHADOW_GRANULARITY)
f->redzone = SHADOW_GRANULARITY;
...
f->quarantine_size_mb = kDefaultQuarantineSizeMb;
...
if (f->thread_local_quarantine_size_kb < 0) {
const u32 kDefaultThreadLocalQuarantineSizeKb =
// It is not advised to go lower than 64Kb, otherwise quarantine batches
// pushed from thread local quarantine to global one will create too
// much overhead. One quarantine batch size is 8Kb and it holds up to
// 1021 chunk, which amounts to 1/8 memory overhead per batch when
// thread local quarantine is set to 64Kb.
(ASAN_LOW_MEMORY) ? 1 << 6 : FIRST_32_SECOND_64(1 << 8, 1 << 10);
f->thread_local_quarantine_size_kb = kDefaultThreadLocalQuarantineSizeKb;
}
...
}
3 AsanCheckIncompatibleRT
记录当前进程内存映射布局(查看proc/self/maps
),默认缓存起来。
检查确保内存映射中不存在名字包含libclang_rt.asan
、libasan.so
的Segment。最后标记为ASAN_RT_VERSION_STATIC
,表示静态链接了ASAN Runtime,如libclang_rt.asan-x86_64.a
。
// compiler-rt/lib/asan/asan_linux.cpp
void AsanCheckIncompatibleRT() {
...
if (__asan_rt_version == ASAN_RT_VERSION_UNDEFINED) {
// Ensure that dynamic runtime is not present. We should detect it
// as early as possible, otherwise ASan interceptors could bind to
// the functions in dynamic ASan runtime instead of the functions in
// system libraries, causing crashes later in ASan initialization.
MemoryMappingLayout proc_maps(/*cache_enabled*/true);
char filename[PATH_MAX];
MemoryMappedSegment segment(filename, sizeof(filename));
while (proc_maps.Next(&segment)) {
if (IsDynamicRTName(segment.filename)) {
Report("Your application is linked against "
"incompatible ASan runtimes.\n");
Die();
}
}
__asan_rt_version = ASAN_RT_VERSION_STATIC;
} else ...
}
4 AsanCheckDynamicRTPrereqs
由于链接的ASAN Runtime是Static的,因此该函数不执行任何东西。
5 SetCanPoisonMemory
根据poison_heap
原子设置__asan::can_poison_memory
,表明可以在内存(解)分配时污染堆对应的影子内存。
ASAN_FLAG(
bool, poison_heap, true,
"Poison (or not) the heap memory on [de]allocation. Zero value is useful "
"for benchmarking the allocator or instrumentator.")
6 SetMallocContextSize
根据malloc_context_size
(已经从1改成了30)原子设置__asan::malloc_context_size
,表明内存(解)分配出现内存错误时显示的调用栈层数。
COMMON_FLAG(int, malloc_context_size, 1,
"Max number of stack frames kept for each allocation/deallocation.")
7 InitializeHighMemEnd
H
i
g
h
M
e
m
=
0
x
10007
f
f
f
8000
∼
0
x
7
f
f
f
f
f
f
f
f
f
f
f
HighMem=0x10007fff8000\sim0x7fffffffffff
HighMem=0x10007fff8000∼0x7fffffffffff,对应(Shadow = (Mem >> 3) + 0x7fff8000
)的
S
h
d
o
w
M
e
m
o
r
y
=
0
x
02008
f
f
f
7000
∼
0
x
10007
f
f
f
7
f
f
f
ShdowMemory=0x02008fff7000\sim0x10007fff7fff
ShdowMemory=0x02008fff7000∼0x10007fff7fff(与这一致):
kHighMemEnd = (1ULL << 47) - 1
,即0x7fffffffffff
kHighMemBeg = (kHighMemEnd >> 3) + (0x7FFFFFFF & (~0xFFFULL << 3)) + 1
,即0x10007fff8000
。- (其中3是
kDefaultShadowScale
)
8 AsanDoesNotSupportStaticLinkage
Make sure we are not statically linked.
确保存在Dynamic Seciton _Dynamic
。(?)
9 一些简单配置
函数或操作 | 内容 |
---|---|
AddDieCallback | 将AsanDie 注册为回调函数InternalDieCallbacks ,在调用Die() 时会调用。 |
SetCheckFailedCallback | 将AsanCheckFailed 注册为回调函数CheckFailedCallback ,在调用CheckFailed() 时会调用。 |
SetPrintfAndReportCallback | 将AppendToErrorMessageBuffer 注册为回调函数PrintfAndReportCallback ,在调用CallPrintfAndReportCallback() 时会调用。 |
__sanitizer_set_report_path | 由于common_flags_dont_use.log_path 为空,因此默认输出路径仍为kStderrFd (2) |
__asan_option_detect_stack_use_after_return = false | 不检测栈上的UAR问题。 |
SetLowLevelAllocateMinAlignment | 更新设置LowLevelAllocate 最小对齐粒度,low_level_alloc_min_alignment=SHADOW_GRANULARITY (8),前面使用LowLevelAllocate 时粒度为kLowLevelAllocatorDefaultAlignment |
SetLowLevelAllocateCallback | 将OnLowLevelAllocate 注册为回调函数low_level_alloc_callback ,在调用LowLevelAllocator::Allocate() 时会调用。 |
10 InitializeAsanInterceptors
10.1 InitializeCommonInterceptors
Placement New一个interceptor_metadata_map
。
从INIT_MMAP
开始逐一初始化通用函数拦截器。在此之前,会有一系列变量、函数指针(mmap_type
、__interception::real_mmap
、mmap
、__interceptor_mmap
)被初始化了,如下所示(每个函数的拦截的具体过程会有区别):
// compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
#if SANITIZER_INTERCEPT_MMAP
// INTERCEPTOR Macro (compiler-rt/lib/interception/interception.h) Example:
// typedef void *(*mmap_type)(void *, SIZE_T, int, int, int, OFF_T);
// namespace __interception { mmap_type real_mmap; }
// extern "C" void * mmap(void *, SIZE_T, int, int, int, OFF_T) __attribute__((weak, alias("__interceptor_mmap"), visibility("default")));
// extern "C" __attribute__((visibility("default"))) void * __interceptor_mmap(void *, SIZE_T, int, int, int, OFF_T)
INTERCEPTOR(void *, mmap, void *addr, SIZE_T sz, int prot, int flags, int fd, OFF_T off) {
void *ctx;
if (common_flags()->detect_write_exec)
ReportMmapWriteExec(prot);
if (COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED)
return (void *)internal_mmap(addr, sz, prot, flags, fd, off); // 如果ASAN尚未初始化完成,__interceptor_mmap依然使用internal_mmap
// COMMON_INTERCEPTOR_ENTER Macro (compiler-rt/lib/asan/asan_interceptors.cpp) Example:
// AsanInterceptorContext _ctx = {mmap};
// ctx = (void *)&_ctx;
// (void) ctx; // 当前例子尚未用到
// if (asan_init_is_running)
// return __interception::real_mmap(__VA_ARGS__); //初始化还在进行中,先使用真的
// CHECK(!asan_init_is_running);
// if (UNLIKELY(!asan_inited)) {
// AsanInitFromRtl();
COMMON_INTERCEPTOR_ENTER(ctx, mmap, addr, sz, prot, flags, fd, off);
// Example: { return __interception::real_mmap(addr, sz, prot, flags, fd, off); } // 初始化完成,已明确__interception::real_mmap并使用
COMMON_INTERCEPTOR_MMAP_IMPL(ctx, mmap, addr, sz, prot, flags, fd, off);
}
#endif
INIT_MMAP
会调用InterceptFunction
,其中通过dlsym
找到/usr/lib/x86_64-linux-gnu/libc-2.31.so
中的mmap64
,赋值给__interception::real_mmap
。
// compiler-rt/lib/interception/interception_linux.cpp
// InterceptFunction("mmap", 0x53ef00 <__interception::real_mmap>, mmap(weak alias to)->0x436ae0 <__interceptor_mmap(...)>, 0x436ae0 <__interceptor_mmap(...)>)
bool InterceptFunction(const char *name, uptr *ptr_to_real, uptr func,
uptr wrapper) {
void *addr = GetFuncAddr(name, wrapper);
*ptr_to_real = (uptr)addr;
return addr && (func == wrapper);
}
(请注意__interceptor_mmap
是函数名,__interception::real_mmap
是函数指针,关系粗略可以理解为:函数指针 = &函数名
)
*__interceptor_mmap
{void *(void *, SIZE_T, int, int, int, OFF_T)} 0x436ae0 <__interceptor_mmap(void*, SIZE_T, int, int, int, OFF_T)>
Attempt to take contents of a non-pointer value.
__interceptor_mmap
{void *(void *, SIZE_T, int, int, int, OFF_T)} 0x436ae0 <__interceptor_mmap(void*, SIZE_T, int, int, int, OFF_T)>
type = void *(void *, SIZE_T, int, int, int, OFF_T)
&__interceptor_mmap
0x436ae0 <__interceptor_mmap(void*, SIZE_T, int, int, int, OFF_T)>
type = void *(*)(void *, SIZE_T, int, int, int, OFF_T)
*__interception::real_mmap
{void *(void *, SIZE_T, int, int, int, OFF_T)} 0x7ffff7b5aa20 <mmap64>
type = void *(void *, SIZE_T, int, int, int, OFF_T)
__interception::real_mmap
0x7ffff7b5aa20 <mmap64>
type = void *(*)(void *, SIZE_T, int, int, int, OFF_T)
&__interception::real_mmap
0x53ef00 <__interception::real_mmap>
type = void *(**)(void *, SIZE_T, int, int, int, OFF_T)
10.2 InitializeSignalInterceptors
初始化信号函数拦截器,如__interception::real_signal
指向libc
的ssignal
,__interception::real_sigaction
指向libc
的sigaction
。
10.3 拦截剩下的函数
前面通用函数拦截器是各种Sanitizer
均可能调用的,之后拦截strcat
等ASAN特别需要拦截的函数。
10.4 关键点
大部分拦截的函数都使用真实的函数去执行了,也就是说没有插入额外的操作,主要是内存分配相关的函数被拦截并进入asan_xxx()
的逻辑。
void *asan_memalign(uptr alignment, uptr size, BufferedStackTrace *stack,
AllocType alloc_type);
void asan_free(void *ptr, BufferedStackTrace *stack, AllocType alloc_type);
void asan_delete(void *ptr, uptr size, uptr alignment,
BufferedStackTrace *stack, AllocType alloc_type);
void *asan_malloc(uptr size, BufferedStackTrace *stack);
void *asan_calloc(uptr nmemb, uptr size, BufferedStackTrace *stack);
void *asan_realloc(void *p, uptr size, BufferedStackTrace *stack);
void *asan_reallocarray(void *p, uptr nmemb, uptr size,
BufferedStackTrace *stack);
void *asan_valloc(uptr size, BufferedStackTrace *stack);
void *asan_pvalloc(uptr size, BufferedStackTrace *stack);
void *asan_aligned_alloc(uptr alignment, uptr size, BufferedStackTrace *stack);
int asan_posix_memalign(void **memptr, uptr alignment, uptr size,
BufferedStackTrace *stack);
uptr asan_malloc_usable_size(const void *ptr, uptr pc, uptr bp);
uptr asan_mz_size(const void *ptr);
void asan_mz_force_lock();
void asan_mz_force_unlock();
11 CheckASLR、AndroidLogInit和ReplaceSystemMalloc
当前平台下不做任何事情。
12 DisableCoreDumperIfNecessary
依据disable_coredump
设置RLIMIT_CORE
的限长为0,也就是进制核心转储。
COMMON_FLAG(
bool, disable_coredump, (SANITIZER_WORDSIZE == 64) && !SANITIZER_GO,
"Disable core dumping. By default, disable_coredump=1 on 64-bit to avoid"
" dumping a 16T+ core file. Ignored on OSes that don't dump core by"
" default and for sanitizers that don't reserve lots of virtual memory.")
13 InitializeShadowMemory
查看内存映射布局,确保ShadowMemory不和Segment重叠,即当前整个ShadowMemory范围空闲可用。(如若ShadowMemory范围并不是全部可用,就需要引入MidMem)
打印地址空间布局:
|| `[0x10007fff8000, 0x7fffffffffff]` || HighMem ||
|| `[0x02008fff7000, 0x10007fff7fff]` || HighShadow ||
|| `[0x00008fff7000, 0x02008fff6fff]` || ShadowGap ||
|| `[0x00007fff8000, 0x00008fff6fff]` || LowShadow ||
|| `[0x000000000000, 0x00007fff7fff]` || LowMem ||
MemToShadow(shadow): 0x00008fff7000 0x000091ff6dff 0x004091ff6e00 0x02008fff6fff
redzone=16
max_redzone=2048
quarantine_size_mb=256M
thread_local_quarantine_size_kb=1024K
malloc_context_size=30
SHADOW_SCALE: 3
SHADOW_GRANULARITY: 8
SHADOW_OFFSET: 0x7fff8000
定址映射LowShadow和HighShadow并且不保留Swap空间(MAP_NORESERVE | MAP_FIXED
),进一步不采用HugePage且不转储(MADV_NOHUGEPAGE | MADV_DONTDUMP
)。
映射ShadowGap并禁止访问(PROT_NONE
)。
14 AsanTSDInit
初始化tsd_key
(Thread-Specifical Data Key),对应析构函数为PlatformTSDDtor
。
15 InstallDeadlySignalHandlers
为主线程创建备用的信号栈(当前初始化还在进行当中,调用真正的sigaltstack
),大小为4* SIGSTKSZ=0x8000
然后依据sanitizer_flags.inc
默认对SIGSEGV、SIGBUS、SIGFPE注册(消亡)信号处理函数AsanOnDeadlySignal
。
// compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
#define COMMON_FLAG_HANDLE_SIGNAL_HELP(signal) \
"Controls custom tool's " #signal " handler (0 - do not registers the " \
"handler, 1 - register the handler and allow user to set own, " \
"2 - registers the handler and block user from changing it). "
COMMON_FLAG(HandleSignalMode, handle_segv, kHandleSignalYes,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGSEGV))
COMMON_FLAG(HandleSignalMode, handle_sigbus, kHandleSignalYes,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGBUS))
COMMON_FLAG(HandleSignalMode, handle_abort, kHandleSignalNo,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGABRT))
COMMON_FLAG(HandleSignalMode, handle_sigill, kHandleSignalNo,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGILL))
COMMON_FLAG(HandleSignalMode, handle_sigtrap, kHandleSignalNo,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGTRAP))
COMMON_FLAG(HandleSignalMode, handle_sigfpe, kHandleSignalYes,
COMMON_FLAG_HANDLE_SIGNAL_HELP(SIGFPE))
16 InitializeAllocator(很重要又很复杂)
从ASAN_Flags和Common_Flags读取内存分配相关选项:
// compiler-rt/lib/asan/asan_allocator.h
struct AllocatorOptions {
u32 quarantine_size_mb;
u32 thread_local_quarantine_size_kb;
u16 min_redzone;
u16 max_redzone;
u8 may_return_null;
u8 alloc_dealloc_mismatch;
s32 release_to_os_interval_ms;
void SetFrom(const Flags *f, const CommonFlags *cf);
void CopyTo(Flags *f, CommonFlags *cf);
};
// compiler-rt/lib/asan/asan_flags.inc
ASAN_FLAG(int, quarantine_size_mb, -1,
"Size (in Mb) of quarantine used to detect use-after-free "
"errors. Lower value may reduce memory usage but increase the "
"chance of false negatives.")
ASAN_FLAG(int, thread_local_quarantine_size_kb, -1,
"Size (in Kb) of thread local quarantine used to detect "
"use-after-free errors. Lower value may reduce memory usage but "
"increase the chance of false negatives. It is not advised to go "
"lower than 64Kb, otherwise frequent transfers to global quarantine "
"might affect performance.")
ASAN_FLAG(int, redzone, 16,
"Minimal size (in bytes) of redzones around heap objects. "
"Requirement: redzone >= 16, is a power of two.")
ASAN_FLAG(int, max_redzone, 2048,
"Maximal size (in bytes) of redzones around heap objects.")
// Turn off alloc/dealloc mismatch checker on Mac and Windows for now.
// https://github.com/google/sanitizers/issues/131
// https://github.com/google/sanitizers/issues/309
// TODO(glider,timurrrr): Fix known issues and enable this back.
ASAN_FLAG(bool, alloc_dealloc_mismatch,
!SANITIZER_MAC && !SANITIZER_WINDOWS && !SANITIZER_ANDROID,
"Report errors on malloc/delete, new/free, new/delete[], etc.")
// compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
COMMON_FLAG(bool, allocator_may_return_null, false,
"If false, the allocator will crash instead of returning 0 on "
"out-of-memory.")
COMMON_FLAG(s32, allocator_release_to_os_interval_ms,
((bool)SANITIZER_FUCHSIA || (bool)SANITIZER_WINDOWS) ? -1 : 5000,
"Only affects a 64-bit allocator. If set, tries to release unused "
"memory to the OS, but not more often than this interval (in "
"milliseconds). Negative values mean do not attempt to release "
"memory to the OS.\n")
基于选项初始化Allocator(包括quarantine和redzone等,此外会在0x640000000000~0x640000003000
初始化RegionInfo,并污染这块区域,具体用fa
值PoisonShadow
0x00000c807fff8000~0x00000c807fff8600
,这应该是第一次PoisonShadow
?)
下面是一些备忘:
变量 | 类型 |
---|---|
struct Allocator instance | struct __asan::Allocator |
instance.allocator | __sanitizer::CombinedAllocator<__sanitizer::SizeClassAllocator64<__asan::AP64<__sanitizer::LocalAddressSpaceView> >, __sanitizer::LargeMmapAllocatorPtrArrayDynamic> |
instance.allocator.primary_ | __sanitizer::SizeClassAllocator64<__asan::AP64<__sanitizer::LocalAddressSpaceView> |
instance.allocator.secondary_ | __sanitizer::LargeMmapAllocator<__asan::AsanMapUnmapCallback, __sanitizer::LargeMmapAllocatorPtrArrayDynamic, __sanitizer::LocalAddressSpaceView> |
17 ASAN初始化大体完成
// compiler-rt/lib/asan/asan_rtl.cpp
// On Linux AsanThread::ThreadStart() calls malloc() that's why asan_inited
// should be set to 1 prior to initializing the threads.
asan_inited = 1;
asan_init_is_running = false;
18 InitializeCoverage
设置不记录覆盖率。
使用__cxa_atexit
注册__sanitizer_cov_dump
和AtCxaAtexit
使得在程序退出或共享库卸载时调用。
在InternalDieCallbacks
中注册消亡回调函数__sanitizer_cov_dump
。
InitTlsSize
调用_dl_get_tls_static_info
获取tls_size
。
CreateMainThread
创建主线程上下文,与tsd_key
绑定。然后针对主线程,通过查询proc_map和fs寄存器分别取得栈和TLS的地址区间,并将其标记为可寻址,PosionShadow为0。最后将主线程标记为运行中。