From 1e980b6bc8315d00a07312b25486531247abd98c Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Thu, 17 Jan 2013 18:36:06 -0800 Subject: Fix the duplication in the debugging code. We had two copies of the backtrace code, and two copies of the libcorkscrew /proc/pid/maps code. This patch gets us down to one. We also had hacks so we could log in the malloc debugging code. This patch pulls the non-allocating "printf" code out of the dynamic linker so everyone can share. This patch also makes the leak diagnostics easier to read, and makes it possible to paste them directly into the 'stack' tool (by using relative PCs). This patch also fixes the stdio standard stream leak that was causing a leak warning every time tf_daemon ran. Bug: 7291287 Change-Id: I66e4083ac2c5606c8d2737cb45c8ac8a32c7cfe8 --- libc/Android.mk | 7 +- libc/arch-arm/bionic/atexit_legacy.c | 5 +- libc/arch-mips/bionic/cacheflush.c | 6 +- libc/bionic/assert.cpp | 5 +- libc/bionic/debug_format.cpp | 634 ++++++++++++++++++++ libc/bionic/debug_mapinfo.cpp | 96 +++ libc/bionic/debug_mapinfo.h | 45 ++ libc/bionic/debug_stacktrace.cpp | 112 ++++ libc/bionic/debug_stacktrace.h | 45 ++ libc/bionic/logd_write.c | 4 +- libc/bionic/malloc_debug_check.cpp | 94 +-- libc/bionic/malloc_debug_check_mapinfo.cpp | 120 ---- libc/bionic/malloc_debug_check_mapinfo.h | 46 -- libc/bionic/malloc_debug_common.cpp | 18 +- libc/bionic/malloc_debug_common.h | 8 +- libc/bionic/malloc_debug_leak.cpp | 3 +- libc/bionic/malloc_debug_qemu.cpp | 24 +- libc/bionic/malloc_debug_stacktrace.cpp | 72 --- libc/bionic/pthread.c | 3 +- libc/bionic/pthread_debug.c | 903 ----------------------------- libc/bionic/pthread_debug.cpp | 727 +++++++++++++++++++++++ libc/bionic/ssp.cpp | 4 +- libc/bionic/stubs.cpp | 3 +- libc/private/debug_format.h | 55 ++ 24 files changed, 1793 insertions(+), 1246 deletions(-) create mode 100644 libc/bionic/debug_format.cpp create mode 100644 libc/bionic/debug_mapinfo.cpp create mode 100644 libc/bionic/debug_mapinfo.h create mode 100644 libc/bionic/debug_stacktrace.cpp create mode 100644 libc/bionic/debug_stacktrace.h delete mode 100644 libc/bionic/malloc_debug_check_mapinfo.cpp delete mode 100644 libc/bionic/malloc_debug_check_mapinfo.h delete mode 100644 libc/bionic/malloc_debug_stacktrace.cpp delete mode 100644 libc/bionic/pthread_debug.c create mode 100644 libc/bionic/pthread_debug.cpp create mode 100644 libc/private/debug_format.h (limited to 'libc') diff --git a/libc/Android.mk b/libc/Android.mk index c98d2ff..05373dc 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -272,6 +272,7 @@ libc_common_src_files := \ libc_bionic_src_files := \ bionic/assert.cpp \ + bionic/debug_format.cpp \ bionic/dirent.cpp \ bionic/eventfd.cpp \ bionic/__fgets_chk.cpp \ @@ -881,7 +882,7 @@ LOCAL_SRC_FILES := \ $(libc_static_common_src_files) \ bionic/dlmalloc.c \ bionic/malloc_debug_common.cpp \ - bionic/pthread_debug.c \ + bionic/pthread_debug.cpp \ bionic/libc_init_dynamic.c ifeq ($(TARGET_ARCH),arm) @@ -934,10 +935,10 @@ LOCAL_CFLAGS := \ LOCAL_C_INCLUDES := $(libc_common_c_includes) LOCAL_SRC_FILES := \ + bionic/debug_mapinfo.cpp \ + bionic/debug_stacktrace.cpp \ bionic/malloc_debug_leak.cpp \ bionic/malloc_debug_check.cpp \ - bionic/malloc_debug_check_mapinfo.cpp \ - bionic/malloc_debug_stacktrace.cpp LOCAL_MODULE:= libc_malloc_debug_leak LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk diff --git a/libc/arch-arm/bionic/atexit_legacy.c b/libc/arch-arm/bionic/atexit_legacy.c index 4abe839..6e299ac 100644 --- a/libc/arch-arm/bionic/atexit_legacy.c +++ b/libc/arch-arm/bionic/atexit_legacy.c @@ -50,10 +50,9 @@ atexit(void (*func)(void)) * calling library may have been dlclose()'d, causing the program to * crash. */ - static char const warning[] = - "WARNING: generic atexit() called from legacy shared library\n"; + static char const warning[] = "WARNING: generic atexit() called from legacy shared library\n"; - __libc_android_log_print(ANDROID_LOG_WARN, "libc", warning); + __libc_android_log_write(ANDROID_LOG_WARN, "libc", warning); fprintf(stderr, warning); return (__cxa_atexit((void (*)(void *))func, NULL, NULL)); diff --git a/libc/arch-mips/bionic/cacheflush.c b/libc/arch-mips/bionic/cacheflush.c index 05085b6..1911687 100644 --- a/libc/arch-mips/bionic/cacheflush.c +++ b/libc/arch-mips/bionic/cacheflush.c @@ -29,9 +29,9 @@ #include #ifdef DEBUG -#include -#define XLOG(...) \ - __libc_android_log_print(ANDROID_LOG_DEBUG,"libc-cacheflush",__VA_ARGS__) +#include +#include +#define XLOG(...) __libc_format_log(ANDROID_LOG_DEBUG,"libc-cacheflush",__VA_ARGS__) #endif /* diff --git a/libc/bionic/assert.cpp b/libc/bionic/assert.cpp index 7c0a860..e38c16d 100644 --- a/libc/bionic/assert.cpp +++ b/libc/bionic/assert.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include // We log to stderr for the benefit of "adb shell" users, and the log for the benefit @@ -39,7 +40,7 @@ void __assert(const char* file, int line, const char* failed_expression) { const char* fmt = "%s:%d: assertion \"%s\" failed\n"; - __libc_android_log_print(ANDROID_LOG_FATAL, "libc", fmt, file, line, failed_expression); + __libc_format_log(ANDROID_LOG_FATAL, "libc", fmt, file, line, failed_expression); fprintf(stderr, fmt, file, line, failed_expression); abort(); /* NOTREACHED */ @@ -47,7 +48,7 @@ void __assert(const char* file, int line, const char* failed_expression) { void __assert2(const char* file, int line, const char* function, const char* failed_expression) { const char* fmt = "%s:%d: %s: assertion \"%s\" failed\n"; - __libc_android_log_print(ANDROID_LOG_FATAL, "libc", fmt, file, line, function, failed_expression); + __libc_format_log(ANDROID_LOG_FATAL, "libc", fmt, file, line, function, failed_expression); fprintf(stderr, fmt, file, line, function, failed_expression); abort(); /* NOTREACHED */ diff --git a/libc/bionic/debug_format.cpp b/libc/bionic/debug_format.cpp new file mode 100644 index 0000000..e8d6a45 --- /dev/null +++ b/libc/bionic/debug_format.cpp @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2010 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. + */ + +// Temporarily disable _FORTIFY_SOURCE to get this code to +// compile under GCC 4.7 +#undef _FORTIFY_SOURCE + +#include "debug_format.h" + +#include +#include +#include +#include +#include +#include +#include + +/* define UNIT_TESTS to build this file as a single executable that runs + * the formatter's unit tests + */ +#define xxUNIT_TESTS + +/*** Generic output sink + ***/ + +struct Out { + void *opaque; + void (*send)(void *opaque, const char *data, int len); +}; + +static void out_send(Out *o, const char *data, size_t len) { + o->send(o->opaque, data, (int)len); +} + +static void +out_send_repeat(Out *o, char ch, int count) +{ + char pad[8]; + const int padSize = (int)sizeof(pad); + + memset(pad, ch, sizeof(pad)); + while (count > 0) { + int avail = count; + if (avail > padSize) { + avail = padSize; + } + o->send(o->opaque, pad, avail); + count -= avail; + } +} + +/* forward declaration */ +static void out_vformat(Out* o, const char* format, va_list args); + +/*** Bounded buffer output + ***/ + +struct BufOut { + Out out[1]; + char *buffer; + char *pos; + char *end; + int total; +}; + +static void buf_out_send(void *opaque, const char *data, int len) { + BufOut *bo = reinterpret_cast(opaque); + + if (len < 0) { + len = strlen(data); + } + + bo->total += len; + + while (len > 0) { + int avail = bo->end - bo->pos; + if (avail == 0) + break; + if (avail > len) + avail = len; + memcpy(bo->pos, data, avail); + bo->pos += avail; + bo->pos[0] = '\0'; + len -= avail; + } +} + +static Out* +buf_out_init(BufOut *bo, char *buffer, size_t size) +{ + if (size == 0) + return NULL; + + bo->out->opaque = bo; + bo->out->send = buf_out_send; + bo->buffer = buffer; + bo->end = buffer + size - 1; + bo->pos = bo->buffer; + bo->pos[0] = '\0'; + bo->total = 0; + + return bo->out; +} + +static int +buf_out_length(BufOut *bo) +{ + return bo->total; +} + +static int +vformat_buffer(char *buff, size_t buffsize, const char *format, va_list args) +{ + BufOut bo; + Out *out; + + out = buf_out_init(&bo, buff, buffsize); + if (out == NULL) + return 0; + + out_vformat(out, format, args); + + return buf_out_length(&bo); +} + +int __libc_format_buffer(char* buffer, size_t buffer_size, const char* format, ...) { + va_list args; + va_start(args, format); + int result = vformat_buffer(buffer, buffer_size, format, args); + va_end(args); + return result; +} + + +/*** File descriptor output + ***/ + +struct FdOut { + Out out[1]; + int fd; + int total; +}; + +static void +fd_out_send(void *opaque, const char *data, int len) +{ + FdOut *fdo = reinterpret_cast(opaque); + + if (len < 0) + len = strlen(data); + + while (len > 0) { + int ret = write(fdo->fd, data, len); + if (ret < 0) { + if (errno == EINTR) + continue; + break; + } + data += ret; + len -= ret; + fdo->total += ret; + } +} + +static Out* +fd_out_init(FdOut *fdo, int fd) +{ + fdo->out->opaque = fdo; + fdo->out->send = fd_out_send; + fdo->fd = fd; + fdo->total = 0; + + return fdo->out; +} + +static int +fd_out_length(FdOut *fdo) +{ + return fdo->total; +} + + +int __libc_format_fd(int fd, const char* format, ...) { + FdOut fdo; + Out* out = fd_out_init(&fdo, fd); + if (out == NULL) { + return 0; + } + + va_list args; + va_start(args, format); + out_vformat(out, format, args); + va_end(args); + + return fd_out_length(&fdo); +} + +/*** Log output + ***/ + +#include +#include +#include + +int __libc_format_log_va_list(int priority, const char* tag, const char* fmt, va_list args) { + char buf[1024]; + int result = vformat_buffer(buf, sizeof buf, fmt, args); + + static int log_fd = -1; + if (log_fd == -1) { + log_fd = open("/dev/log/main", O_WRONLY); + if (log_fd == -1) { + return result; + } + } + + struct iovec vec[3]; + vec[0].iov_base = (unsigned char *) &priority; + vec[0].iov_len = 1; + vec[1].iov_base = (void *) tag; + vec[1].iov_len = strlen(tag) + 1; + vec[2].iov_base = (void *) buf; + vec[2].iov_len = strlen(buf) + 1; + + TEMP_FAILURE_RETRY(writev(log_fd, vec, 3)); + + return result; +} + +int __libc_format_log(int priority, const char* tag, const char* format, ...) { + va_list args; + va_start(args, format); + int result = __libc_format_log_va_list(priority, tag, format, args); + va_end(args); + return result; +} + +/*** formatted output implementation + ***/ + +/* Parse a decimal string from 'format + *ppos', + * return the value, and writes the new position past + * the decimal string in '*ppos' on exit. + * + * NOTE: Does *not* handle a sign prefix. + */ +static unsigned +parse_decimal(const char *format, int *ppos) +{ + const char* p = format + *ppos; + unsigned result = 0; + + for (;;) { + int ch = *p; + unsigned d = (unsigned)(ch - '0'); + + if (d >= 10U) + break; + + result = result*10 + d; + p++; + } + *ppos = p - format; + return result; +} + +// Writes number 'value' in base 'base' into buffer 'buf' of size 'buf_size' bytes. +// Assumes that buf_size > 0. +static void format_number(char* buf, size_t buf_size, uint64_t value, int base, bool caps) { + char* p = buf; + char* end = buf + buf_size - 1; + + // Generate digit string in reverse order. + while (value) { + unsigned d = value % base; + value /= base; + if (p != end) { + char ch; + if (d < 10) { + ch = '0' + d; + } else { + ch = (caps ? 'A' : 'a') + (d - 10); + } + *p++ = ch; + } + } + + // Special case for 0. + if (p == buf) { + if (p != end) { + *p++ = '0'; + } + } + *p = '\0'; + + // Reverse digit string in-place. + size_t length = p - buf; + for (size_t i = 0, j = length - 1; i < j; ++i, --j) { + char ch = buf[i]; + buf[i] = buf[j]; + buf[j] = ch; + } +} + +/* Write an integer (octal or decimal) into a buffer, assumes buffsize > 2 */ +static void +format_integer(char *buffer, size_t buffsize, uint64_t value, int base, int isSigned) +{ + // TODO: this is incorrect for MIN_INT. + if (isSigned && (int64_t)value < 0) { + buffer[0] = '-'; + buffer += 1; + buffsize -= 1; + value = (uint64_t)(-(int64_t)value); + } + + format_number(buffer, buffsize, value, base, false); +} + +// Assumes buf_size > 2. +static void format_hex(char* buf, size_t buf_size, uint64_t value, bool caps) { + format_number(buf, buf_size, value, 16, caps); +} + + +/* Perform formatted output to an output target 'o' */ +static void +out_vformat(Out *o, const char *format, va_list args) +{ + int nn = 0; + + for (;;) { + int mm; + int padZero = 0; + int padLeft = 0; + char sign = '\0'; + int width = -1; + int prec = -1; + size_t bytelen = sizeof(int); + const char* str; + int slen; + char buffer[32]; /* temporary buffer used to format numbers */ + + char c; + + /* first, find all characters that are not 0 or '%' */ + /* then send them to the output directly */ + mm = nn; + do { + c = format[mm]; + if (c == '\0' || c == '%') + break; + mm++; + } while (1); + + if (mm > nn) { + out_send(o, format+nn, mm-nn); + nn = mm; + } + + /* is this it ? then exit */ + if (c == '\0') + break; + + /* nope, we are at a '%' modifier */ + nn++; // skip it + + /* parse flags */ + for (;;) { + c = format[nn++]; + if (c == '\0') { /* single trailing '%' ? */ + c = '%'; + out_send(o, &c, 1); + return; + } + else if (c == '0') { + padZero = 1; + continue; + } + else if (c == '-') { + padLeft = 1; + continue; + } + else if (c == ' ' || c == '+') { + sign = c; + continue; + } + break; + } + + /* parse field width */ + if ((c >= '0' && c <= '9')) { + nn --; + width = (int)parse_decimal(format, &nn); + c = format[nn++]; + } + + /* parse precision */ + if (c == '.') { + prec = (int)parse_decimal(format, &nn); + c = format[nn++]; + } + + /* length modifier */ + switch (c) { + case 'h': + bytelen = sizeof(short); + if (format[nn] == 'h') { + bytelen = sizeof(char); + nn += 1; + } + c = format[nn++]; + break; + case 'l': + bytelen = sizeof(long); + if (format[nn] == 'l') { + bytelen = sizeof(long long); + nn += 1; + } + c = format[nn++]; + break; + case 'z': + bytelen = sizeof(size_t); + c = format[nn++]; + break; + case 't': + bytelen = sizeof(ptrdiff_t); + c = format[nn++]; + break; + default: + ; + } + + /* conversion specifier */ + if (c == 's') { + /* string */ + str = va_arg(args, const char*); + } else if (c == 'c') { + /* character */ + /* NOTE: char is promoted to int when passed through the stack */ + buffer[0] = (char) va_arg(args, int); + buffer[1] = '\0'; + str = buffer; + } else if (c == 'p') { + uint64_t value = (uintptr_t) va_arg(args, void*); + buffer[0] = '0'; + buffer[1] = 'x'; + format_hex(buffer + 2, sizeof buffer-2, value, false); + str = buffer; + } else { + /* integers - first read value from stack */ + uint64_t value; + int isSigned = (c == 'd' || c == 'i' || c == 'o'); + + /* NOTE: int8_t and int16_t are promoted to int when passed + * through the stack + */ + switch (bytelen) { + case 1: value = (uint8_t) va_arg(args, int); break; + case 2: value = (uint16_t) va_arg(args, int); break; + case 4: value = va_arg(args, uint32_t); break; + case 8: value = va_arg(args, uint64_t); break; + default: return; /* should not happen */ + } + + /* sign extension, if needed */ + if (isSigned) { + int shift = 64 - 8*bytelen; + value = (uint64_t)(((int64_t)(value << shift)) >> shift); + } + + /* format the number properly into our buffer */ + switch (c) { + case 'i': case 'd': + format_integer(buffer, sizeof buffer, value, 10, isSigned); + break; + case 'o': + format_integer(buffer, sizeof buffer, value, 8, isSigned); + break; + case 'x': case 'X': + format_hex(buffer, sizeof buffer, value, (c == 'X')); + break; + default: + buffer[0] = '\0'; + } + /* then point to it */ + str = buffer; + } + + /* if we are here, 'str' points to the content that must be + * outputted. handle padding and alignment now */ + + slen = strlen(str); + + if (sign != '\0' || prec != -1) { + __assert(__FILE__, __LINE__, "sign/precision unsupported"); + } + + if (slen < width && !padLeft) { + char padChar = padZero ? '0' : ' '; + out_send_repeat(o, padChar, width - slen); + } + + out_send(o, str, slen); + + if (slen < width && padLeft) { + char padChar = padZero ? '0' : ' '; + out_send_repeat(o, padChar, width - slen); + } + } +} + + +#ifdef UNIT_TESTS + +#include + +static int gFails = 0; + +#define MARGIN 40 + +#define UTEST_CHECK(condition,message) \ + printf("Checking %-*s: ", MARGIN, message); fflush(stdout); \ + if (!(condition)) { \ + printf("KO\n"); \ + gFails += 1; \ + } else { \ + printf("ok\n"); \ + } + +static void +utest_BufOut(void) +{ + char buffer[16]; + BufOut bo[1]; + Out* out; + int ret; + + buffer[0] = '1'; + out = buf_out_init(bo, buffer, sizeof buffer); + UTEST_CHECK(buffer[0] == '\0', "buf_out_init clears initial byte"); + out_send(out, "abc", 3); + UTEST_CHECK(!memcmp(buffer, "abc", 4), "out_send() works with BufOut"); + out_send_repeat(out, 'X', 4); + UTEST_CHECK(!memcmp(buffer, "abcXXXX", 8), "out_send_repeat() works with BufOut"); + buffer[sizeof buffer-1] = 'x'; + out_send_repeat(out, 'Y', 2*sizeof(buffer)); + UTEST_CHECK(buffer[sizeof buffer-1] == '\0', "overflows always zero-terminates"); + + out = buf_out_init(bo, buffer, sizeof buffer); + out_send_repeat(out, 'X', 2*sizeof(buffer)); + ret = buf_out_length(bo); + UTEST_CHECK(ret == 2*sizeof(buffer), "correct size returned on overflow"); +} + +static void +utest_expect(const char* result, const char* format, ...) +{ + va_list args; + BufOut bo[1]; + char buffer[256]; + Out* out = buf_out_init(bo, buffer, sizeof buffer); + + printf("Checking %-*s: ", MARGIN, format); fflush(stdout); + va_start(args, format); + out_vformat(out, format, args); + va_end(args); + + if (strcmp(result, buffer)) { + printf("KO. got '%s' expecting '%s'\n", buffer, result); + gFails += 1; + } else { + printf("ok. got '%s'\n", result); + } +} + +int main(void) +{ + utest_BufOut(); + utest_expect("", ""); + utest_expect("a", "a"); + utest_expect("01234", "01234", ""); + utest_expect("01234", "%s", "01234"); + utest_expect("aabbcc", "aa%scc", "bb"); + utest_expect("a", "%c", 'a'); + utest_expect("1234", "%d", 1234); + utest_expect("-8123", "%d", -8123); + utest_expect("16", "%hd", 0x7fff0010); + utest_expect("16", "%hhd", 0x7fffff10); + utest_expect("68719476736", "%lld", 0x1000000000LL); + utest_expect("70000", "%ld", 70000); + utest_expect("0xb0001234", "%p", (void*)0xb0001234); + utest_expect("12ab", "%x", 0x12ab); + utest_expect("12AB", "%X", 0x12ab); + utest_expect("00123456", "%08x", 0x123456); + utest_expect("01234", "0%d", 1234); + utest_expect(" 1234", "%5d", 1234); + utest_expect("01234", "%05d", 1234); + utest_expect(" 1234", "%8d", 1234); + utest_expect("1234 ", "%-8d", 1234); + utest_expect("abcdef ", "%-11s", "abcdef"); + utest_expect("something:1234", "%s:%d", "something", 1234); + utest_expect("005:5:05", "%03d:%d:%02d", 5, 5, 5); + utest_expect("5,0x0", "%d,%p", 5, NULL); + utest_expect("68719476736,6,7,8", "%lld,%d,%d,%d", 0x1000000000LL, 6, 7, 8); + return gFails != 0; +} + +#endif /* UNIT_TESTS */ diff --git a/libc/bionic/debug_mapinfo.cpp b/libc/bionic/debug_mapinfo.cpp new file mode 100644 index 0000000..174cc28 --- /dev/null +++ b/libc/bionic/debug_mapinfo.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 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 +#include +#include + +#include "dlmalloc.h" +#include "debug_mapinfo.h" + +// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so +// 012345678901234567890123456789012345678901234567890123456789 +// 0 1 2 3 4 5 + +static mapinfo_t* parse_maps_line(char* line) { + int len = strlen(line); + + if (len < 1) return 0; + line[--len] = 0; + + if (len < 50) return 0; + if (line[20] != 'x') return 0; + + mapinfo_t* mi = static_cast(dlmalloc(sizeof(mapinfo_t) + (len - 47))); + if (mi == 0) return 0; + + mi->start = strtoul(line, 0, 16); + mi->end = strtoul(line + 9, 0, 16); + mi->next = 0; + strcpy(mi->name, line + 49); + + return mi; +} + +__LIBC_HIDDEN__ mapinfo_t* mapinfo_create(int pid) { + struct mapinfo_t* milist = NULL; + char data[1024]; // Used to read lines as well as to construct the filename. + snprintf(data, sizeof(data), "/proc/%d/maps", pid); + FILE* fp = fopen(data, "r"); + if (fp != NULL) { + while (fgets(data, sizeof(data), fp) != NULL) { + mapinfo_t* mi = parse_maps_line(data); + if (mi) { + mi->next = milist; + milist = mi; + } + } + fclose(fp); + } + return milist; +} + +__LIBC_HIDDEN__ void mapinfo_destroy(mapinfo_t* mi) { + while (mi) { + mapinfo_t* del = mi; + mi = mi->next; + dlfree(del); + } +} + +// Find the containing map info for the PC. +__LIBC_HIDDEN__ const mapinfo_t* mapinfo_find(mapinfo_t* mi, unsigned pc, unsigned* rel_pc) { + *rel_pc = pc; + for (; mi != NULL; mi = mi->next) { + if ((pc >= mi->start) && (pc < mi->end)) { + *rel_pc -= mi->start; + return mi; + } + } + return NULL; +} diff --git a/libc/bionic/debug_mapinfo.h b/libc/bionic/debug_mapinfo.h new file mode 100644 index 0000000..6df55c5 --- /dev/null +++ b/libc/bionic/debug_mapinfo.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 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. + */ + +#ifndef DEBUG_MAPINFO_H +#define DEBUG_MAPINFO_H + +#include + +struct mapinfo_t { + struct mapinfo_t* next; + unsigned start; + unsigned end; + char name[]; +}; + +__LIBC_HIDDEN__ mapinfo_t* mapinfo_create(int pid); +__LIBC_HIDDEN__ void mapinfo_destroy(mapinfo_t* mi); +__LIBC_HIDDEN__ const mapinfo_t* mapinfo_find(mapinfo_t* mi, unsigned pc, unsigned* rel_pc); + +#endif /* DEBUG_MAPINFO_H */ diff --git a/libc/bionic/debug_stacktrace.cpp b/libc/bionic/debug_stacktrace.cpp new file mode 100644 index 0000000..4b080a4 --- /dev/null +++ b/libc/bionic/debug_stacktrace.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 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 "debug_stacktrace.h" + +#include +#include +#include +#include + +#include "debug_format.h" +#include "debug_mapinfo.h" +#include "logd.h" + +/* depends how the system includes define this */ +#ifdef HAVE_UNWIND_CONTEXT_STRUCT +typedef struct _Unwind_Context __unwind_context; +#else +typedef _Unwind_Context __unwind_context; +#endif + +static _Unwind_Reason_Code trace_function(__unwind_context* context, void* arg) { + stack_crawl_state_t* state = static_cast(arg); + if (state->count) { + intptr_t ip = (intptr_t)_Unwind_GetIP(context); + if (ip) { + state->addrs[0] = ip; + state->addrs++; + state->count--; + return _URC_NO_REASON; + } + } + // If we run out of space to record the address or 0 has been seen, stop + // unwinding the stack. + return _URC_END_OF_STACK; +} + +__LIBC_HIDDEN__ int get_backtrace(intptr_t* addrs, size_t max_entries) { + stack_crawl_state_t state; + state.count = max_entries; + state.addrs = addrs; + _Unwind_Backtrace(trace_function, &state); + return max_entries - state.count; +} + +__LIBC_HIDDEN__ void log_backtrace(mapinfo_t* map_info, intptr_t* addrs, size_t c) { + intptr_t self_bt[16]; + if (addrs == NULL) { + c = get_backtrace(self_bt, 16); + addrs = self_bt; + } + + __libc_format_log(ANDROID_LOG_ERROR, "libc", + "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); + + int index = 0; + for (size_t i = 0 ; i < c; ++i) { + void* offset = 0; + const char* symbol = NULL; + + Dl_info info; + if (dladdr((void*) addrs[i], &info) != 0) { + offset = info.dli_saddr; + symbol = info.dli_sname; + } + + // This test is a bit sketchy, but it allows us to skip the + // stack trace entries due to this debugging code. it works + // because those don't have a symbol (they're not exported). + if (symbol != NULL || index > 0) { + unsigned int rel_pc; + const mapinfo_t* mi = mapinfo_find(map_info, addrs[i], &rel_pc); + const char* soname = mi ? mi->name : info.dli_fname; + if (soname == NULL) { + soname = "unknown"; + } + if (symbol) { + __libc_format_log(ANDROID_LOG_ERROR, "libc", " #%02d pc %08x %s (%s+0x%x)", + index, rel_pc, soname, symbol, addrs[i] - (intptr_t)offset); + } else { + __libc_format_log(ANDROID_LOG_ERROR, "libc", " #%02d pc %08x %s", + index, rel_pc, soname); + } + ++index; + } + } +} diff --git a/libc/bionic/debug_stacktrace.h b/libc/bionic/debug_stacktrace.h new file mode 100644 index 0000000..3c66ea6 --- /dev/null +++ b/libc/bionic/debug_stacktrace.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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. + */ + +#ifndef DEBUG_STACKTRACE_H +#define DEBUG_STACKTRACE_H + +#include +#include + +struct stack_crawl_state_t { + size_t count; + intptr_t* addrs; +}; + +struct mapinfo_t; + +__LIBC_HIDDEN__ int get_backtrace(intptr_t* stack_frames, size_t max_entries); +__LIBC_HIDDEN__ void log_backtrace(mapinfo_t* map_info, intptr_t* stack_frames, size_t frame_count); + +#endif /* DEBUG_STACKTRACE_H */ diff --git a/libc/bionic/logd_write.c b/libc/bionic/logd_write.c index 71a6f8e..11c0e68 100644 --- a/libc/bionic/logd_write.c +++ b/libc/bionic/logd_write.c @@ -250,9 +250,7 @@ void __libc_android_log_event_uid(int32_t tag) __LIBC_HIDDEN__ void __fortify_chk_fail(const char *msg, uint32_t tag) { - __libc_android_log_print(ANDROID_LOG_FATAL, "libc", - "FORTIFY_SOURCE: %s. Calling abort().\n", - msg); + __libc_format_log(ANDROID_LOG_FATAL, "libc", "FORTIFY_SOURCE: %s. Calling abort().\n", msg); if (tag != 0) { __libc_android_log_event_uid(tag); } diff --git a/libc/bionic/malloc_debug_check.cpp b/libc/bionic/malloc_debug_check.cpp index 5ad3486..60ee0cc 100644 --- a/libc/bionic/malloc_debug_check.cpp +++ b/libc/bionic/malloc_debug_check.cpp @@ -45,18 +45,19 @@ #include #include +#include "debug_mapinfo.h" +#include "debug_stacktrace.h" #include "dlmalloc.h" #include "logd.h" -#include "malloc_debug_check_mapinfo.h" #include "malloc_debug_common.h" #include "ScopedPthreadMutexLocker.h" -static mapinfo *milist; +static mapinfo_t* gMapInfo; /* libc.debug.malloc.backlog */ extern unsigned int malloc_double_free_backlog; -#define MAX_BACKTRACE_DEPTH 15 +#define MAX_BACKTRACE_DEPTH 16 #define ALLOCATION_TAG 0x1ee7d00d #define BACKLOG_TAG 0xbabecafe #define FREE_POISON 0xa5 @@ -67,20 +68,10 @@ extern unsigned int malloc_double_free_backlog; #define REAR_GUARD_LEN (1<<5) static void log_message(const char* format, ...) { - extern const MallocDebug __libc_malloc_default_dispatch; - extern const MallocDebug* __libc_malloc_dispatch; - extern pthread_mutex_t gAllocationsMutex; - - va_list args; - { - ScopedPthreadMutexLocker locker(&gAllocationsMutex); - const MallocDebug* current_dispatch = __libc_malloc_dispatch; - __libc_malloc_dispatch = &__libc_malloc_default_dispatch; - va_start(args, format); - __libc_android_log_vprint(ANDROID_LOG_ERROR, "libc", format, args); - va_end(args); - __libc_malloc_dispatch = current_dispatch; - } + va_list args; + va_start(args, format); + __libc_format_log_va_list(ANDROID_LOG_ERROR, "libc", format, args); + va_end(args); } struct hdr_t { @@ -121,28 +112,6 @@ static hdr_t *backlog_tail; static hdr_t *backlog_head; static pthread_mutex_t backlog_lock = PTHREAD_MUTEX_INITIALIZER; -extern __LIBC_HIDDEN__ int get_backtrace(intptr_t* addrs, size_t max_entries); - -static void print_backtrace(const intptr_t *bt, unsigned int depth) { - const mapinfo *mi; - unsigned int cnt; - unsigned int rel_pc; - intptr_t self_bt[MAX_BACKTRACE_DEPTH]; - - if (!bt) { - depth = get_backtrace(self_bt, MAX_BACKTRACE_DEPTH); - bt = self_bt; - } - - log_message("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); - for (cnt = 0; cnt < depth && cnt < MAX_BACKTRACE_DEPTH; cnt++) { - mi = pc_to_mapinfo(milist, bt[cnt], &rel_pc); - log_message("\t#%02d pc %08x %s\n", cnt, - mi ? (intptr_t)rel_pc : bt[cnt], - mi ? mi->name : "(unknown)"); - } -} - static inline void init_front_guard(hdr_t *hdr) { memset(hdr->front_guard, FRONT_GUARD, FRONT_GUARD_LEN); } @@ -292,11 +261,11 @@ static inline int check_allocation_locked(hdr_t *hdr, int *safe) { if (!valid && *safe) { log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->bt, hdr->bt_depth); + log_backtrace(gMapInfo, hdr->bt, hdr->bt_depth); if (hdr->tag == BACKLOG_TAG) { log_message("+++ ALLOCATION %p SIZE %d FREED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->freed_bt, hdr->freed_bt_depth); + log_backtrace(gMapInfo, hdr->freed_bt, hdr->freed_bt_depth); } } @@ -381,18 +350,18 @@ extern "C" void chk_free(void *ptr) { user(hdr), hdr->size); log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->bt, hdr->bt_depth); + log_backtrace(gMapInfo, hdr->bt, hdr->bt_depth); /* hdr->freed_bt_depth should be nonzero here */ log_message("+++ ALLOCATION %p SIZE %d FIRST FREED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->freed_bt, hdr->freed_bt_depth); + log_backtrace(gMapInfo, hdr->freed_bt, hdr->freed_bt_depth); log_message("+++ ALLOCATION %p SIZE %d NOW BEING FREED HERE:\n", user(hdr), hdr->size); - print_backtrace(bt, depth); + log_backtrace(gMapInfo, bt, depth); } else { log_message("+++ ALLOCATION %p IS CORRUPTED OR NOT ALLOCATED VIA TRACKER!\n", user(hdr)); - print_backtrace(bt, depth); + log_backtrace(gMapInfo, bt, depth); /* Leak here so that we do not crash */ //dlfree(user(hdr)); } @@ -428,14 +397,14 @@ extern "C" void *chk_realloc(void *ptr, size_t size) { user(hdr), size, hdr->size); log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->bt, hdr->bt_depth); + log_backtrace(gMapInfo, hdr->bt, hdr->bt_depth); /* hdr->freed_bt_depth should be nonzero here */ log_message("+++ ALLOCATION %p SIZE %d FIRST FREED HERE:\n", user(hdr), hdr->size); - print_backtrace(hdr->freed_bt, hdr->freed_bt_depth); + log_backtrace(gMapInfo, hdr->freed_bt, hdr->freed_bt_depth); log_message("+++ ALLOCATION %p SIZE %d NOW BEING REALLOCATED HERE:\n", user(hdr), hdr->size); - print_backtrace(bt, depth); + log_backtrace(gMapInfo, bt, depth); /* We take the memory out of the backlog and fall through so the * reallocation below succeeds. Since we didn't really free it, we @@ -445,7 +414,7 @@ extern "C" void *chk_realloc(void *ptr, size_t size) { } else { log_message("+++ REALLOCATION %p SIZE %d IS CORRUPTED OR NOT ALLOCATED VIA TRACKER!\n", user(hdr), size); - print_backtrace(bt, depth); + log_backtrace(gMapInfo, bt, depth); // just get a whole new allocation and leak the old one return dlrealloc(0, size); // return dlrealloc(user(hdr), size); // assuming it was allocated externally @@ -467,8 +436,7 @@ extern "C" void *chk_calloc(int nmemb, size_t size) { size_t total_size = nmemb * size; hdr_t* hdr = static_cast(dlcalloc(1, sizeof(hdr_t) + total_size + sizeof(ftr_t))); if (hdr) { - hdr->bt_depth = get_backtrace( - hdr->bt, MAX_BACKTRACE_DEPTH); + hdr->bt_depth = get_backtrace(hdr->bt, MAX_BACKTRACE_DEPTH); add(hdr, total_size); return user(hdr); } @@ -476,21 +444,20 @@ extern "C" void *chk_calloc(int nmemb, size_t size) { } static void heaptracker_free_leaked_memory() { + size_t total = num; if (num) { - log_message("+++ THERE ARE %d LEAKED ALLOCATIONS\n", num); + log_message("+++ Leaked allocations: %d\n", num); } hdr_t *del = NULL; while (head) { int safe; del = head; - log_message("+++ DELETING %d BYTES OF LEAKED MEMORY AT %p (%d REMAINING)\n", - del->size, user(del), num); + log_message("+++ Leaked block of size %d at %p (leak %d of %d)\n", + del->size, user(del), 1 + total - num, total); if (del_leak(del, &safe)) { /* safe == 1, because the allocation is valid */ - log_message("+++ ALLOCATION %p SIZE %d ALLOCATED HERE:\n", - user(del), del->size); - print_backtrace(del->bt, del->bt_depth); + log_backtrace(gMapInfo, del->bt, del->bt_depth); } dlfree(del); } @@ -507,13 +474,14 @@ static void heaptracker_free_leaked_memory() { * See comments on MallocDebugInit in malloc_debug_common.h */ extern "C" int malloc_debug_initialize() { - if (!malloc_double_free_backlog) - malloc_double_free_backlog = BACKLOG_DEFAULT_LEN; - milist = init_mapinfo(getpid()); - return 0; + if (!malloc_double_free_backlog) { + malloc_double_free_backlog = BACKLOG_DEFAULT_LEN; + } + gMapInfo = mapinfo_create(getpid()); + return 0; } extern "C" void malloc_debug_finalize() { - heaptracker_free_leaked_memory(); - deinit_mapinfo(milist); + heaptracker_free_leaked_memory(); + mapinfo_destroy(gMapInfo); } diff --git a/libc/bionic/malloc_debug_check_mapinfo.cpp b/libc/bionic/malloc_debug_check_mapinfo.cpp deleted file mode 100644 index 8cc2c99..0000000 --- a/libc/bionic/malloc_debug_check_mapinfo.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2012 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 -#include -#include - -#include "dlmalloc.h" -#include "malloc_debug_check_mapinfo.h" - -// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so -// 012345678901234567890123456789012345678901234567890123456789 -// 0 1 2 3 4 5 - -static mapinfo* parse_maps_line(char* line) { - int len = strlen(line); - - if (len < 1) return 0; - line[--len] = 0; - - if (len < 50) return 0; - if (line[20] != 'x') return 0; - - mapinfo* mi = static_cast(dlmalloc(sizeof(mapinfo) + (len - 47))); - if (mi == 0) return 0; - - mi->start = strtoul(line, 0, 16); - mi->end = strtoul(line + 9, 0, 16); - /* To be filled in parse_elf_info if the mapped section starts with - * elf_header - */ - mi->next = 0; - strcpy(mi->name, line + 49); - - return mi; -} - -__LIBC_HIDDEN__ -mapinfo *init_mapinfo(int pid) { - struct mapinfo *milist = NULL; - char data[1024]; // Used to read lines as well as to construct the filename. - snprintf(data, sizeof(data), "/proc/%d/maps", pid); - FILE *fp = fopen(data, "r"); - if (fp) { - while (fgets(data, sizeof(data), fp)) { - mapinfo *mi = parse_maps_line(data); - if (mi) { - mi->next = milist; - milist = mi; - } - } - fclose(fp); - } - - return milist; -} - -__LIBC_HIDDEN__ -void deinit_mapinfo(mapinfo *mi) { - mapinfo *del; - while (mi) { - del = mi; - mi = mi->next; - dlfree(del); - } -} - -/* Map a pc address to the name of the containing ELF file */ -__LIBC_HIDDEN__ -const char *map_to_name(mapinfo *mi, unsigned pc, const char* def) { - while (mi) { - if ((pc >= mi->start) && (pc < mi->end)) { - return mi->name; - } - mi = mi->next; - } - return def; -} - -/* Find the containing map info for the pc */ -__LIBC_HIDDEN__ -const mapinfo *pc_to_mapinfo(mapinfo *mi, unsigned pc, unsigned *rel_pc) { - *rel_pc = pc; - while (mi) { - if ((pc >= mi->start) && (pc < mi->end)) { - // Only calculate the relative offset for shared libraries - if (strstr(mi->name, ".so")) { - *rel_pc -= mi->start; - } - return mi; - } - mi = mi->next; - } - return NULL; -} diff --git a/libc/bionic/malloc_debug_check_mapinfo.h b/libc/bionic/malloc_debug_check_mapinfo.h deleted file mode 100644 index e19f71e..0000000 --- a/libc/bionic/malloc_debug_check_mapinfo.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#ifndef MALLOC_DEBUG_CHECK_MAPINFO_H -#define MALLOC_DEBUG_CHECK_MAPINFO_H - -#include - -struct mapinfo { - struct mapinfo* next; - unsigned start; - unsigned end; - char name[]; -}; - -__LIBC_HIDDEN__ mapinfo *init_mapinfo(int pid); -__LIBC_HIDDEN__ void deinit_mapinfo(mapinfo *mi); -__LIBC_HIDDEN__ const char *map_to_name(mapinfo *mi, unsigned pc, const char* def); -__LIBC_HIDDEN__ const mapinfo *pc_to_mapinfo(mapinfo *mi, unsigned pc, unsigned *rel_pc); - -#endif /*MALLOC_DEBUG_CHECK_MAPINFO_H*/ diff --git a/libc/bionic/malloc_debug_common.cpp b/libc/bionic/malloc_debug_common.cpp index 624a40e..bba0472 100644 --- a/libc/bionic/malloc_debug_common.cpp +++ b/libc/bionic/malloc_debug_common.cpp @@ -294,8 +294,8 @@ static void* libc_malloc_impl_handle = NULL; unsigned int malloc_double_free_backlog; static void InitMalloc(MallocDebug* table, int debug_level, const char* prefix) { - __libc_android_log_print(ANDROID_LOG_INFO, "libc", "%s: using libc.debug.malloc %d (%s)\n", - __progname, debug_level, prefix); + __libc_format_log(ANDROID_LOG_INFO, "libc", "%s: using libc.debug.malloc %d (%s)\n", + __progname, debug_level, prefix); char symbol[128]; @@ -429,7 +429,7 @@ static void malloc_init_impl() { dlclose(libc_malloc_impl_handle); return; } - if (malloc_debug_initialize()) { + if (malloc_debug_initialize() == -1) { dlclose(libc_malloc_impl_handle); return; } @@ -487,11 +487,19 @@ static void malloc_init_impl() { } static void malloc_fini_impl() { - if (libc_malloc_impl_handle) { + // Our BSD stdio implementation doesn't close the standard streams, it only flushes them. + // And it doesn't do that until its atexit handler (_cleanup) is run, and we run first! + // It's great that other unclosed FILE*s show up as malloc leaks, but we need to manually + // clean up the standard streams ourselves. + fclose(stdin); + fclose(stdout); + fclose(stderr); + + if (libc_malloc_impl_handle != NULL) { MallocDebugFini malloc_debug_finalize = reinterpret_cast(dlsym(libc_malloc_impl_handle, "malloc_debug_finalize")); - if (malloc_debug_finalize) { + if (malloc_debug_finalize != NULL) { malloc_debug_finalize(); } } diff --git a/libc/bionic/malloc_debug_common.h b/libc/bionic/malloc_debug_common.h index 78ad5e5..3d12f87 100644 --- a/libc/bionic/malloc_debug_common.h +++ b/libc/bionic/malloc_debug_common.h @@ -35,6 +35,8 @@ #include +#include + #define HASHTABLE_SIZE 1543 #define BACKTRACE_SIZE 32 /* flag definitions, currently sharing storage with "size" */ @@ -97,10 +99,10 @@ typedef void (*MallocDebugFini)(); // ============================================================================= #define debug_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_DEBUG, "malloc_leak_check", (format), ##__VA_ARGS__ ) + __libc_format_log(ANDROID_LOG_DEBUG, "malloc_leak_check", (format), ##__VA_ARGS__ ) #define error_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_ERROR, "malloc_leak_check", (format), ##__VA_ARGS__ ) + __libc_format_log(ANDROID_LOG_ERROR, "malloc_leak_check", (format), ##__VA_ARGS__ ) #define info_log(format, ...) \ - __libc_android_log_print(ANDROID_LOG_INFO, "malloc_leak_check", (format), ##__VA_ARGS__ ) + __libc_format_log(ANDROID_LOG_INFO, "malloc_leak_check", (format), ##__VA_ARGS__ ) #endif // MALLOC_DEBUG_COMMON_H diff --git a/libc/bionic/malloc_debug_leak.cpp b/libc/bionic/malloc_debug_leak.cpp index 090a981..68b6ae2 100644 --- a/libc/bionic/malloc_debug_leak.cpp +++ b/libc/bionic/malloc_debug_leak.cpp @@ -45,6 +45,7 @@ #include #include +#include "debug_stacktrace.h" #include "dlmalloc.h" #include "logd.h" #include "malloc_debug_common.h" @@ -255,8 +256,6 @@ extern "C" void* fill_memalign(size_t alignment, size_t bytes) { static void* MEMALIGN_GUARD = reinterpret_cast(0xA1A41520); -extern __LIBC_HIDDEN__ int get_backtrace(intptr_t* addrs, size_t max_entries); - extern "C" void* leak_malloc(size_t bytes) { // allocate enough space infront of the allocation to store the pointer for // the alloc structure. This will making free'ing the structer really fast! diff --git a/libc/bionic/malloc_debug_qemu.cpp b/libc/bionic/malloc_debug_qemu.cpp index e586b1b..812a451 100644 --- a/libc/bionic/malloc_debug_qemu.cpp +++ b/libc/bionic/malloc_debug_qemu.cpp @@ -257,8 +257,8 @@ static void dump_malloc_descriptor(char* str, INFO_TRACING_ENABLED) /* Prints a string to the emulator's stdout. - * In early stages of system loading, logging mesages via - * __libc_android_log_print API is not available, because ADB API has not been + * In early stages of system loading, logging messages to logcat + * is not available, because ADB API has not been * hooked up yet. So, in order to see such messages we need to print them to * the emulator's stdout. * Parameters passed to this macro are the same as parameters for printf @@ -289,8 +289,7 @@ static void dump_malloc_descriptor(char* str, */ #define qemu_debug_log(format, ...) \ do { \ - __libc_android_log_print(ANDROID_LOG_DEBUG, "memcheck", \ - (format), ##__VA_ARGS__); \ + __libc_format_log(ANDROID_LOG_DEBUG, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & DEBUG_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_DEBUG, (format), ##__VA_ARGS__); \ } \ @@ -298,8 +297,7 @@ static void dump_malloc_descriptor(char* str, #define qemu_error_log(format, ...) \ do { \ - __libc_android_log_print(ANDROID_LOG_ERROR, "memcheck", \ - (format), ##__VA_ARGS__); \ + __libc_format_log(ANDROID_LOG_ERROR, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & ERROR_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_ERROR, (format), ##__VA_ARGS__); \ } \ @@ -307,8 +305,7 @@ static void dump_malloc_descriptor(char* str, #define qemu_info_log(format, ...) \ do { \ - __libc_android_log_print(ANDROID_LOG_INFO, "memcheck", \ - (format), ##__VA_ARGS__); \ + __libc_format_log(ANDROID_LOG_INFO, "memcheck", (format), ##__VA_ARGS__); \ if (tracing_flags & INFO_TRACING_ENABLED) { \ qemu_log(ANDROID_LOG_INFO, (format), ##__VA_ARGS__); \ } \ @@ -318,20 +315,19 @@ static void dump_malloc_descriptor(char* str, * Param: * type - Message type: debug, error, or info * desc - MallocDesc instance to dump. - * frmt + rest - Formats message preceding dumped descriptor. + * fmt + rest - Formats message preceding dumped descriptor. */ -#define log_mdesc(type, desc, frmt, ...) \ +#define log_mdesc(type, desc, fmt, ...) \ do { \ if (tracing_enabled(type)) { \ char log_str[4096]; \ - size_t str_len; \ - snprintf(log_str, sizeof(log_str), frmt, ##__VA_ARGS__); \ + __libc_format_buffer(log_str, sizeof(log_str), fmt, ##__VA_ARGS__); \ log_str[sizeof(log_str) - 1] = '\0'; \ - str_len = strlen(log_str); \ + size_t str_len = strlen(log_str); \ dump_malloc_descriptor(log_str + str_len, \ sizeof(log_str) - str_len, \ (desc)); \ - type##_log(log_str); \ + type##_log("%s", log_str); \ } \ } while (0) diff --git a/libc/bionic/malloc_debug_stacktrace.cpp b/libc/bionic/malloc_debug_stacktrace.cpp deleted file mode 100644 index 32b8ac0..0000000 --- a/libc/bionic/malloc_debug_stacktrace.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2012 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 -#include - -// ============================================================================= -// stack trace functions -// ============================================================================= - -struct stack_crawl_state_t { - size_t count; - intptr_t* addrs; -}; - - -/* depends how the system includes define this */ -#ifdef HAVE_UNWIND_CONTEXT_STRUCT -typedef struct _Unwind_Context __unwind_context; -#else -typedef _Unwind_Context __unwind_context; -#endif - -static _Unwind_Reason_Code trace_function(__unwind_context* context, void* arg) { - stack_crawl_state_t* state = static_cast(arg); - if (state->count) { - intptr_t ip = (intptr_t)_Unwind_GetIP(context); - if (ip) { - state->addrs[0] = ip; - state->addrs++; - state->count--; - return _URC_NO_REASON; - } - } - /* - * If we run out of space to record the address or 0 has been seen, stop - * unwinding the stack. - */ - return _URC_END_OF_STACK; -} - -__LIBC_HIDDEN__ int get_backtrace(intptr_t* addrs, size_t max_entries) { - stack_crawl_state_t state; - state.count = max_entries; - state.addrs = addrs; - _Unwind_Backtrace(trace_function, &state); - return max_entries - state.count; -} diff --git a/libc/bionic/pthread.c b/libc/bionic/pthread.c index f294723..f2a7ebe 100644 --- a/libc/bionic/pthread.c +++ b/libc/bionic/pthread.c @@ -50,6 +50,7 @@ #include "bionic_pthread.h" #include "bionic_ssp.h" #include "bionic_tls.h" +#include "debug_format.h" #include "pthread_internal.h" #include "thread_private.h" @@ -229,7 +230,7 @@ int _init_thread(pthread_internal_t* thread, pid_t kernel_id, const pthread_attr // For backwards compatibility reasons, we just warn about failures here. // error = errno; const char* msg = "pthread_create sched_setscheduler call failed: %s\n"; - __libc_android_log_print(ANDROID_LOG_WARN, "libc", msg, strerror(errno)); + __libc_format_log(ANDROID_LOG_WARN, "libc", msg, strerror(errno)); } } diff --git a/libc/bionic/pthread_debug.c b/libc/bionic/pthread_debug.c deleted file mode 100644 index 7ee208c..0000000 --- a/libc/bionic/pthread_debug.c +++ /dev/null @@ -1,903 +0,0 @@ -/* - * Copyright (C) 2011 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 -#include -#include -#include - -#if HAVE_DLADDR -#include -#endif -#include -#include -#include -#include -#include -#include -#include - -#include "logd.h" -#include "bionic_tls.h" - -/* - * =========================================================================== - * Deadlock prediction - * =========================================================================== - */ -/* -The idea is to predict the possibility of deadlock by recording the order -in which locks are acquired. If we see an attempt to acquire a lock -out of order, we can identify the locks and offending code. - -To make this work, we need to keep track of the locks held by each thread, -and create history trees for each lock. When a thread tries to acquire -a new lock, we walk through the "history children" of the lock, looking -for a match with locks the thread already holds. If we find a match, -it means the thread has made a request that could result in a deadlock. - -To support recursive locks, we always allow re-locking a currently-held -lock, and maintain a recursion depth count. - -An ASCII-art example, where letters represent locks: - - A - /|\ - / | \ - B | D - \ | - \| - C - -The above is the tree we'd have after handling lock synchronization -sequences "ABC", "AC", "AD". A has three children, {B, C, D}. C is also -a child of B. (The lines represent pointers between parent and child. -Every node can have multiple parents and multiple children.) - -If we hold AC, and want to lock B, we recursively search through B's -children to see if A or C appears. It does, so we reject the attempt. -(A straightforward way to implement it: add a link from C to B, then -determine whether the graph starting at B contains a cycle.) - -If we hold AC and want to lock D, we would succeed, creating a new link -from C to D. - -Updates to MutexInfo structs are only allowed for the thread that holds -the lock, so we actually do most of our deadlock prediction work after -the lock has been acquired. -*/ - -// ============================================================================= -// log functions -// ============================================================================= - -#define LOGD(format, ...) \ - __libc_android_log_print(ANDROID_LOG_DEBUG, \ - "pthread_debug", (format), ##__VA_ARGS__ ) - -#define LOGW(format, ...) \ - __libc_android_log_print(ANDROID_LOG_WARN, \ - "pthread_debug", (format), ##__VA_ARGS__ ) - -#define LOGE(format, ...) \ - __libc_android_log_print(ANDROID_LOG_ERROR, \ - "pthread_debug", (format), ##__VA_ARGS__ ) - -#define LOGI(format, ...) \ - __libc_android_log_print(ANDROID_LOG_INFO, \ - "pthread_debug", (format), ##__VA_ARGS__ ) - -static const char* const kStartBanner = - "==============================================================="; - -static const char* const kEndBanner = - "==============================================================="; - -extern char* __progname; - -// ============================================================================= -// map info functions -// ============================================================================= - -typedef struct mapinfo { - struct mapinfo *next; - unsigned start; - unsigned end; - char name[]; -} mapinfo; - -static mapinfo* sMapInfo = NULL; - -static mapinfo *parse_maps_line(char *line) -{ - mapinfo *mi; - int len = strlen(line); - - if(len < 1) return 0; - line[--len] = 0; - - if(len < 50) return 0; - if(line[20] != 'x') return 0; - - mi = malloc(sizeof(mapinfo) + (len - 47)); - if(mi == 0) return 0; - - mi->start = strtoul(line, 0, 16); - mi->end = strtoul(line + 9, 0, 16); - /* To be filled in parse_elf_info if the mapped section starts with - * elf_header - */ - mi->next = 0; - strcpy(mi->name, line + 49); - - return mi; -} - -static mapinfo *init_mapinfo(int pid) -{ - struct mapinfo *milist = NULL; - char data[1024]; - sprintf(data, "/proc/%d/maps", pid); - FILE *fp = fopen(data, "r"); - if(fp) { - while(fgets(data, sizeof(data), fp)) { - mapinfo *mi = parse_maps_line(data); - if(mi) { - mi->next = milist; - milist = mi; - } - } - fclose(fp); - } - - return milist; -} - -static void deinit_mapinfo(mapinfo *mi) -{ - mapinfo *del; - while(mi) { - del = mi; - mi = mi->next; - free(del); - } -} - -/* Find the containing map info for the pc */ -static const mapinfo *pc_to_mapinfo(mapinfo *mi, unsigned pc, unsigned *rel_pc) -{ - *rel_pc = pc; - while(mi) { - if((pc >= mi->start) && (pc < mi->end)){ - // Only calculate the relative offset for shared libraries - if (strstr(mi->name, ".so")) { - *rel_pc -= mi->start; - } - return mi; - } - mi = mi->next; - } - return NULL; -} - -// ============================================================================= -// stack trace functions -// ============================================================================= - -#define STACK_TRACE_DEPTH 16 - -typedef struct -{ - size_t count; - intptr_t* addrs; -} stack_crawl_state_t; - -/* depends how the system includes define this */ -#ifdef HAVE_UNWIND_CONTEXT_STRUCT -typedef struct _Unwind_Context __unwind_context; -#else -typedef _Unwind_Context __unwind_context; -#endif - -static _Unwind_Reason_Code trace_function(__unwind_context *context, void *arg) -{ - stack_crawl_state_t* state = (stack_crawl_state_t*)arg; - if (state->count) { - intptr_t ip = (intptr_t)_Unwind_GetIP(context); - if (ip) { - state->addrs[0] = ip; - state->addrs++; - state->count--; - return _URC_NO_REASON; - } - } - /* - * If we run out of space to record the address or 0 has been seen, stop - * unwinding the stack. - */ - return _URC_END_OF_STACK; -} - -static inline -int get_backtrace(intptr_t* addrs, size_t max_entries) -{ - stack_crawl_state_t state; - state.count = max_entries; - state.addrs = (intptr_t*)addrs; - _Unwind_Backtrace(trace_function, (void*)&state); - return max_entries - state.count; -} - -static void log_backtrace(intptr_t* addrs, size_t c) -{ - int index = 0; - size_t i; - for (i=0 ; i0 || !HAVE_DLADDR) { - /* - * this test is a bit sketchy, but it allows us to skip the - * stack trace entries due to this debugging code. it works - * because those don't have a symbol (they're not exported) - */ - mapinfo const* mi = pc_to_mapinfo(sMapInfo, addrs[i], &relpc); - char const* soname = mi ? mi->name : NULL; -#if HAVE_DLADDR - if (!soname) - soname = info.dli_fname; -#endif - if (!soname) - soname = "unknown"; - - if (symbol) { - LOGW(" " - "#%02d pc %08lx %s (%s+0x%x)", - index, relpc, soname, symbol, - addrs[i] - (intptr_t)offset); - } else { - LOGW(" " - "#%02d pc %08lx %s", - index, relpc, soname); - } - index++; - } - } -} - -/****************************************************************************/ - -/* - * level <= 0 : deadlock prediction disabled - * level 1 : deadlock prediction enabled, w/o call stacks - * level 2 : deadlock prediction enabled w/ call stacks - */ -#define CAPTURE_CALLSTACK 2 -static int sPthreadDebugLevel = 0; -static pid_t sPthreadDebugDisabledThread = -1; -static pthread_mutex_t sDbgLock = PTHREAD_MUTEX_INITIALIZER; - -/****************************************************************************/ - -/* some simple/lame malloc replacement - * NOT thread-safe and leaks everything - */ - -#define DBG_ALLOC_BLOCK_SIZE PAGESIZE -static size_t sDbgAllocOffset = DBG_ALLOC_BLOCK_SIZE; -static char* sDbgAllocPtr = NULL; - -static void* DbgAllocLocked(size_t size) { - if ((sDbgAllocOffset + size) > DBG_ALLOC_BLOCK_SIZE) { - sDbgAllocOffset = 0; - sDbgAllocPtr = mmap(NULL, DBG_ALLOC_BLOCK_SIZE, PROT_READ|PROT_WRITE, - MAP_ANON | MAP_PRIVATE, 0, 0); - if (sDbgAllocPtr == MAP_FAILED) { - return NULL; - } - } - void* addr = sDbgAllocPtr + sDbgAllocOffset; - sDbgAllocOffset += size; - return addr; -} - -static void* debug_realloc(void *ptr, size_t size, size_t old_size) { - void* addr = mmap(NULL, size, PROT_READ|PROT_WRITE, - MAP_ANON | MAP_PRIVATE, 0, 0); - if (addr != MAP_FAILED) { - if (ptr) { - memcpy(addr, ptr, old_size); - munmap(ptr, old_size); - } - } else { - addr = NULL; - } - return addr; -} - -/*****************************************************************************/ - -struct MutexInfo; - -typedef struct CallStack { - intptr_t depth; - intptr_t* addrs; -} CallStack; - -typedef struct MutexInfo* MutexInfoListEntry; -typedef struct CallStack CallStackListEntry; - -typedef struct GrowingList { - int alloc; - int count; - union { - void* data; - MutexInfoListEntry* list; - CallStackListEntry* stack; - }; -} GrowingList; - -typedef GrowingList MutexInfoList; -typedef GrowingList CallStackList; - -typedef struct MutexInfo { - // thread currently holding the lock or 0 - pid_t owner; - - // most-recently-locked doubly-linked list - struct MutexInfo* prev; - struct MutexInfo* next; - - // for reentrant locks - int lockCount; - // when looking for loops in the graph, marks visited nodes - int historyMark; - // the actual mutex - pthread_mutex_t* mutex; - // list of locks directly acquired AFTER this one in the same thread - MutexInfoList children; - // list of locks directly acquired BEFORE this one in the same thread - MutexInfoList parents; - // list of call stacks when a new link is established to this lock form its parent - CallStackList stacks; - // call stack when this lock was acquired last - int stackDepth; - intptr_t stackTrace[STACK_TRACE_DEPTH]; -} MutexInfo; - -static void growingListInit(GrowingList* list) { - list->alloc = 0; - list->count = 0; - list->data = NULL; -} - -static void growingListAdd(GrowingList* pList, size_t objSize) { - if (pList->count == pList->alloc) { - size_t oldsize = pList->alloc * objSize; - pList->alloc += PAGESIZE / objSize; - size_t size = pList->alloc * objSize; - pList->data = debug_realloc(pList->data, size, oldsize); - } - pList->count++; -} - -static void initMutexInfo(MutexInfo* object, pthread_mutex_t* mutex) { - object->owner = 0; - object->prev = 0; - object->next = 0; - object->lockCount = 0; - object->historyMark = 0; - object->mutex = mutex; - growingListInit(&object->children); - growingListInit(&object->parents); - growingListInit(&object->stacks); - object->stackDepth = 0; -} - -typedef struct ThreadInfo { - pid_t pid; - MutexInfo* mrl; -} ThreadInfo; - -static void initThreadInfo(ThreadInfo* object, pid_t pid) { - object->pid = pid; - object->mrl = NULL; -} - -/****************************************************************************/ - -static MutexInfo* get_mutex_info(pthread_mutex_t *mutex); -static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object); -static void mutex_unlock_checked(MutexInfo* object); - -/****************************************************************************/ - -extern int pthread_mutex_lock_impl(pthread_mutex_t *mutex); -extern int pthread_mutex_unlock_impl(pthread_mutex_t *mutex); - -static int pthread_mutex_lock_unchecked(pthread_mutex_t *mutex) { - return pthread_mutex_lock_impl(mutex); -} - -static int pthread_mutex_unlock_unchecked(pthread_mutex_t *mutex) { - return pthread_mutex_unlock_impl(mutex); -} - -/****************************************************************************/ - -static void dup_backtrace(CallStack* stack, int count, intptr_t const* addrs) { - stack->depth = count; - stack->addrs = DbgAllocLocked(count * sizeof(intptr_t)); - memcpy(stack->addrs, addrs, count * sizeof(intptr_t)); -} - -/****************************************************************************/ - -static int historyListHas( - const MutexInfoList* list, MutexInfo const * obj) { - int i; - for (i=0; icount; i++) { - if (list->list[i] == obj) { - return i; - } - } - return -1; -} - -static void historyListAdd(MutexInfoList* pList, MutexInfo* obj) { - growingListAdd(pList, sizeof(MutexInfoListEntry)); - pList->list[pList->count - 1] = obj; -} - -static int historyListRemove(MutexInfoList* pList, MutexInfo* obj) { - int i; - for (i = pList->count-1; i >= 0; i--) { - if (pList->list[i] == obj) { - break; - } - } - if (i < 0) { - // not found! - return 0; - } - - if (i != pList->count-1) { - // copy the last entry to the new free slot - pList->list[i] = pList->list[pList->count-1]; - } - pList->count--; - memset(&pList->list[pList->count], 0, sizeof(MutexInfoListEntry)); - return 1; -} - -static void linkParentToChild(MutexInfo* parent, MutexInfo* child) { - historyListAdd(&parent->children, child); - historyListAdd(&child->parents, parent); -} - -static void unlinkParentFromChild(MutexInfo* parent, MutexInfo* child) { - historyListRemove(&parent->children, child); - historyListRemove(&child->parents, parent); -} - -/****************************************************************************/ - -static void callstackListAdd(CallStackList* pList, - int count, intptr_t const* addrs) { - growingListAdd(pList, sizeof(CallStackListEntry)); - dup_backtrace(&pList->stack[pList->count - 1], count, addrs); -} - -/****************************************************************************/ - -/* - * Recursively traverse the object hierarchy starting at "obj". We mark - * ourselves on entry and clear the mark on exit. If we ever encounter - * a marked object, we have a cycle. - * - * Returns "true" if all is well, "false" if we found a cycle. - */ - -static int traverseTree(MutexInfo* obj, MutexInfo const* objParent) -{ - /* - * Have we been here before? - */ - if (obj->historyMark) { - int stackDepth; - intptr_t addrs[STACK_TRACE_DEPTH]; - - /* Turn off prediction temporarily in this thread while logging */ - sPthreadDebugDisabledThread = gettid(); - - if (sMapInfo == NULL) { - // note: we're protected by sDbgLock - sMapInfo = init_mapinfo(getpid()); - } - - LOGW("%s\n", kStartBanner); - LOGW("pid: %d, tid: %d >>> %s <<<", getpid(), gettid(), __progname); - LOGW("Illegal lock attempt:\n"); - LOGW("--- pthread_mutex_t at %p\n", obj->mutex); - stackDepth = get_backtrace(addrs, STACK_TRACE_DEPTH); - log_backtrace(addrs, stackDepth); - - LOGW("+++ Currently held locks in this thread (in reverse order):"); - MutexInfo* cur = obj; - pid_t ourtid = gettid(); - int i; - for (i=0 ; iparents.count ; i++) { - MutexInfo* parent = cur->parents.list[i]; - if (parent->owner == ourtid) { - LOGW("--- pthread_mutex_t at %p\n", parent->mutex); - if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { - log_backtrace(parent->stackTrace, parent->stackDepth); - } - cur = parent; - break; - } - } - - LOGW("+++ Earlier, the following lock order (from last to first) was established\n"); - return 0; - } - - obj->historyMark = 1; - - MutexInfoList* pList = &obj->children; - int result = 1; - int i; - for (i = pList->count-1; i >= 0; i--) { - MutexInfo* child = pList->list[i]; - if (!traverseTree(child, obj)) { - LOGW("--- pthread_mutex_t at %p\n", obj->mutex); - if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { - int index = historyListHas(&obj->parents, objParent); - if ((size_t)index < (size_t)obj->stacks.count) { - log_backtrace( - obj->stacks.stack[index].addrs, - obj->stacks.stack[index].depth); - } else { - log_backtrace( - obj->stackTrace, - obj->stackDepth); - } - } - result = 0; - break; - } - } - - obj->historyMark = 0; - return result; -} - -/****************************************************************************/ - -static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object) -{ - pid_t tid = gettid(); - if (object->owner == tid) { - object->lockCount++; - return; - } - - object->owner = tid; - object->lockCount = 0; - - if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { - // always record the call stack when acquiring a lock. - // it's not efficient, but is useful during diagnostics - object->stackDepth = get_backtrace(object->stackTrace, STACK_TRACE_DEPTH); - } - - // no other locks held in this thread -- no deadlock possible! - if (mrl == NULL) - return; - - // check if the lock we're trying to acquire is a direct descendant of - // the most recently locked mutex in this thread, in which case we're - // in a good situation -- no deadlock possible - if (historyListHas(&mrl->children, object) >= 0) - return; - - pthread_mutex_lock_unchecked(&sDbgLock); - - linkParentToChild(mrl, object); - if (!traverseTree(object, mrl)) { - deinit_mapinfo(sMapInfo); - sMapInfo = NULL; - LOGW("%s\n", kEndBanner); - unlinkParentFromChild(mrl, object); - // reenable pthread debugging for this thread - sPthreadDebugDisabledThread = -1; - } else { - // record the call stack for this link - // NOTE: the call stack is added at the same index - // as mrl in object->parents[] - // ie: object->parents.count == object->stacks.count, which is - // also the index. - if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { - callstackListAdd(&object->stacks, - object->stackDepth, object->stackTrace); - } - } - - pthread_mutex_unlock_unchecked(&sDbgLock); -} - -static void mutex_unlock_checked(MutexInfo* object) -{ - pid_t tid = gettid(); - if (object->owner == tid) { - if (object->lockCount == 0) { - object->owner = 0; - } else { - object->lockCount--; - } - } -} - - -// ============================================================================= -// Hash Table functions -// ============================================================================= - -/****************************************************************************/ - -#define HASHTABLE_SIZE 256 - -typedef struct HashEntry HashEntry; -struct HashEntry { - size_t slot; - HashEntry* prev; - HashEntry* next; - void* data; -}; - -typedef struct HashTable HashTable; -struct HashTable { - HashEntry* slots[HASHTABLE_SIZE]; -}; - -static HashTable sMutexMap; -static HashTable sThreadMap; - -/****************************************************************************/ - -static uint32_t get_hashcode(void const * key, size_t keySize) -{ - uint32_t h = keySize; - char const* data = (char const*)key; - size_t i; - for (i = 0; i < keySize; i++) { - h = h * 31 + *data; - data++; - } - return (uint32_t)h; -} - -static size_t get_index(uint32_t h) -{ - // We apply this secondary hashing discovered by Doug Lea to defend - // against bad hashes. - h += ~(h << 9); - h ^= (((unsigned int) h) >> 14); - h += (h << 4); - h ^= (((unsigned int) h) >> 10); - return (size_t)h & (HASHTABLE_SIZE - 1); -} - -/****************************************************************************/ - -static void hashmap_init(HashTable* table) { - memset(table, 0, sizeof(HashTable)); -} - -static void hashmap_removeEntry(HashTable* table, HashEntry* entry) -{ - HashEntry* prev = entry->prev; - HashEntry* next = entry->next; - if (prev != NULL) entry->prev->next = next; - if (next != NULL) entry->next->prev = prev; - if (prev == NULL) { - // we are the head of the list. set the head to be next - table->slots[entry->slot] = entry->next; - } -} - -static HashEntry* hashmap_lookup(HashTable* table, - void const* key, size_t ksize, - int (*equals)(void const* data, void const* key)) -{ - const uint32_t hash = get_hashcode(key, ksize); - const size_t slot = get_index(hash); - - HashEntry* entry = table->slots[slot]; - while (entry) { - if (equals(entry->data, key)) { - break; - } - entry = entry->next; - } - - if (entry == NULL) { - // create a new entry - entry = (HashEntry*)DbgAllocLocked(sizeof(HashEntry)); - entry->data = NULL; - entry->slot = slot; - entry->prev = NULL; - entry->next = table->slots[slot]; - if (entry->next != NULL) { - entry->next->prev = entry; - } - table->slots[slot] = entry; - } - return entry; -} - -/****************************************************************************/ - -static int MutexInfo_equals(void const* data, void const* key) { - return ((MutexInfo const *)data)->mutex == *(pthread_mutex_t **)key; -} - -static MutexInfo* get_mutex_info(pthread_mutex_t *mutex) -{ - pthread_mutex_lock_unchecked(&sDbgLock); - - HashEntry* entry = hashmap_lookup(&sMutexMap, - &mutex, sizeof(mutex), - &MutexInfo_equals); - if (entry->data == NULL) { - entry->data = (MutexInfo*)DbgAllocLocked(sizeof(MutexInfo)); - initMutexInfo(entry->data, mutex); - } - - pthread_mutex_unlock_unchecked(&sDbgLock); - - return (MutexInfo *)entry->data; -} - -/****************************************************************************/ - -static int ThreadInfo_equals(void const* data, void const* key) { - return ((ThreadInfo const *)data)->pid == *(pid_t *)key; -} - -static ThreadInfo* get_thread_info(pid_t pid) -{ - pthread_mutex_lock_unchecked(&sDbgLock); - - HashEntry* entry = hashmap_lookup(&sThreadMap, - &pid, sizeof(pid), - &ThreadInfo_equals); - if (entry->data == NULL) { - entry->data = (ThreadInfo*)DbgAllocLocked(sizeof(ThreadInfo)); - initThreadInfo(entry->data, pid); - } - - pthread_mutex_unlock_unchecked(&sDbgLock); - - return (ThreadInfo *)entry->data; -} - -static void push_most_recently_locked(MutexInfo* mrl) { - ThreadInfo* tinfo = get_thread_info(gettid()); - mrl->next = NULL; - mrl->prev = tinfo->mrl; - tinfo->mrl = mrl; -} - -static void remove_most_recently_locked(MutexInfo* mrl) { - ThreadInfo* tinfo = get_thread_info(gettid()); - if (mrl->next) { - (mrl->next)->prev = mrl->prev; - } - if (mrl->prev) { - (mrl->prev)->next = mrl->next; - } - if (tinfo->mrl == mrl) { - tinfo->mrl = mrl->next; - } -} - -static MutexInfo* get_most_recently_locked() { - ThreadInfo* tinfo = get_thread_info(gettid()); - return tinfo->mrl; -} - -/****************************************************************************/ - -/* pthread_debug_init() is called from libc_init_dynamic() just - * after system properties have been initialized - */ - -__LIBC_HIDDEN__ -void pthread_debug_init(void) { - char env[PROP_VALUE_MAX]; - if (__system_property_get("debug.libc.pthread", env)) { - int level = atoi(env); - if (level) { - LOGI("pthread deadlock detection level %d enabled for pid %d (%s)", - level, getpid(), __progname); - hashmap_init(&sMutexMap); - sPthreadDebugLevel = level; - } - } -} - -/* - * See if we were allowed to grab the lock at this time. We do it - * *after* acquiring the lock, rather than before, so that we can - * freely update the MutexInfo struct. This seems counter-intuitive, - * but our goal is deadlock *prediction* not deadlock *prevention*. - * (If we actually deadlock, the situation is easy to diagnose from - * a thread dump, so there's no point making a special effort to do - * the checks before the lock is held.) - */ - -__LIBC_HIDDEN__ -void pthread_debug_mutex_lock_check(pthread_mutex_t *mutex) -{ - if (sPthreadDebugLevel == 0) return; - // prediction disabled for this thread - if (sPthreadDebugDisabledThread == gettid()) - return; - MutexInfo* object = get_mutex_info(mutex); - MutexInfo* mrl = get_most_recently_locked(); - mutex_lock_checked(mrl, object); - push_most_recently_locked(object); -} - -/* - * pthread_debug_mutex_unlock_check() must be called with the mutex - * still held (ie: before calling the real unlock) - */ - -__LIBC_HIDDEN__ -void pthread_debug_mutex_unlock_check(pthread_mutex_t *mutex) -{ - if (sPthreadDebugLevel == 0) return; - // prediction disabled for this thread - if (sPthreadDebugDisabledThread == gettid()) - return; - MutexInfo* object = get_mutex_info(mutex); - remove_most_recently_locked(object); - mutex_unlock_checked(object); -} diff --git a/libc/bionic/pthread_debug.cpp b/libc/bionic/pthread_debug.cpp new file mode 100644 index 0000000..c557211 --- /dev/null +++ b/libc/bionic/pthread_debug.cpp @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2011 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 +#include +#include +#include + +//#include +#include +#include +#include +#include +#include +#include +#include + +#include "bionic_tls.h" +#include "debug_mapinfo.h" +#include "debug_stacktrace.h" +#include "logd.h" + +#include + +/* + * =========================================================================== + * Deadlock prediction + * =========================================================================== + */ +/* +The idea is to predict the possibility of deadlock by recording the order +in which locks are acquired. If we see an attempt to acquire a lock +out of order, we can identify the locks and offending code. + +To make this work, we need to keep track of the locks held by each thread, +and create history trees for each lock. When a thread tries to acquire +a new lock, we walk through the "history children" of the lock, looking +for a match with locks the thread already holds. If we find a match, +it means the thread has made a request that could result in a deadlock. + +To support recursive locks, we always allow re-locking a currently-held +lock, and maintain a recursion depth count. + +An ASCII-art example, where letters represent locks: + + A + /|\ + / | \ + B | D + \ | + \| + C + +The above is the tree we'd have after handling lock synchronization +sequences "ABC", "AC", "AD". A has three children, {B, C, D}. C is also +a child of B. (The lines represent pointers between parent and child. +Every node can have multiple parents and multiple children.) + +If we hold AC, and want to lock B, we recursively search through B's +children to see if A or C appears. It does, so we reject the attempt. +(A straightforward way to implement it: add a link from C to B, then +determine whether the graph starting at B contains a cycle.) + +If we hold AC and want to lock D, we would succeed, creating a new link +from C to D. + +Updates to MutexInfo structs are only allowed for the thread that holds +the lock, so we actually do most of our deadlock prediction work after +the lock has been acquired. +*/ + +// ============================================================================= +// log functions +// ============================================================================= + +#define LOGD(format, ...) \ + __libc_format_log(ANDROID_LOG_DEBUG, "pthread_debug", (format), ##__VA_ARGS__ ) + +#define LOGW(format, ...) \ + __libc_format_log(ANDROID_LOG_WARN, "pthread_debug", (format), ##__VA_ARGS__ ) + +#define LOGE(format, ...) \ + __libc_format_log(ANDROID_LOG_ERROR, "pthread_debug", (format), ##__VA_ARGS__ ) + +#define LOGI(format, ...) \ + __libc_format_log(ANDROID_LOG_INFO, "pthread_debug", (format), ##__VA_ARGS__ ) + +static const char* const kStartBanner = + "==============================================================="; + +static const char* const kEndBanner = + "==============================================================="; + +extern char* __progname; + +#define STACK_TRACE_DEPTH 16 + +static mapinfo_t* gMapInfo; + +/****************************************************************************/ + +/* + * level <= 0 : deadlock prediction disabled + * level 1 : deadlock prediction enabled, w/o call stacks + * level 2 : deadlock prediction enabled w/ call stacks + */ +#define CAPTURE_CALLSTACK 2 +static int sPthreadDebugLevel = 0; +static pid_t sPthreadDebugDisabledThread = -1; +static pthread_mutex_t sDbgLock = PTHREAD_MUTEX_INITIALIZER; + +/****************************************************************************/ + +/* some simple/lame malloc replacement + * NOT thread-safe and leaks everything + */ + +#define DBG_ALLOC_BLOCK_SIZE PAGESIZE +static size_t sDbgAllocOffset = DBG_ALLOC_BLOCK_SIZE; +static char* sDbgAllocPtr = NULL; + +template +static T* DbgAllocLocked(size_t count = 1) { + size_t size = sizeof(T) * count; + if ((sDbgAllocOffset + size) > DBG_ALLOC_BLOCK_SIZE) { + sDbgAllocOffset = 0; + sDbgAllocPtr = reinterpret_cast(mmap(NULL, DBG_ALLOC_BLOCK_SIZE, + PROT_READ|PROT_WRITE, + MAP_ANON | MAP_PRIVATE, 0, 0)); + if (sDbgAllocPtr == MAP_FAILED) { + return NULL; + } + } + void* addr = sDbgAllocPtr + sDbgAllocOffset; + sDbgAllocOffset += size; + return reinterpret_cast(addr); +} + +static void* debug_realloc(void *ptr, size_t size, size_t old_size) { + void* addr = mmap(NULL, size, PROT_READ|PROT_WRITE, + MAP_ANON | MAP_PRIVATE, 0, 0); + if (addr != MAP_FAILED) { + if (ptr) { + memcpy(addr, ptr, old_size); + munmap(ptr, old_size); + } + } else { + addr = NULL; + } + return addr; +} + +/*****************************************************************************/ + +struct MutexInfo; + +typedef struct CallStack { + intptr_t depth; + intptr_t* addrs; +} CallStack; + +typedef struct MutexInfo* MutexInfoListEntry; +typedef struct CallStack CallStackListEntry; + +typedef struct GrowingList { + int alloc; + int count; + union { + void* data; + MutexInfoListEntry* list; + CallStackListEntry* stack; + }; +} GrowingList; + +typedef GrowingList MutexInfoList; +typedef GrowingList CallStackList; + +typedef struct MutexInfo { + // thread currently holding the lock or 0 + pid_t owner; + + // most-recently-locked doubly-linked list + struct MutexInfo* prev; + struct MutexInfo* next; + + // for reentrant locks + int lockCount; + // when looking for loops in the graph, marks visited nodes + int historyMark; + // the actual mutex + pthread_mutex_t* mutex; + // list of locks directly acquired AFTER this one in the same thread + MutexInfoList children; + // list of locks directly acquired BEFORE this one in the same thread + MutexInfoList parents; + // list of call stacks when a new link is established to this lock form its parent + CallStackList stacks; + // call stack when this lock was acquired last + int stackDepth; + intptr_t stackTrace[STACK_TRACE_DEPTH]; +} MutexInfo; + +static void growingListInit(GrowingList* list) { + list->alloc = 0; + list->count = 0; + list->data = NULL; +} + +static void growingListAdd(GrowingList* pList, size_t objSize) { + if (pList->count == pList->alloc) { + size_t oldsize = pList->alloc * objSize; + pList->alloc += PAGESIZE / objSize; + size_t size = pList->alloc * objSize; + pList->data = debug_realloc(pList->data, size, oldsize); + } + pList->count++; +} + +static void initMutexInfo(MutexInfo* object, pthread_mutex_t* mutex) { + object->owner = 0; + object->prev = 0; + object->next = 0; + object->lockCount = 0; + object->historyMark = 0; + object->mutex = mutex; + growingListInit(&object->children); + growingListInit(&object->parents); + growingListInit(&object->stacks); + object->stackDepth = 0; +} + +typedef struct ThreadInfo { + pid_t pid; + MutexInfo* mrl; +} ThreadInfo; + +static void initThreadInfo(ThreadInfo* object, pid_t pid) { + object->pid = pid; + object->mrl = NULL; +} + +/****************************************************************************/ + +static MutexInfo* get_mutex_info(pthread_mutex_t *mutex); +static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object); +static void mutex_unlock_checked(MutexInfo* object); + +/****************************************************************************/ + +extern int pthread_mutex_lock_impl(pthread_mutex_t *mutex); +extern int pthread_mutex_unlock_impl(pthread_mutex_t *mutex); + +static int pthread_mutex_lock_unchecked(pthread_mutex_t *mutex) { + return pthread_mutex_lock_impl(mutex); +} + +static int pthread_mutex_unlock_unchecked(pthread_mutex_t *mutex) { + return pthread_mutex_unlock_impl(mutex); +} + +/****************************************************************************/ + +static void dup_backtrace(CallStack* stack, size_t count, intptr_t const* addrs) { + stack->depth = count; + stack->addrs = DbgAllocLocked(count); + memcpy(stack->addrs, addrs, count * sizeof(intptr_t)); +} + +/****************************************************************************/ + +static int historyListHas( + const MutexInfoList* list, MutexInfo const * obj) { + int i; + for (i=0; icount; i++) { + if (list->list[i] == obj) { + return i; + } + } + return -1; +} + +static void historyListAdd(MutexInfoList* pList, MutexInfo* obj) { + growingListAdd(pList, sizeof(MutexInfoListEntry)); + pList->list[pList->count - 1] = obj; +} + +static int historyListRemove(MutexInfoList* pList, MutexInfo* obj) { + int i; + for (i = pList->count-1; i >= 0; i--) { + if (pList->list[i] == obj) { + break; + } + } + if (i < 0) { + // not found! + return 0; + } + + if (i != pList->count-1) { + // copy the last entry to the new free slot + pList->list[i] = pList->list[pList->count-1]; + } + pList->count--; + memset(&pList->list[pList->count], 0, sizeof(MutexInfoListEntry)); + return 1; +} + +static void linkParentToChild(MutexInfo* parent, MutexInfo* child) { + historyListAdd(&parent->children, child); + historyListAdd(&child->parents, parent); +} + +static void unlinkParentFromChild(MutexInfo* parent, MutexInfo* child) { + historyListRemove(&parent->children, child); + historyListRemove(&child->parents, parent); +} + +/****************************************************************************/ + +static void callstackListAdd(CallStackList* pList, + int count, intptr_t const* addrs) { + growingListAdd(pList, sizeof(CallStackListEntry)); + dup_backtrace(&pList->stack[pList->count - 1], count, addrs); +} + +/****************************************************************************/ + +/* + * Recursively traverse the object hierarchy starting at "obj". We mark + * ourselves on entry and clear the mark on exit. If we ever encounter + * a marked object, we have a cycle. + * + * Returns "true" if all is well, "false" if we found a cycle. + */ + +static int traverseTree(MutexInfo* obj, MutexInfo const* objParent) +{ + /* + * Have we been here before? + */ + if (obj->historyMark) { + int stackDepth; + intptr_t addrs[STACK_TRACE_DEPTH]; + + /* Turn off prediction temporarily in this thread while logging */ + sPthreadDebugDisabledThread = gettid(); + + if (gMapInfo == NULL) { + // note: we're protected by sDbgLock. + gMapInfo = mapinfo_create(getpid()); + } + + LOGW("%s\n", kStartBanner); + LOGW("pid: %d, tid: %d >>> %s <<<", getpid(), gettid(), __progname); + LOGW("Illegal lock attempt:\n"); + LOGW("--- pthread_mutex_t at %p\n", obj->mutex); + stackDepth = get_backtrace(addrs, STACK_TRACE_DEPTH); + log_backtrace(gMapInfo, addrs, stackDepth); + + LOGW("+++ Currently held locks in this thread (in reverse order):"); + MutexInfo* cur = obj; + pid_t ourtid = gettid(); + int i; + for (i=0 ; iparents.count ; i++) { + MutexInfo* parent = cur->parents.list[i]; + if (parent->owner == ourtid) { + LOGW("--- pthread_mutex_t at %p\n", parent->mutex); + if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { + log_backtrace(gMapInfo, parent->stackTrace, parent->stackDepth); + } + cur = parent; + break; + } + } + + LOGW("+++ Earlier, the following lock order (from last to first) was established\n"); + return 0; + } + + obj->historyMark = 1; + + MutexInfoList* pList = &obj->children; + int result = 1; + int i; + for (i = pList->count-1; i >= 0; i--) { + MutexInfo* child = pList->list[i]; + if (!traverseTree(child, obj)) { + LOGW("--- pthread_mutex_t at %p\n", obj->mutex); + if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { + int index = historyListHas(&obj->parents, objParent); + if ((size_t)index < (size_t)obj->stacks.count) { + log_backtrace(gMapInfo, + obj->stacks.stack[index].addrs, + obj->stacks.stack[index].depth); + } else { + log_backtrace(gMapInfo, + obj->stackTrace, + obj->stackDepth); + } + } + result = 0; + break; + } + } + + obj->historyMark = 0; + return result; +} + +/****************************************************************************/ + +static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object) +{ + pid_t tid = gettid(); + if (object->owner == tid) { + object->lockCount++; + return; + } + + object->owner = tid; + object->lockCount = 0; + + if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { + // always record the call stack when acquiring a lock. + // it's not efficient, but is useful during diagnostics + object->stackDepth = get_backtrace(object->stackTrace, STACK_TRACE_DEPTH); + } + + // no other locks held in this thread -- no deadlock possible! + if (mrl == NULL) + return; + + // check if the lock we're trying to acquire is a direct descendant of + // the most recently locked mutex in this thread, in which case we're + // in a good situation -- no deadlock possible + if (historyListHas(&mrl->children, object) >= 0) + return; + + pthread_mutex_lock_unchecked(&sDbgLock); + + linkParentToChild(mrl, object); + if (!traverseTree(object, mrl)) { + mapinfo_destroy(gMapInfo); + gMapInfo = NULL; + LOGW("%s\n", kEndBanner); + unlinkParentFromChild(mrl, object); + // reenable pthread debugging for this thread + sPthreadDebugDisabledThread = -1; + } else { + // record the call stack for this link + // NOTE: the call stack is added at the same index + // as mrl in object->parents[] + // ie: object->parents.count == object->stacks.count, which is + // also the index. + if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) { + callstackListAdd(&object->stacks, + object->stackDepth, object->stackTrace); + } + } + + pthread_mutex_unlock_unchecked(&sDbgLock); +} + +static void mutex_unlock_checked(MutexInfo* object) +{ + pid_t tid = gettid(); + if (object->owner == tid) { + if (object->lockCount == 0) { + object->owner = 0; + } else { + object->lockCount--; + } + } +} + + +// ============================================================================= +// Hash Table functions +// ============================================================================= + +/****************************************************************************/ + +#define HASHTABLE_SIZE 256 + +typedef struct HashEntry HashEntry; +struct HashEntry { + size_t slot; + HashEntry* prev; + HashEntry* next; + void* data; +}; + +typedef struct HashTable HashTable; +struct HashTable { + HashEntry* slots[HASHTABLE_SIZE]; +}; + +static HashTable sMutexMap; +static HashTable sThreadMap; + +/****************************************************************************/ + +static uint32_t get_hashcode(void const * key, size_t keySize) +{ + uint32_t h = keySize; + char const* data = (char const*)key; + size_t i; + for (i = 0; i < keySize; i++) { + h = h * 31 + *data; + data++; + } + return (uint32_t)h; +} + +static size_t get_index(uint32_t h) +{ + // We apply this secondary hashing discovered by Doug Lea to defend + // against bad hashes. + h += ~(h << 9); + h ^= (((unsigned int) h) >> 14); + h += (h << 4); + h ^= (((unsigned int) h) >> 10); + return (size_t)h & (HASHTABLE_SIZE - 1); +} + +/****************************************************************************/ + +static void hashmap_init(HashTable* table) { + memset(table, 0, sizeof(HashTable)); +} + +static void hashmap_removeEntry(HashTable* table, HashEntry* entry) +{ + HashEntry* prev = entry->prev; + HashEntry* next = entry->next; + if (prev != NULL) entry->prev->next = next; + if (next != NULL) entry->next->prev = prev; + if (prev == NULL) { + // we are the head of the list. set the head to be next + table->slots[entry->slot] = entry->next; + } +} + +static HashEntry* hashmap_lookup(HashTable* table, + void const* key, size_t ksize, + int (*equals)(void const* data, void const* key)) +{ + const uint32_t hash = get_hashcode(key, ksize); + const size_t slot = get_index(hash); + + HashEntry* entry = table->slots[slot]; + while (entry) { + if (equals(entry->data, key)) { + break; + } + entry = entry->next; + } + + if (entry == NULL) { + // create a new entry + entry = DbgAllocLocked(); + entry->data = NULL; + entry->slot = slot; + entry->prev = NULL; + entry->next = table->slots[slot]; + if (entry->next != NULL) { + entry->next->prev = entry; + } + table->slots[slot] = entry; + } + return entry; +} + +/****************************************************************************/ + +static int MutexInfo_equals(void const* data, void const* key) { + return ((MutexInfo const *)data)->mutex == *(pthread_mutex_t **)key; +} + +static MutexInfo* get_mutex_info(pthread_mutex_t *mutex) +{ + pthread_mutex_lock_unchecked(&sDbgLock); + + HashEntry* entry = hashmap_lookup(&sMutexMap, + &mutex, sizeof(mutex), + &MutexInfo_equals); + if (entry->data == NULL) { + MutexInfo* mutex_info = DbgAllocLocked(); + entry->data = mutex_info; + initMutexInfo(mutex_info, mutex); + } + + pthread_mutex_unlock_unchecked(&sDbgLock); + + return (MutexInfo *)entry->data; +} + +/****************************************************************************/ + +static int ThreadInfo_equals(void const* data, void const* key) { + return ((ThreadInfo const *)data)->pid == *(pid_t *)key; +} + +static ThreadInfo* get_thread_info(pid_t pid) +{ + pthread_mutex_lock_unchecked(&sDbgLock); + + HashEntry* entry = hashmap_lookup(&sThreadMap, + &pid, sizeof(pid), + &ThreadInfo_equals); + if (entry->data == NULL) { + ThreadInfo* thread_info = DbgAllocLocked(); + entry->data = thread_info; + initThreadInfo(thread_info, pid); + } + + pthread_mutex_unlock_unchecked(&sDbgLock); + + return (ThreadInfo *)entry->data; +} + +static void push_most_recently_locked(MutexInfo* mrl) { + ThreadInfo* tinfo = get_thread_info(gettid()); + mrl->next = NULL; + mrl->prev = tinfo->mrl; + tinfo->mrl = mrl; +} + +static void remove_most_recently_locked(MutexInfo* mrl) { + ThreadInfo* tinfo = get_thread_info(gettid()); + if (mrl->next) { + (mrl->next)->prev = mrl->prev; + } + if (mrl->prev) { + (mrl->prev)->next = mrl->next; + } + if (tinfo->mrl == mrl) { + tinfo->mrl = mrl->next; + } +} + +static MutexInfo* get_most_recently_locked() { + ThreadInfo* tinfo = get_thread_info(gettid()); + return tinfo->mrl; +} + +/****************************************************************************/ + +/* pthread_debug_init() is called from libc_init_dynamic() just + * after system properties have been initialized + */ + +extern "C" __LIBC_HIDDEN__ void pthread_debug_init() { + char env[PROP_VALUE_MAX]; + if (__system_property_get("debug.libc.pthread", env)) { + int level = atoi(env); + if (level) { + LOGI("pthread deadlock detection level %d enabled for pid %d (%s)", + level, getpid(), __progname); + hashmap_init(&sMutexMap); + sPthreadDebugLevel = level; + } + } +} + +/* + * See if we were allowed to grab the lock at this time. We do it + * *after* acquiring the lock, rather than before, so that we can + * freely update the MutexInfo struct. This seems counter-intuitive, + * but our goal is deadlock *prediction* not deadlock *prevention*. + * (If we actually deadlock, the situation is easy to diagnose from + * a thread dump, so there's no point making a special effort to do + * the checks before the lock is held.) + */ + +extern "C" __LIBC_HIDDEN__ void pthread_debug_mutex_lock_check(pthread_mutex_t *mutex) +{ + if (sPthreadDebugLevel == 0) return; + // prediction disabled for this thread + if (sPthreadDebugDisabledThread == gettid()) + return; + MutexInfo* object = get_mutex_info(mutex); + MutexInfo* mrl = get_most_recently_locked(); + mutex_lock_checked(mrl, object); + push_most_recently_locked(object); +} + +/* + * pthread_debug_mutex_unlock_check() must be called with the mutex + * still held (ie: before calling the real unlock) + */ + +extern "C" __LIBC_HIDDEN__ void pthread_debug_mutex_unlock_check(pthread_mutex_t *mutex) +{ + if (sPthreadDebugLevel == 0) return; + // prediction disabled for this thread + if (sPthreadDebugDisabledThread == gettid()) + return; + MutexInfo* object = get_mutex_info(mutex); + remove_most_recently_locked(object); + mutex_unlock_checked(object); +} diff --git a/libc/bionic/ssp.cpp b/libc/bionic/ssp.cpp index fdf8832..08c36c5 100644 --- a/libc/bionic/ssp.cpp +++ b/libc/bionic/ssp.cpp @@ -63,8 +63,8 @@ void __stack_chk_fail() { path[count] = '\0'; } - // Do a best effort at logging. This ends up calling writev(2). - __libc_android_log_print(ANDROID_LOG_FATAL, path, "stack corruption detected: aborted"); + // Do a best effort at logging. + __libc_android_log_write(ANDROID_LOG_FATAL, path, "stack corruption detected: aborted"); // Make sure there is no default action for SIGABRT. struct sigaction sa; diff --git a/libc/bionic/stubs.cpp b/libc/bionic/stubs.cpp index 48281b4..3f24d1b 100644 --- a/libc/bionic/stubs.cpp +++ b/libc/bionic/stubs.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -435,7 +436,7 @@ protoent* getprotobynumber(int /*proto*/) { static void unimplemented_stub(const char* function) { const char* fmt = "%s(3) is not implemented on Android\n"; - __libc_android_log_print(ANDROID_LOG_WARN, "libc", fmt, function); + __libc_format_log(ANDROID_LOG_WARN, "libc", fmt, function); fprintf(stderr, fmt, function); } diff --git a/libc/private/debug_format.h b/libc/private/debug_format.h new file mode 100644 index 0000000..0bc1148 --- /dev/null +++ b/libc/private/debug_format.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _DEBUG_FORMAT_H +#define _DEBUG_FORMAT_H + +#include +#include +#include + +__BEGIN_DECLS + +// Formatting routines for the C library's internal debugging. +// Unlike the usual alternatives, these don't allocate. + +__LIBC_HIDDEN__ int __libc_format_buffer(char* buffer, size_t buffer_size, const char* format, ...) + __attribute__((__format__(printf, 3, 4))); + +__LIBC_HIDDEN__ int __libc_format_fd(int fd, const char* format, ...) + __attribute__((__format__(printf, 2, 3))); + +__LIBC_HIDDEN__ int __libc_format_log(int priority, const char* tag, const char* format, ...) + __attribute__((__format__(printf, 3, 4))); + +__LIBC_HIDDEN__ int __libc_format_log_va_list(int priority, const char* tag, const char* format, + va_list ap); + +__END_DECLS + +#endif /* _DEBUG_FORMAT_H */ -- cgit v1.1