Merge "Implement malloc hooks."

am: af8d54a7d7

Change-Id: Ic89879c033b37b11c56ff557b0af3e95453dc1da
This commit is contained in:
Christopher Ferris 2018-02-14 01:02:59 +00:00 committed by android-build-merger
commit 7cf17cc1b0
16 changed files with 1079 additions and 132 deletions

View file

@ -72,6 +72,12 @@ static constexpr MallocDispatch __libc_malloc_default_dispatch
Malloc(aligned_alloc),
};
// Malloc hooks.
void* (*volatile __malloc_hook)(size_t, const void*);
void* (*volatile __realloc_hook)(void*, size_t, const void*);
void (*volatile __free_hook)(void*, const void*);
void* (*volatile __memalign_hook)(size_t, size_t, const void*);
// In a VM process, this is set to 1 after fork()ing out of zygote.
int gMallocLeakZygoteChild = 0;
@ -190,17 +196,30 @@ extern "C" void* valloc(size_t bytes) {
extern "C" int __cxa_atexit(void (*func)(void *), void *arg, void *dso);
static const char* HOOKS_SHARED_LIB = "libc_malloc_hooks.so";
static const char* HOOKS_PROPERTY_ENABLE = "libc.debug.hooks.enable";
static const char* HOOKS_ENV_ENABLE = "LIBC_HOOKS_ENABLE";
static const char* DEBUG_SHARED_LIB = "libc_malloc_debug.so";
static const char* DEBUG_MALLOC_PROPERTY_OPTIONS = "libc.debug.malloc.options";
static const char* DEBUG_MALLOC_PROPERTY_PROGRAM = "libc.debug.malloc.program";
static const char* DEBUG_MALLOC_ENV_OPTIONS = "LIBC_DEBUG_MALLOC_OPTIONS";
static const char* DEBUG_PROPERTY_OPTIONS = "libc.debug.malloc.options";
static const char* DEBUG_PROPERTY_PROGRAM = "libc.debug.malloc.program";
static const char* DEBUG_ENV_OPTIONS = "LIBC_DEBUG_MALLOC_OPTIONS";
static void* libc_malloc_impl_handle = nullptr;
enum FunctionEnum : uint8_t {
FUNC_INITIALIZE,
FUNC_FINALIZE,
FUNC_GET_MALLOC_LEAK_INFO,
FUNC_FREE_MALLOC_LEAK_INFO,
FUNC_MALLOC_BACKTRACE,
FUNC_LAST,
};
static void* g_functions[FUNC_LAST];
static void (*g_debug_finalize_func)();
static void (*g_debug_get_malloc_leak_info_func)(uint8_t**, size_t*, size_t*, size_t*, size_t*);
static void (*g_debug_free_malloc_leak_info_func)(uint8_t*);
static ssize_t (*g_debug_malloc_backtrace_func)(void*, uintptr_t*, size_t);
typedef void (*finalize_func_t)();
typedef bool (*init_func_t)(const MallocDispatch*, int*, const char*);
typedef void (*get_malloc_leak_info_func_t)(uint8_t**, size_t*, size_t*, size_t*, size_t*);
typedef void (*free_malloc_leak_info_func_t)(uint8_t*);
typedef ssize_t (*malloc_backtrace_func_t)(void*, uintptr_t*, size_t);
// =============================================================================
// Log functions
@ -225,17 +244,20 @@ static ssize_t (*g_debug_malloc_backtrace_func)(void*, uintptr_t*, size_t);
// "*backtrace_size" is set to the maximum number of entries in the back trace
extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overall_size,
size_t* info_size, size_t* total_memory, size_t* backtrace_size) {
if (g_debug_get_malloc_leak_info_func == nullptr) {
void* func = g_functions[FUNC_GET_MALLOC_LEAK_INFO];
if (func == nullptr) {
return;
}
g_debug_get_malloc_leak_info_func(info, overall_size, info_size, total_memory, backtrace_size);
reinterpret_cast<get_malloc_leak_info_func_t>(func)(info, overall_size, info_size, total_memory,
backtrace_size);
}
extern "C" void free_malloc_leak_info(uint8_t* info) {
if (g_debug_free_malloc_leak_info_func == nullptr) {
void* func = g_functions[FUNC_FREE_MALLOC_LEAK_INFO];
if (func == nullptr) {
return;
}
g_debug_free_malloc_leak_info_func(info);
reinterpret_cast<free_malloc_leak_info_func_t>(func)(info);
}
// =============================================================================
@ -252,66 +274,56 @@ static bool InitMallocFunction(void* malloc_impl_handler, FunctionType* func, co
return true;
}
static bool InitMalloc(void* malloc_impl_handler, MallocDispatch* table, const char* prefix) {
if (!InitMallocFunction<MallocCalloc>(malloc_impl_handler, &table->calloc,
prefix, "calloc")) {
static bool InitMallocFunctions(void* impl_handler, MallocDispatch* table, const char* prefix) {
if (!InitMallocFunction<MallocCalloc>(impl_handler, &table->calloc, prefix, "calloc")) {
return false;
}
if (!InitMallocFunction<MallocFree>(malloc_impl_handler, &table->free,
prefix, "free")) {
if (!InitMallocFunction<MallocFree>(impl_handler, &table->free, prefix, "free")) {
return false;
}
if (!InitMallocFunction<MallocMallinfo>(malloc_impl_handler, &table->mallinfo,
prefix, "mallinfo")) {
if (!InitMallocFunction<MallocMallinfo>(impl_handler, &table->mallinfo, prefix, "mallinfo")) {
return false;
}
if (!InitMallocFunction<MallocMallopt>(malloc_impl_handler, &table->mallopt,
prefix, "mallopt")) {
if (!InitMallocFunction<MallocMallopt>(impl_handler, &table->mallopt, prefix, "mallopt")) {
return false;
}
if (!InitMallocFunction<MallocMalloc>(malloc_impl_handler, &table->malloc,
prefix, "malloc")) {
if (!InitMallocFunction<MallocMalloc>(impl_handler, &table->malloc, prefix, "malloc")) {
return false;
}
if (!InitMallocFunction<MallocMallocUsableSize>(
malloc_impl_handler, &table->malloc_usable_size, prefix, "malloc_usable_size")) {
if (!InitMallocFunction<MallocMallocUsableSize>(impl_handler, &table->malloc_usable_size, prefix,
"malloc_usable_size")) {
return false;
}
if (!InitMallocFunction<MallocMemalign>(malloc_impl_handler, &table->memalign,
prefix, "memalign")) {
if (!InitMallocFunction<MallocMemalign>(impl_handler, &table->memalign, prefix, "memalign")) {
return false;
}
if (!InitMallocFunction<MallocPosixMemalign>(malloc_impl_handler, &table->posix_memalign,
prefix, "posix_memalign")) {
if (!InitMallocFunction<MallocPosixMemalign>(impl_handler, &table->posix_memalign, prefix,
"posix_memalign")) {
return false;
}
if (!InitMallocFunction<MallocAlignedAlloc>(malloc_impl_handler, &table->aligned_alloc,
if (!InitMallocFunction<MallocAlignedAlloc>(impl_handler, &table->aligned_alloc,
prefix, "aligned_alloc")) {
return false;
}
if (!InitMallocFunction<MallocRealloc>(malloc_impl_handler, &table->realloc,
prefix, "realloc")) {
if (!InitMallocFunction<MallocRealloc>(impl_handler, &table->realloc, prefix, "realloc")) {
return false;
}
if (!InitMallocFunction<MallocIterate>(malloc_impl_handler, &table->iterate,
prefix, "iterate")) {
if (!InitMallocFunction<MallocIterate>(impl_handler, &table->iterate, prefix, "iterate")) {
return false;
}
if (!InitMallocFunction<MallocMallocDisable>(malloc_impl_handler, &table->malloc_disable,
prefix, "malloc_disable")) {
if (!InitMallocFunction<MallocMallocDisable>(impl_handler, &table->malloc_disable, prefix,
"malloc_disable")) {
return false;
}
if (!InitMallocFunction<MallocMallocEnable>(malloc_impl_handler, &table->malloc_enable,
prefix, "malloc_enable")) {
if (!InitMallocFunction<MallocMallocEnable>(impl_handler, &table->malloc_enable, prefix,
"malloc_enable")) {
return false;
}
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
if (!InitMallocFunction<MallocPvalloc>(malloc_impl_handler, &table->pvalloc,
prefix, "pvalloc")) {
if (!InitMallocFunction<MallocPvalloc>(impl_handler, &table->pvalloc, prefix, "pvalloc")) {
return false;
}
if (!InitMallocFunction<MallocValloc>(malloc_impl_handler, &table->valloc,
prefix, "valloc")) {
if (!InitMallocFunction<MallocValloc>(impl_handler, &table->valloc, prefix, "valloc")) {
return false;
}
#endif
@ -328,102 +340,114 @@ static void malloc_fini_impl(void*) {
fclose(stdout);
fclose(stderr);
g_debug_finalize_func();
reinterpret_cast<finalize_func_t>(g_functions[FUNC_FINALIZE])();
}
static bool CheckLoadMallocHooks(char** options) {
char* env = getenv(HOOKS_ENV_ENABLE);
if ((env == nullptr || env[0] == '\0' || env[0] == '0') &&
(__system_property_get(HOOKS_PROPERTY_ENABLE, *options) == 0 || *options[0] == '\0' || *options[0] == '0')) {
return false;
}
*options = nullptr;
return true;
}
static bool CheckLoadMallocDebug(char** options) {
// If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
char* env = getenv(DEBUG_ENV_OPTIONS);
if (env == nullptr || env[0] == '\0') {
if (__system_property_get(DEBUG_PROPERTY_OPTIONS, *options) == 0 || *options[0] == '\0') {
return false;
}
// Check to see if only a specific program should have debug malloc enabled.
char program[PROP_VALUE_MAX];
if (__system_property_get(DEBUG_PROPERTY_PROGRAM, program) != 0 &&
strstr(getprogname(), program) == nullptr) {
return false;
}
} else {
*options = env;
}
return true;
}
static void ClearGlobalFunctions() {
for (size_t i = 0; i < FUNC_LAST; i++) {
g_functions[i] = nullptr;
}
}
static void* LoadSharedLibrary(const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
void* impl_handle = dlopen(shared_lib, RTLD_NOW | RTLD_LOCAL);
if (impl_handle == nullptr) {
error_log("%s: Unable to open shared library %s: %s", getprogname(), shared_lib, dlerror());
return nullptr;
}
static constexpr const char* names[] = {
"initialize",
"finalize",
"get_malloc_leak_info",
"free_malloc_leak_info",
"malloc_backtrace",
};
for (size_t i = 0; i < FUNC_LAST; i++) {
char symbol[128];
snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);
g_functions[i] = dlsym(impl_handle, symbol);
if (g_functions[i] == nullptr) {
error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);
dlclose(impl_handle);
ClearGlobalFunctions();
return nullptr;
}
}
if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {
dlclose(impl_handle);
ClearGlobalFunctions();
return nullptr;
}
return impl_handle;
}
// Initializes memory allocation framework once per process.
static void malloc_init_impl(libc_globals* globals) {
char value[PROP_VALUE_MAX];
// If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
const char* options = getenv(DEBUG_MALLOC_ENV_OPTIONS);
if (options == nullptr || options[0] == '\0') {
if (__system_property_get(DEBUG_MALLOC_PROPERTY_OPTIONS, value) == 0 || value[0] == '\0') {
return;
}
options = value;
// Check to see if only a specific program should have debug malloc enabled.
char program[PROP_VALUE_MAX];
if (__system_property_get(DEBUG_MALLOC_PROPERTY_PROGRAM, program) != 0 &&
strstr(getprogname(), program) == nullptr) {
return;
}
}
// Load the debug malloc shared library.
void* malloc_impl_handle = dlopen(DEBUG_SHARED_LIB, RTLD_NOW | RTLD_LOCAL);
if (malloc_impl_handle == nullptr) {
error_log("%s: Unable to open debug malloc shared library %s: %s",
getprogname(), DEBUG_SHARED_LIB, dlerror());
const char* prefix;
const char* shared_lib;
char prop[PROP_VALUE_MAX];
char* options = prop;
// Prefer malloc debug since it existed first and is a more complete
// malloc interceptor than the hooks.
if (CheckLoadMallocDebug(&options)) {
prefix = "debug";
shared_lib = DEBUG_SHARED_LIB;
} else if (CheckLoadMallocHooks(&options)) {
prefix = "hooks";
shared_lib = HOOKS_SHARED_LIB;
} else {
return;
}
// Initialize malloc debugging in the loaded module.
auto init_func = reinterpret_cast<bool (*)(const MallocDispatch*, int*, const char*)>(
dlsym(malloc_impl_handle, "debug_initialize"));
if (init_func == nullptr) {
error_log("%s: debug_initialize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
// Get the syms for the external functions.
void* finalize_sym = dlsym(malloc_impl_handle, "debug_finalize");
if (finalize_sym == nullptr) {
error_log("%s: debug_finalize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
void* get_leak_info_sym = dlsym(malloc_impl_handle, "debug_get_malloc_leak_info");
if (get_leak_info_sym == nullptr) {
error_log("%s: debug_get_malloc_leak_info routine not found in %s", getprogname(),
DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
void* free_leak_info_sym = dlsym(malloc_impl_handle, "debug_free_malloc_leak_info");
if (free_leak_info_sym == nullptr) {
error_log("%s: debug_free_malloc_leak_info routine not found in %s", getprogname(),
DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
return;
}
void* malloc_backtrace_sym = dlsym(malloc_impl_handle, "debug_malloc_backtrace");
if (malloc_backtrace_sym == nullptr) {
error_log("%s: debug_malloc_backtrace routine not found in %s", getprogname(),
DEBUG_SHARED_LIB);
dlclose(malloc_impl_handle);
MallocDispatch dispatch_table;
void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &dispatch_table);
if (impl_handle == nullptr) {
return;
}
init_func_t init_func = reinterpret_cast<init_func_t>(g_functions[FUNC_INITIALIZE]);
if (!init_func(&__libc_malloc_default_dispatch, &gMallocLeakZygoteChild, options)) {
dlclose(malloc_impl_handle);
dlclose(impl_handle);
ClearGlobalFunctions();
return;
}
MallocDispatch malloc_dispatch_table;
if (!InitMalloc(malloc_impl_handle, &malloc_dispatch_table, "debug")) {
auto finalize_func = reinterpret_cast<void (*)()>(finalize_sym);
finalize_func();
dlclose(malloc_impl_handle);
return;
}
globals->malloc_dispatch = dispatch_table;
g_debug_finalize_func = reinterpret_cast<void (*)()>(finalize_sym);
g_debug_get_malloc_leak_info_func = reinterpret_cast<void (*)(
uint8_t**, size_t*, size_t*, size_t*, size_t*)>(get_leak_info_sym);
g_debug_free_malloc_leak_info_func = reinterpret_cast<void (*)(uint8_t*)>(free_leak_info_sym);
g_debug_malloc_backtrace_func = reinterpret_cast<ssize_t (*)(
void*, uintptr_t*, size_t)>(malloc_backtrace_sym);
globals->malloc_dispatch = malloc_dispatch_table;
libc_malloc_impl_handle = malloc_impl_handle;
info_log("%s: malloc debug enabled", getprogname());
info_log("%s: malloc %s enabled", getprogname(), prefix);
// Use atexit to trigger the cleanup function. This avoids a problem
// where another atexit function is used to cleanup allocated memory,
@ -478,10 +502,11 @@ extern "C" void malloc_enable() {
#ifndef LIBC_STATIC
extern "C" ssize_t malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count) {
if (g_debug_malloc_backtrace_func == nullptr) {
void* func = g_functions[FUNC_MALLOC_BACKTRACE];
if (func == nullptr) {
return 0;
}
return g_debug_malloc_backtrace_func(pointer, frames, frame_count);
return reinterpret_cast<malloc_backtrace_func_t>(func)(pointer, frames, frame_count);
}
#else
extern "C" ssize_t malloc_backtrace(void*, uintptr_t*, size_t) {

View file

@ -81,6 +81,14 @@ int malloc_info(int __must_be_zero, FILE* __fp) __INTRODUCED_IN(23);
#define M_DECAY_TIME -100
int mallopt(int __option, int __value) __INTRODUCED_IN(26);
/*
* Memory Allocation Hooks
*/
extern void* (*volatile __malloc_hook)(size_t, const void*) __INTRODUCED_IN(28);
extern void* (*volatile __realloc_hook)(void*, size_t, const void*) __INTRODUCED_IN(28);
extern void (*volatile __free_hook)(void*, const void*) __INTRODUCED_IN(28);
extern void* (*volatile __memalign_hook)(size_t, size_t, const void*) __INTRODUCED_IN(28);
__END_DECLS
#endif /* LIBC_INCLUDE_MALLOC_H_ */

View file

@ -1321,7 +1321,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1241,7 +1241,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1346,7 +1346,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1305,7 +1305,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1241,7 +1241,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1303,7 +1303,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -1241,7 +1241,11 @@ LIBC_O {
LIBC_P { # introduced=P
global:
__freading;
__free_hook;
__fwriting;
__malloc_hook;
__memalign_hook;
__realloc_hook;
aligned_alloc;
endhostent;
endnetent;

View file

@ -0,0 +1,66 @@
// ==============================================================
// libc_malloc_hooks.so
// ==============================================================
cc_library {
name: "libc_malloc_hooks",
srcs: [
"malloc_hooks.cpp",
],
static_libs: [
"libasync_safe",
],
multilib: {
lib32: {
version_script: "exported32.map",
},
lib64: {
version_script: "exported64.map",
},
},
include_dirs: ["bionic/libc"],
sanitize: {
never: true,
},
native_coverage: false,
cflags: [
"-Wall",
"-Werror",
"-fno-stack-protector",
],
}
// ==============================================================
// Unit Tests
// ==============================================================
cc_test {
name: "malloc_hooks_unit_tests",
multilib: {
lib32: {
suffix: "32",
},
lib64: {
suffix: "64",
},
},
srcs: [
"tests/malloc_hooks_tests.cpp",
],
whole_static_libs: ["libc_malloc_hooks"],
shared_libs: ["libbase"],
local_include_dirs: ["tests"],
include_dirs: ["bionic/libc", "bionic"],
cflags: [
"-Wall",
"-Werror",
],
}

118
libc/malloc_hooks/README.md Normal file
View file

@ -0,0 +1,118 @@
Malloc Hooks
============
Malloc hooks allows a program to intercept all allocation/free calls that
happen during execution. It is only available in Android P and newer versions
of the OS.
There are two ways to enable these hooks, set a special system
property, or set a special environment variable and run your app/program.
When malloc hooks is enabled, it works by adding a shim layer that replaces
the normal allocation calls. The replaced calls are:
* `malloc`
* `free`
* `calloc`
* `realloc`
* `posix_memalign`
* `memalign`
* `aligned_alloc`
* `malloc_usable_size`
On 32 bit systems, these two deprecated functions are also replaced:
* `pvalloc`
* `valloc`
These four hooks are defined in malloc.h:
void* (*volatile __malloc_hook)(size_t, const void*);
void* (*volatile __realloc_hook)(void*, size_t, const void*);
void (*volatile __free_hook)(void*, const void*);
void* (*volatile __memalign_hook)(size_t, size_t, const void*);
When malloc is called and \_\_malloc\_hook has been set, then the hook
function is called instead.
When realloc is called and \_\_realloc\_hook has been set, then the hook
function is called instead.
When free is called and \_\_free\_hook has been set, then the hook
function is called instead.
When memalign is called and \_\_memalign\_hook has been set, then the hook
function is called instead.
For posix\_memalign, if \_\_memalign\_hook has been set, then the hook is
called, but only if alignment is a power of 2.
For aligned\_alloc, if \_\_memalign\_hook has been set, then the hook is
called, but only if alignment is a power of 2.
For calloc, if \_\_malloc\_hook has been set, then the hook function is
called, then the allocated memory is set to zero.
For the two deprecated functions pvalloc and valloc, if \_\_memalign\_hook
has been set, then the hook is called with an appropriate alignment value.
There is no hook for malloc\_usable\_size as of now.
These hooks can be set at any time, but there is no thread safety, so
the caller must guarantee that it does not depend on allocations/frees
occurring at the same time.
Implementation Details
======================
When malloc hooks is enabled, then the hook pointers are set to
the current default allocation functions. It is expected that if an
app does intercept the allocation/free calls, it will eventually call
the original hook function to do allocations. If the app does not do this,
it runs the risk of crashing whenever a malloc\_usable\_size call is made.
Example Implementation
======================
Below is a simple implementation intercepting only malloc/calloc calls.
void* new_malloc_hook(size_t bytes, const char* arg) {
return orig_malloc_hook(bytes, arg);
}
void orig_malloc_hook = __malloc_hook;
__malloc_hook = new_malloc_hook;
Enabling Examples
=================
### For platform developers
Enable the hooks for all processes:
adb shell stop
adb shell setprop libc.debug.malloc.hooks 1
adb shell start
Enable malloc debug using an environment variable:
adb shell
# export LIBC_HOOK_ENABLE=1
# ls
Any process spawned from this shell will run with malloc hooks enabled.
### For app developers
Enable malloc hooks for a specific program/application:
adb shell setprop wrap.<APP> '"LIBC_HOOKS_ENABLE=1"'
For example, to enable malloc hooks for the google search box:
adb shell setprop wrap.com.google.android.googlequicksearchbox '"LIBC_HOOKS_ENABLE=1 logwrapper"'
adb shell am force-stop com.google.android.googlequicksearchbox
NOTE: On pre-O versions of the Android OS, property names had a length limit
of 32. This meant that to create a wrap property with the name of the app, it
was necessary to truncate the name to fit. On O, property names can be
an order of magnitude larger, so there should be no need to truncate the name
at all.

View file

@ -0,0 +1,26 @@
LIBC_MALLOC_HOOKS {
global:
hooks_aligned_alloc;
hooks_calloc;
hooks_finalize;
hooks_free;
hooks_free_malloc_leak_info;
hooks_get_malloc_leak_info;
hooks_initialize;
hooks_iterate;
hooks_mallinfo;
hooks_malloc;
hooks_malloc_backtrace;
hooks_malloc_disable;
hooks_malloc_enable;
hooks_malloc_usable_size;
hooks_mallopt;
hooks_memalign;
hooks_posix_memalign;
hooks_pvalloc;
hooks_realloc;
hooks_valloc;
local:
*;
};

View file

@ -0,0 +1,24 @@
LIBC_MALLOC_HOOKS {
global:
hooks_aligned_alloc;
hooks_calloc;
hooks_finalize;
hooks_free;
hooks_free_malloc_leak_info;
hooks_get_malloc_leak_info;
hooks_initialize;
hooks_iterate;
hooks_mallinfo;
hooks_malloc;
hooks_malloc_backtrace;
hooks_malloc_disable;
hooks_malloc_enable;
hooks_malloc_usable_size;
hooks_mallopt;
hooks_memalign;
hooks_posix_memalign;
hooks_realloc;
local:
*;
};

View file

@ -0,0 +1,234 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <errno.h>
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#include <private/bionic_malloc_dispatch.h>
// ------------------------------------------------------------------------
// Global Data
// ------------------------------------------------------------------------
const MallocDispatch* g_dispatch;
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Use C style prototypes for all exported functions. This makes it easy
// to do dlsym lookups during libc initialization when hooks are enabled.
// ------------------------------------------------------------------------
__BEGIN_DECLS
bool hooks_initialize(const MallocDispatch* malloc_dispatch, int* malloc_zygote_child,
const char* options);
void hooks_finalize();
void hooks_get_malloc_leak_info(
uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory,
size_t* backtrace_size);
ssize_t hooks_malloc_backtrace(void* pointer, uintptr_t* frames, size_t frame_count);
void hooks_free_malloc_leak_info(uint8_t* info);
size_t hooks_malloc_usable_size(void* pointer);
void* hooks_malloc(size_t size);
void hooks_free(void* pointer);
void* hooks_memalign(size_t alignment, size_t bytes);
void* hooks_aligned_alloc(size_t alignment, size_t bytes);
void* hooks_realloc(void* pointer, size_t bytes);
void* hooks_calloc(size_t nmemb, size_t bytes);
struct mallinfo hooks_mallinfo();
int hooks_mallopt(int param, int value);
int hooks_posix_memalign(void** memptr, size_t alignment, size_t size);
int hooks_iterate(uintptr_t base, size_t size,
void (*callback)(uintptr_t base, size_t size, void* arg), void* arg);
void hooks_malloc_disable();
void hooks_malloc_enable();
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
void* hooks_pvalloc(size_t bytes);
void* hooks_valloc(size_t size);
#endif
static void* default_malloc_hook(size_t bytes, const void*) {
return g_dispatch->malloc(bytes);
}
static void* default_realloc_hook(void* pointer, size_t bytes, const void*) {
return g_dispatch->realloc(pointer, bytes);
}
static void default_free_hook(void* pointer, const void*) {
g_dispatch->free(pointer);
}
static void* default_memalign_hook(size_t alignment, size_t bytes, const void*) {
return g_dispatch->memalign(alignment, bytes);
}
__END_DECLS
// ------------------------------------------------------------------------
bool hooks_initialize(const MallocDispatch* malloc_dispatch, int*, const char*) {
g_dispatch = malloc_dispatch;
__malloc_hook = default_malloc_hook;
__realloc_hook = default_realloc_hook;
__free_hook = default_free_hook;
__memalign_hook = default_memalign_hook;
return true;
}
void hooks_finalize() {
}
void hooks_get_malloc_leak_info(uint8_t** info, size_t* overall_size,
size_t* info_size, size_t* total_memory, size_t* backtrace_size) {
*info = nullptr;
*overall_size = 0;
*info_size = 0;
*total_memory = 0;
*backtrace_size = 0;
}
void hooks_free_malloc_leak_info(uint8_t*) {
}
size_t hooks_malloc_usable_size(void* pointer) {
return g_dispatch->malloc_usable_size(pointer);
}
void* hooks_malloc(size_t size) {
if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
return __malloc_hook(size, __builtin_return_address(0));
}
return g_dispatch->malloc(size);
}
void hooks_free(void* pointer) {
if (__free_hook != nullptr && __free_hook != default_free_hook) {
return __free_hook(pointer, __builtin_return_address(0));
}
return g_dispatch->free(pointer);
}
void* hooks_memalign(size_t alignment, size_t bytes) {
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
return __memalign_hook(alignment, bytes, __builtin_return_address(0));
}
return g_dispatch->memalign(alignment, bytes);
}
void* hooks_realloc(void* pointer, size_t bytes) {
if (__realloc_hook != nullptr && __realloc_hook != default_realloc_hook) {
return __realloc_hook(pointer, bytes, __builtin_return_address(0));
}
return g_dispatch->realloc(pointer, bytes);
}
void* hooks_calloc(size_t nmemb, size_t bytes) {
if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
size_t size;
if (__builtin_mul_overflow(nmemb, bytes, &size)) {
return nullptr;
}
void* ptr = __malloc_hook(size, __builtin_return_address(0));
if (ptr != nullptr) {
memset(ptr, 0, size);
}
return ptr;
}
return g_dispatch->calloc(nmemb, bytes);
}
struct mallinfo hooks_mallinfo() {
return g_dispatch->mallinfo();
}
int hooks_mallopt(int param, int value) {
return g_dispatch->mallopt(param, value);
}
void* hooks_aligned_alloc(size_t alignment, size_t size) {
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
if (!powerof2(alignment)) {
errno = EINVAL;
return nullptr;
}
void* ptr = __memalign_hook(alignment, size, __builtin_return_address(0));
if (ptr == nullptr) {
errno = ENOMEM;
}
return ptr;
}
return g_dispatch->aligned_alloc(alignment, size);
}
int hooks_posix_memalign(void** memptr, size_t alignment, size_t size) {
if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
if (!powerof2(alignment)) {
return EINVAL;
}
*memptr = __memalign_hook(alignment, size, __builtin_return_address(0));
if (*memptr == nullptr) {
return ENOMEM;
}
return 0;
}
return g_dispatch->posix_memalign(memptr, alignment, size);
}
int hooks_iterate(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*) {
return 0;
}
void hooks_malloc_disable() {
}
void hooks_malloc_enable() {
}
ssize_t hooks_malloc_backtrace(void*, uintptr_t*, size_t) {
return 0;
}
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
void* hooks_pvalloc(size_t bytes) {
size_t pagesize = getpagesize();
size_t size = __BIONIC_ALIGN(bytes, pagesize);
if (size < bytes) {
// Overflow
errno = ENOMEM;
return nullptr;
}
return hooks_memalign(pagesize, size);
}
void* hooks_valloc(size_t size) {
return hooks_memalign(getpagesize(), size);
}
#endif

View file

@ -0,0 +1,412 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <fcntl.h>
#include <malloc.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <private/bionic_malloc_dispatch.h>
#include <tests/utils.h>
__BEGIN_DECLS
void get_malloc_leak_info(uint8_t**, size_t*, size_t*, size_t*, size_t*);
void free_malloc_leak_info(uint8_t*);
int malloc_iterate(uintptr_t, size_t, void (*)(uintptr_t, size_t, void*), void*);
void malloc_enable();
void malloc_disable();
ssize_t malloc_backtrace(void*, uintptr_t*, size_t);
__END_DECLS
class MallocHooksTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_EQ(0, setenv("LIBC_HOOKS_ENABLE", "1", true));
initialized_ = false;
}
void TearDown() override {
if (initialized_) {
Reset();
}
ASSERT_EQ(0, unsetenv("LIBC_HOOKS_ENABLE"));
}
void Init() {
initialized_ = true;
malloc_hook_called_ = false;
free_hook_called_ = false;
realloc_hook_called_ = false;
memalign_hook_called_ = false;
void_arg_ = nullptr;
orig_malloc_hook_ = __malloc_hook;
orig_free_hook_ = __free_hook;
orig_realloc_hook_ = __realloc_hook;
orig_memalign_hook_ = __memalign_hook;
}
void Reset() {
__malloc_hook = orig_malloc_hook_;
__free_hook = orig_free_hook_;
__realloc_hook = orig_realloc_hook_;
__memalign_hook = orig_memalign_hook_;
}
void Exec(std::vector<const char*> args);
void RunTest(std::string test_name);
public:
bool initialized_;
int fd_;
std::string raw_output_;
pid_t pid_;
static bool malloc_hook_called_;
static bool free_hook_called_;
static bool realloc_hook_called_;
static bool memalign_hook_called_;
static const void* void_arg_;
static void* (*orig_malloc_hook_)(size_t, const void*);
static void (*orig_free_hook_)(void*, const void*);
static void* (*orig_realloc_hook_)(void*, size_t, const void*);
static void* (*orig_memalign_hook_)(size_t, size_t, const void*);
static void* test_malloc_hook(size_t, const void*);
static void* test_realloc_hook(void* ptr, size_t, const void*);
static void* test_memalign_hook(size_t, size_t, const void*);
static void test_free_hook(void*, const void*);
};
bool MallocHooksTest::malloc_hook_called_;
bool MallocHooksTest::free_hook_called_;
bool MallocHooksTest::realloc_hook_called_;
bool MallocHooksTest::memalign_hook_called_;
const void* MallocHooksTest::void_arg_;
void* (*MallocHooksTest::orig_malloc_hook_)(size_t, const void*);
void (*MallocHooksTest::orig_free_hook_)(void*, const void*);
void* (*MallocHooksTest::orig_realloc_hook_)(void*, size_t, const void*);
void* (*MallocHooksTest::orig_memalign_hook_)(size_t, size_t, const void*);
static void GetExe(std::string* exe_name) {
char path[PATH_MAX];
ssize_t path_len = readlink("/proc/self/exe", path, sizeof(path));
ASSERT_TRUE(path_len >= 0);
*exe_name = std::string(path, path_len);
}
void MallocHooksTest::RunTest(std::string test_name) {
std::vector<const char*> args;
args.push_back("--gtest_also_run_disabled_tests");
std::string filter_arg("--gtest_filter=" + test_name);
args.push_back(filter_arg.c_str());
ExecTestHelper test;
test.Run(
[&]() {
std::string exe_name;
GetExe(&exe_name);
args.insert(args.begin(), exe_name.c_str());
args.push_back(nullptr);
execv(args[0], reinterpret_cast<char* const*>(const_cast<char**>(args.data())));
exit(1);
},
0, nullptr);
}
void* MallocHooksTest::test_malloc_hook(size_t size, const void* arg) {
malloc_hook_called_ = true;
void_arg_ = arg;
return orig_malloc_hook_(size, arg);
}
void* MallocHooksTest::test_realloc_hook(void* ptr, size_t size, const void* arg) {
realloc_hook_called_ = true;
void_arg_ = arg;
return orig_realloc_hook_(ptr, size, arg);
}
void* MallocHooksTest::test_memalign_hook(size_t alignment, size_t size, const void* arg) {
memalign_hook_called_ = true;
void_arg_ = arg;
return orig_memalign_hook_(alignment, size, arg);
}
void MallocHooksTest::test_free_hook(void* ptr, const void* arg) {
free_hook_called_ = true;
void_arg_ = arg;
return orig_free_hook_(ptr, arg);
}
TEST_F(MallocHooksTest, other_malloc_functions) {
RunTest("*.DISABLED_other_malloc_functions");
}
// Call other intercepted functions to make sure there are no crashes.
TEST_F(MallocHooksTest, DISABLED_other_malloc_functions) {
struct mallinfo info = mallinfo();
EXPECT_NE(0U, info.uordblks);
EXPECT_EQ(0, mallopt(-1000, -1000));
void* ptr = malloc(1024);
EXPECT_LE(1024U, malloc_usable_size(ptr));
free(ptr);
}
TEST_F(MallocHooksTest, extended_functions) {
RunTest("*.DISABLED_extended_functions");
}
TEST_F(MallocHooksTest, DISABLED_extended_functions) {
uint8_t* info = nullptr;
size_t overall_size = 100;
size_t info_size = 200;
size_t total_memory = 300;
size_t backtrace_size = 400;
get_malloc_leak_info(&info, &overall_size, &info_size, &total_memory, &backtrace_size);
EXPECT_EQ(nullptr, info);
EXPECT_EQ(0U, overall_size);
EXPECT_EQ(0U, info_size);
EXPECT_EQ(0U, total_memory);
EXPECT_EQ(0U, backtrace_size);
free_malloc_leak_info(info);
malloc_enable();
malloc_disable();
EXPECT_EQ(0, malloc_iterate(0, 0, nullptr, nullptr));
// Malloc hooks doesn't support any backtracing.
EXPECT_EQ(0, malloc_backtrace(nullptr, nullptr, 10));
}
TEST_F(MallocHooksTest, malloc_hook) {
RunTest("*.DISABLED_malloc_hook");
}
TEST_F(MallocHooksTest, DISABLED_malloc_hook) {
Init();
ASSERT_TRUE(__malloc_hook != nullptr);
__malloc_hook = test_malloc_hook;
void* ptr = malloc(1024);
ASSERT_TRUE(ptr != nullptr);
write(0, ptr, 0);
free(ptr);
EXPECT_TRUE(malloc_hook_called_) << "The malloc hook was not called.";
EXPECT_TRUE(void_arg_ != nullptr) << "The malloc hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, free_hook) {
RunTest("*.DISABLED_free_hook");
}
TEST_F(MallocHooksTest, DISABLED_free_hook) {
Init();
ASSERT_TRUE(__free_hook != nullptr);
__free_hook = test_free_hook;
void* ptr = malloc(1024);
ASSERT_TRUE(ptr != nullptr);
free(ptr);
write(0, ptr, 0);
EXPECT_TRUE(free_hook_called_) << "The free hook was not called.";
EXPECT_TRUE(void_arg_ != nullptr) << "The free hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, realloc_hook) {
RunTest("*.DISABLED_realloc_hook");
}
TEST_F(MallocHooksTest, DISABLED_realloc_hook) {
Init();
ASSERT_TRUE(__realloc_hook != nullptr);
__realloc_hook = test_realloc_hook;
void* ptr = malloc(1024);
ASSERT_TRUE(ptr != nullptr);
ptr = realloc(ptr, 2048);
free(ptr);
write(0, ptr, 0);
EXPECT_TRUE(realloc_hook_called_) << "The realloc hook was not called.";
EXPECT_TRUE(void_arg_ != nullptr) << "The realloc hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, memalign_hook) {
RunTest("*.DISABLED_memalign_hook");
}
TEST_F(MallocHooksTest, DISABLED_memalign_hook) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr = memalign(32, 1024);
ASSERT_TRUE(ptr != nullptr);
free(ptr);
write(0, ptr, 0);
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called.";
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, posix_memalign_hook) {
RunTest("*.DISABLED_posix_memalign_hook");
}
TEST_F(MallocHooksTest, DISABLED_posix_memalign_hook) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr;
ASSERT_EQ(0, posix_memalign(&ptr, 32, 1024));
ASSERT_TRUE(ptr != nullptr);
free(ptr);
write(0, ptr, 0);
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called when running posix_memalign.";
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, posix_memalign_hook_error) {
RunTest("*.DISABLED_posix_memalign_hook_error");
}
TEST_F(MallocHooksTest, DISABLED_posix_memalign_hook_error) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr;
ASSERT_EQ(EINVAL, posix_memalign(&ptr, 11, 1024));
write(0, ptr, 0);
EXPECT_FALSE(memalign_hook_called_)
<< "The memalign hook was called when running posix_memalign with an error.";
EXPECT_FALSE(void_arg_ != nullptr)
<< "The memalign hook was called with a nullptr with an error.";
}
TEST_F(MallocHooksTest, aligned_alloc_hook) {
RunTest("*.DISABLED_aligned_alloc_hook");
}
TEST_F(MallocHooksTest, DISABLED_aligned_alloc_hook) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr = aligned_alloc(32, 1024);
ASSERT_TRUE(ptr != nullptr);
free(ptr);
write(0, ptr, 0);
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called when running aligned_alloc.";
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, aligned_alloc_hook_error) {
RunTest("*.DISABLED_aligned_alloc_hook_error");
}
TEST_F(MallocHooksTest, DISABLED_aligned_alloc_hook_error) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr = aligned_alloc(11, 1024);
ASSERT_TRUE(ptr == nullptr);
EXPECT_EQ(EINVAL, errno);
write(0, ptr, 0);
EXPECT_FALSE(memalign_hook_called_)
<< "The memalign hook was called when running aligned_alloc with an error.";
EXPECT_FALSE(void_arg_ != nullptr)
<< "The memalign hook was called with a nullptr with an error.";
}
#if !defined(__LP64__)
TEST_F(MallocHooksTest, pvalloc_hook) {
RunTest("*.DISABLED_pvalloc_hook");
}
extern "C" void* pvalloc(size_t);
TEST_F(MallocHooksTest, DISABLED_pvalloc_hook) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr = pvalloc(1024);
ASSERT_TRUE(ptr != nullptr);
write(0, ptr, 0);
free(ptr);
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called for pvalloc.";
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
}
TEST_F(MallocHooksTest, valloc_hook) {
RunTest("*.DISABLED_valloc_hook");
}
extern "C" void* valloc(size_t);
TEST_F(MallocHooksTest, DISABLED_valloc_hook) {
Init();
ASSERT_TRUE(__memalign_hook != nullptr);
__memalign_hook = test_memalign_hook;
void* ptr = valloc(1024);
ASSERT_TRUE(ptr != nullptr);
write(0, ptr, 0);
free(ptr);
EXPECT_TRUE(memalign_hook_called_) << "The memalign hook was not called for valloc.";
EXPECT_TRUE(void_arg_ != nullptr) << "The memalign hook was called with a nullptr.";
}
#endif

View file

@ -140,15 +140,20 @@ static inline void WaitUntilThreadSleep(std::atomic<pid_t>& tid) {
}
}
static inline void AssertChildExited(int pid, int expected_exit_status) {
static inline void AssertChildExited(int pid, int expected_exit_status,
const std::string* error_msg = nullptr) {
int status;
ASSERT_EQ(pid, TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)));
std::string error;
if (error_msg == nullptr) {
error_msg = &error;
}
ASSERT_EQ(pid, TEMP_FAILURE_RETRY(waitpid(pid, &status, 0))) << *error_msg;
if (expected_exit_status >= 0) {
ASSERT_TRUE(WIFEXITED(status));
ASSERT_EQ(expected_exit_status, WEXITSTATUS(status));
ASSERT_TRUE(WIFEXITED(status)) << *error_msg;
ASSERT_EQ(expected_exit_status, WEXITSTATUS(status)) << *error_msg;
} else {
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_EQ(-expected_exit_status, WTERMSIG(status));
ASSERT_TRUE(WIFSIGNALED(status)) << *error_msg;
ASSERT_EQ(-expected_exit_status, WTERMSIG(status)) << *error_msg;
}
}
@ -215,7 +220,8 @@ class ExecTestHelper {
}
close(fds[0]);
AssertChildExited(pid, expected_exit_status);
std::string error_msg("Test output:\n" + output);
AssertChildExited(pid, expected_exit_status, &error_msg);
if (expected_output != nullptr) {
ASSERT_EQ(expected_output, output);
}