diff options
-rw-r--r-- | include/backtrace/backtrace.h | 104 | ||||
-rw-r--r-- | libbacktrace/Android.mk | 176 | ||||
-rw-r--r-- | libbacktrace/backtrace_test.c | 217 | ||||
-rw-r--r-- | libbacktrace/backtrace_testlib.c | 56 | ||||
-rw-r--r-- | libbacktrace/common.c | 113 | ||||
-rw-r--r-- | libbacktrace/common.h | 25 | ||||
-rw-r--r-- | libbacktrace/corkscrew.c | 130 | ||||
-rw-r--r-- | libbacktrace/demangle.c | 33 | ||||
-rw-r--r-- | libbacktrace/demangle.h | 25 | ||||
-rw-r--r-- | libbacktrace/map_info.c | 173 | ||||
-rw-r--r-- | libbacktrace/stubs.c | 53 | ||||
-rw-r--r-- | libbacktrace/unwind.c | 57 | ||||
-rw-r--r-- | libbacktrace/unwind.h | 34 | ||||
-rw-r--r-- | libbacktrace/unwind_local.c | 123 | ||||
-rw-r--r-- | libbacktrace/unwind_remote.c | 151 |
15 files changed, 1470 insertions, 0 deletions
diff --git a/include/backtrace/backtrace.h b/include/backtrace/backtrace.h new file mode 100644 index 0000000..38a9645 --- /dev/null +++ b/include/backtrace/backtrace.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _BACKTRACE_H +#define _BACKTRACE_H + +#include <sys/types.h> +#include <stdbool.h> +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_BACKTRACE_FRAMES 64 + +typedef struct backtrace_map_info { + struct backtrace_map_info* next; + uintptr_t start; + uintptr_t end; + bool is_readable; + bool is_writable; + bool is_executable; + char name[]; +} backtrace_map_info_t; + +typedef struct { + uintptr_t pc; /* The absolute pc. */ + uintptr_t sp; /* The top of the stack. */ + size_t stack_size; /* The size of the stack, zero indicate an unknown stack size. */ + const char* map_name; /* The name of the map to which this pc belongs, NULL indicates the pc doesn't belong to a known map. */ + uintptr_t map_offset; /* pc relative to the start of the map, only valid if map_name is not NULL. */ + char* proc_name; /* The function name associated with this pc, NULL if no not found. */ + uintptr_t proc_offset; /* pc relative to the start of the procedure, only valid if proc_name is not NULL. */ +} backtrace_frame_data_t; + +typedef struct { + backtrace_frame_data_t frames[MAX_BACKTRACE_FRAMES]; + size_t num_frames; + + pid_t tid; + backtrace_map_info_t* map_info_list; + void* private_data; +} backtrace_t; + +/* Gather the backtrace data for tid and fill in the backtrace structure. + * If tid < 0, then gather the backtrace for the current thread. + */ +bool backtrace_get_data(backtrace_t* backtrace, pid_t tid); + +/* Free any memory associated with the backtrace structure. */ +void backtrace_free_data(backtrace_t* backtrace); + +/* Read data at a specific address for a process. */ +bool backtrace_read_word( + const backtrace_t* backtrace, uintptr_t ptr, uint32_t* value); + +/* Get information about the map associated with a pc. If NULL is + * returned, then map_start is not set. + */ +const char* backtrace_get_map_info( + const backtrace_t* backtrace, uintptr_t pc, uintptr_t* map_start); + +/* Get the procedure name and offest given the pc. If NULL is returned, + * then proc_offset is not set. The returned string is allocated using + * malloc and must be freed by the caller. + */ +char* backtrace_get_proc_name( + const backtrace_t* backtrace, uintptr_t pc, uintptr_t* proc_offset); + +/* Loads memory map from /proc/<tid>/maps. If tid < 0, then load the memory + * map for the current process. + */ +backtrace_map_info_t* backtrace_create_map_info_list(pid_t tid); + +/* Frees memory associated with the map list. */ +void backtrace_destroy_map_info_list(backtrace_map_info_t* map_info_list); + +/* Finds the memory map that contains the specified pc. */ +const backtrace_map_info_t* backtrace_find_map_info( + const backtrace_map_info_t* map_info_list, uintptr_t pc); + +/* Create a formatted line of backtrace information for a single frame. */ +void backtrace_format_frame_data( + const backtrace_frame_data_t* frame, size_t frame_num, char *buf, size_t buf_size); + +#ifdef __cplusplus +} +#endif + +#endif /* _BACKTRACE_H */ diff --git a/libbacktrace/Android.mk b/libbacktrace/Android.mk new file mode 100644 index 0000000..f7b084d --- /dev/null +++ b/libbacktrace/Android.mk @@ -0,0 +1,176 @@ +LOCAL_PATH:= $(call my-dir) + +#---------------------------------------------------------------------------- +# The libbacktrace library using libunwind +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + unwind.c \ + unwind_remote.c \ + unwind_local.c \ + common.c \ + demangle.c \ + map_info.c \ + +LOCAL_CFLAGS := \ + -Wall \ + -Wno-unused-parameter \ + -Werror \ + -std=gnu99 \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := \ + liblog \ + libunwind \ + libunwind-ptrace \ + libgccdemangle \ + +LOCAL_C_INCLUDES := \ + external/libunwind/include \ + +# The libunwind code is not in the tree yet, so don't build this library yet. +#include $(BUILD_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# The libbacktrace library using libcorkscrew +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + corkscrew.c \ + common.c \ + demangle.c \ + map_info.c \ + +LOCAL_CFLAGS := \ + -Wall \ + -Wno-unused-parameter \ + -Werror \ + -std=gnu99 \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := \ + libcorkscrew \ + libdl \ + libgccdemangle \ + liblog \ + +include $(BUILD_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# The host libbacktrace library using libcorkscrew +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_SRC_FILES += \ + corkscrew.c \ + common.c \ + demangle.c \ + map_info.c \ + +LOCAL_CFLAGS += \ + -Wall \ + -Wno-unused-parameter \ + -Werror \ + -std=gnu99 \ + +LOCAL_SHARED_LIBRARIES := \ + liblog \ + libcorkscrew \ + libgccdemangle \ + liblog \ + +LOCAL_LDLIBS += \ + -ldl \ + -lrt \ + +LOCAL_MODULE := libbacktrace +LOCAL_MODULE_TAGS := optional + +include $(BUILD_HOST_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# libbacktrace test library, all optimizations turned off +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := libbacktrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_testlib.c + +LOCAL_CFLAGS += \ + -std=gnu99 \ + -O0 \ + +include $(BUILD_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# libbacktrace test executable +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := backtrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_test.c \ + +LOCAL_CFLAGS += \ + -std=gnu99 \ + +LOCAL_SHARED_LIBRARIES := \ + libbacktrace_test \ + libbacktrace \ + +include $(BUILD_EXECUTABLE) + +#---------------------------------------------------------------------------- +# Only linux-x86 host versions of libbacktrace supported. +#---------------------------------------------------------------------------- +ifeq ($(HOST_OS)-$(HOST_ARCH),linux-x86) + +#---------------------------------------------------------------------------- +# libbacktrace host test library, all optimizations turned off +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := libbacktrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_testlib.c + +LOCAL_CFLAGS += \ + -std=gnu99 \ + -O0 \ + +include $(BUILD_HOST_SHARED_LIBRARY) + +#---------------------------------------------------------------------------- +# libbacktrace host test executable +#---------------------------------------------------------------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE := backtrace_test +LOCAL_MODULE_FLAGS := debug + +LOCAL_SRC_FILES := \ + backtrace_test.c \ + +LOCAL_CFLAGS += \ + -std=gnu99 \ + +LOCAL_SHARED_LIBRARIES := \ + libbacktrace_test \ + libbacktrace \ + +include $(BUILD_HOST_EXECUTABLE) + +endif # HOST_OS-HOST_ARCH == linux-x86 diff --git a/libbacktrace/backtrace_test.c b/libbacktrace/backtrace_test.c new file mode 100644 index 0000000..34d3519 --- /dev/null +++ b/libbacktrace/backtrace_test.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <sys/types.h> +#include <signal.h> +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <unistd.h> +#include <inttypes.h> + +#include <backtrace/backtrace.h> + +#define FINISH(pid) dump_frames(&backtrace); if (pid < 0) exit(1); else return false; + +// Prototypes for functions in the test library. +int test_level_one(int, int, int, int, bool (*)(pid_t)); + +int test_recursive_call(int, bool (*)(pid_t)); + +void dump_frames(const backtrace_t* backtrace) { + for (size_t i = 0; i < backtrace->num_frames; i++) { + printf("%zu ", i); + if (backtrace->frames[i].map_name) { + printf("%s", backtrace->frames[i].map_name); + } else { + printf("<unknown>"); + } + if (backtrace->frames[i].proc_name) { + printf(" %s", backtrace->frames[i].proc_name); + if (backtrace->frames[i].proc_offset) { + printf("+%" PRIuPTR, backtrace->frames[i].proc_offset); + } + } + printf("\n"); + } +} + +bool check_frame(const backtrace_t* backtrace, size_t frame_num, + const char* expected_name) { + if (backtrace->frames[frame_num].proc_name == NULL) { + printf(" Frame %zu function name expected %s, real value is NULL.\n", + frame_num, expected_name); + return false; + } + if (strcmp(backtrace->frames[frame_num].proc_name, expected_name) != 0) { + printf(" Frame %zu function name expected %s, real value is %s.\n", + frame_num, expected_name, backtrace->frames[frame_num].proc_name); + return false; + } + return true; +} + +bool verify_level_backtrace(pid_t pid) { + const char* test_type; + if (pid < 0) { + test_type = "current"; + } else { + test_type = "running"; + } + + backtrace_t backtrace; + if (!backtrace_get_data(&backtrace, pid)) { + printf(" backtrace_get_data failed on %s process.\n", test_type); + FINISH(pid); + } + + if (backtrace.num_frames == 0) { + printf(" backtrace_get_data returned no frames for %s process.\n", + test_type); + FINISH(pid); + } + + // Look through the frames starting at the highest to find the + // frame we want. + size_t frame_num = 0; + for (size_t i = backtrace.num_frames-1; i > 2; i--) { + if (backtrace.frames[i].proc_name != NULL && + strcmp(backtrace.frames[i].proc_name, "test_level_one") == 0) { + frame_num = i; + break; + } + } + if (!frame_num) { + printf(" backtrace_get_data did not include the test_level_one frame.\n"); + FINISH(pid); + } + + if (!check_frame(&backtrace, frame_num, "test_level_one")) { + FINISH(pid); + } + if (!check_frame(&backtrace, frame_num-1, "test_level_two")) { + FINISH(pid); + } + if (!check_frame(&backtrace, frame_num-2, "test_level_three")) { + FINISH(pid); + } + if (!check_frame(&backtrace, frame_num-3, "test_level_four")) { + FINISH(pid); + } + backtrace_free_data(&backtrace); + + return true; +} + +bool verify_max_backtrace(pid_t pid) { + const char* test_type; + if (pid < 0) { + test_type = "current"; + } else { + test_type = "running"; + } + + backtrace_t backtrace; + if (!backtrace_get_data(&backtrace, pid)) { + printf(" backtrace_get_data failed on %s process.\n", test_type); + FINISH(pid); + } + + if (backtrace.num_frames != MAX_BACKTRACE_FRAMES) { + printf(" backtrace_get_data %s process max frame check failed:\n", + test_type); + printf(" Expected num frames to be %zu, found %zu\n", + MAX_BACKTRACE_FRAMES, backtrace.num_frames); + FINISH(pid); + } + backtrace_free_data(&backtrace); + + return true; +} + +void verify_proc_test(pid_t pid, bool (*verify_func)(pid_t)) { + printf(" Waiting 5 seconds for process to get to infinite loop.\n"); + sleep(5); + if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) { + printf("Failed to attach to pid %d\n", pid); + kill(pid, SIGKILL); + exit(1); + } + bool pass = verify_func(pid); + if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) { + printf("Failed to detach from pid %d\n", pid); + kill(pid, SIGKILL); + exit(1); + } + + kill(pid, SIGKILL); + int status; + if (waitpid(pid, &status, 0) != pid) { + printf("Forked process did not terminate properly.\n"); + exit(1); + } + + if (!pass) { + exit(1); + } +} + +int main() { + printf("Running level test on current process...\n"); + int value = test_level_one(1, 2, 3, 4, verify_level_backtrace); + if (value == 0) { + printf("This should never happen.\n"); + exit(1); + } + printf(" Passed.\n"); + + printf("Running max level test on current process...\n"); + value = test_recursive_call(MAX_BACKTRACE_FRAMES+10, verify_max_backtrace); + if (value == 0) { + printf("This should never happen.\n"); + exit(1); + } + printf(" Passed.\n"); + + printf("Running level test on process...\n"); + pid_t pid; + if ((pid = fork()) == 0) { + value = test_level_one(1, 2, 3, 4, NULL); + if (value == 0) { + printf("This should never happen.\n"); + } + exit(1); + } + verify_proc_test(pid, verify_level_backtrace); + printf(" Passed.\n"); + + printf("Running max frame test on process...\n"); + if ((pid = fork()) == 0) { + value = test_recursive_call(MAX_BACKTRACE_FRAMES+10, NULL); + if (value == 0) { + printf("This should never happen.\n"); + } + exit(1); + } + verify_proc_test(pid, verify_max_backtrace); + printf(" Passed.\n"); + + printf("All tests passed.\n"); + return 0; +} diff --git a/libbacktrace/backtrace_testlib.c b/libbacktrace/backtrace_testlib.c new file mode 100644 index 0000000..9400549 --- /dev/null +++ b/libbacktrace/backtrace_testlib.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdbool.h> +#include <unistd.h> + +int test_level_four(int one, int two, int three, int four, + bool (*callback_func)(pid_t)) { + if (callback_func != NULL) { + callback_func(-1); + } else { + while (1) { + } + } + return one + two + three + four; +} + +int test_level_three(int one, int two, int three, int four, + bool (*callback_func)(pid_t)) { + return test_level_four(one+3, two+6, three+9, four+12, callback_func) + 3; +} + +int test_level_two(int one, int two, int three, int four, + bool (*callback_func)(pid_t)) { + return test_level_three(one+2, two+4, three+6, four+8, callback_func) + 2; +} + +int test_level_one(int one, int two, int three, int four, + bool (*callback_func)(pid_t)) { + return test_level_two(one+1, two+2, three+3, four+4, callback_func) + 1; +} + +int test_recursive_call(int level, bool (*callback_func)(pid_t)) { + if (level > 0) { + return test_recursive_call(level - 1, callback_func) + level; + } else if (callback_func != NULL) { + callback_func(-1); + } else { + while (1) { + } + } + return 0; +} diff --git a/libbacktrace/common.c b/libbacktrace/common.c new file mode 100644 index 0000000..20786f4 --- /dev/null +++ b/libbacktrace/common.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "libbacktrace" + +#include <errno.h> +#include <stdlib.h> +#include <sys/ptrace.h> +#include <inttypes.h> + +#include <cutils/log.h> +#include <backtrace/backtrace.h> + +#include "common.h" + +bool backtrace_read_word(const backtrace_t* backtrace, uintptr_t ptr, + uint32_t* out_value) { + if (ptr & 3) { + ALOGW("backtrace_read_word: invalid pointer %p", (void*)ptr); + *out_value = (uint32_t)-1; + return false; + } + + // Check if reading from the current process, or a different process. + if (backtrace->tid < 0) { + const backtrace_map_info_t* map_info = backtrace_find_map_info(backtrace->map_info_list, ptr); + if (map_info && map_info->is_readable) { + *out_value = *(uint32_t*)ptr; + return true; + } else { + ALOGW("backtrace_read_word: pointer %p not in a readbale map", (void*)ptr); + *out_value = (uint32_t)-1; + return false; + } + } else { +#if defined(__APPLE__) + ALOGW("read_word: MacOS does not support reading from another pid.\n"); + return false; +#else + // ptrace() returns -1 and sets errno when the operation fails. + // To disambiguate -1 from a valid result, we clear errno beforehand. + errno = 0; + *out_value = ptrace(PTRACE_PEEKTEXT, backtrace->tid, (void*)ptr, NULL); + if (*out_value == (uint32_t)-1 && errno) { + ALOGW("try_get_word: invalid pointer 0x%08x reading from tid %d, " + "ptrace() errno=%d", ptr, backtrace->tid, errno); + return false; + } + return true; + } +#endif +} + +const char *backtrace_get_map_info( + const backtrace_t* backtrace, uintptr_t pc, uintptr_t* start_pc) { + const backtrace_map_info_t* map_info = backtrace_find_map_info(backtrace->map_info_list, pc); + if (map_info) { + if (start_pc) { + *start_pc = map_info->start; + } + return map_info->name; + } + return NULL; +} + +void backtrace_format_frame_data( + const backtrace_frame_data_t* frame, size_t frame_num, char *buf, size_t buf_size) { + uintptr_t relative_pc; + const char* map_name; + if (frame->map_name) { + map_name = frame->map_name; + } else { + map_name = "<unknown>"; + } + if (frame->map_offset) { + relative_pc = frame->map_offset; + } else { + relative_pc = frame->pc; + } + if (frame->proc_name && frame->proc_offset) { + snprintf(buf, buf_size, "#%02zu pc %0*" PRIxPTR " %s (%s+%" PRIuPTR ")", + frame_num, (int)sizeof(uintptr_t)*2, relative_pc, map_name, + frame->proc_name, frame->proc_offset); + } else if (frame->proc_name) { + snprintf(buf, buf_size, "#%02zu pc %0*" PRIxPTR " %s (%s)", frame_num, + (int)sizeof(uintptr_t)*2, relative_pc, map_name, frame->proc_name); + } else { + snprintf(buf, buf_size, "#%02zu pc %0*" PRIxPTR " %s", frame_num, + (int)sizeof(uintptr_t)*2, relative_pc, map_name); + } +} + +void free_frame_data(backtrace_t* backtrace) { + for (size_t i = 0; i < backtrace->num_frames; i++) { + if (backtrace->frames[i].proc_name) { + free(backtrace->frames[i].proc_name); + } + } + backtrace->num_frames = 0; +} diff --git a/libbacktrace/common.h b/libbacktrace/common.h new file mode 100644 index 0000000..9eef964 --- /dev/null +++ b/libbacktrace/common.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include <backtrace/backtrace.h> + +/* Common routine to free any data allocated to store frame information. */ +void free_frame_data(backtrace_t* backtrace); + +#endif /* _COMMON_H */ diff --git a/libbacktrace/corkscrew.c b/libbacktrace/corkscrew.c new file mode 100644 index 0000000..899409a --- /dev/null +++ b/libbacktrace/corkscrew.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "libbacktrace" + +#include <string.h> + +#include <cutils/log.h> +#include <backtrace/backtrace.h> + +#include <corkscrew/backtrace.h> + +#define __USE_GNU +#include <dlfcn.h> + +#include "common.h" +#include "demangle.h" + +bool backtrace_get_data(backtrace_t* backtrace, pid_t tid) { + backtrace->num_frames = 0; + backtrace->tid = tid; + backtrace->private_data = NULL; + backtrace->map_info_list = backtrace_create_map_info_list(tid); + + backtrace_frame_t frames[MAX_BACKTRACE_FRAMES]; + ssize_t num_frames; + if (tid < 0) { + // Get data for the current thread. + num_frames = unwind_backtrace(frames, 0, MAX_BACKTRACE_FRAMES); + } else { + // Get data for a different thread. + ptrace_context_t* ptrace_context = load_ptrace_context(tid); + backtrace->private_data = ptrace_context; + + num_frames = unwind_backtrace_ptrace( + tid, ptrace_context, frames, 0, MAX_BACKTRACE_FRAMES); + } + if (num_frames < 0) { + ALOGW("backtrace_get_data: unwind_backtrace_ptrace failed %d\n", + num_frames); + backtrace_free_data(backtrace); + return false; + } + + backtrace->num_frames = num_frames; + backtrace_frame_data_t* frame; + uintptr_t map_start; + for (size_t i = 0; i < backtrace->num_frames; i++) { + frame = &backtrace->frames[i]; + frame->pc = frames[i].absolute_pc; + frame->sp = frames[i].stack_top; + frame->stack_size = frames[i].stack_size; + + frame->map_offset = 0; + frame->map_name = backtrace_get_map_info(backtrace, frame->pc, &map_start); + if (frame->map_name) { + frame->map_offset = frame->pc - map_start; + } + + frame->proc_offset = 0; + frame->proc_name = backtrace_get_proc_name(backtrace, frame->pc, &frame->proc_offset); + } + + return true; +} + +void backtrace_free_data(backtrace_t* backtrace) { + free_frame_data(backtrace); + + if (backtrace->map_info_list) { + backtrace_destroy_map_info_list(backtrace->map_info_list); + backtrace->map_info_list = NULL; + } + + if (backtrace->private_data) { + ptrace_context_t* ptrace_context = (ptrace_context_t*)backtrace->private_data; + free_ptrace_context(ptrace_context); + backtrace->private_data = NULL; + } +} + +char* backtrace_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset) { + const char* symbol_name = NULL; + *offset = 0; + if (backtrace->tid < 0) { + // Get information about the current thread. + Dl_info info; + const backtrace_map_info_t* map_info; + map_info = backtrace_find_map_info(backtrace->map_info_list, pc); + if (map_info && dladdr((const void*)pc, &info) && info.dli_sname) { + *offset = pc - map_info->start - (uintptr_t)info.dli_saddr + (uintptr_t)info.dli_fbase; + symbol_name = info.dli_sname; + } + } else { + // Get information about a different thread. + ptrace_context_t* ptrace_context = (ptrace_context_t*)backtrace->private_data; + const map_info_t* map_info; + const symbol_t* symbol; + find_symbol_ptrace(ptrace_context, pc, &map_info, &symbol); + if (symbol) { + if (map_info) { + *offset = pc - map_info->start - symbol->start; + } + symbol_name = symbol->name; + } + } + + char* name = NULL; + if (symbol_name) { + name = demangle_symbol_name(symbol_name); + if (!name) { + name = strdup(symbol_name); + } + } + return name; +} diff --git a/libbacktrace/demangle.c b/libbacktrace/demangle.c new file mode 100644 index 0000000..de9a460 --- /dev/null +++ b/libbacktrace/demangle.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/types.h> + +#include "demangle.h" + +extern char* __cxa_demangle (const char* mangled, char* buf, size_t* len, + int* status); + +char* demangle_symbol_name(const char* name) { +#if defined(__APPLE__) + // Mac OS' __cxa_demangle demangles "f" as "float"; last tested on 10.7. + if (name != NULL && name[0] != '_') { + return NULL; + } +#endif + // __cxa_demangle handles NULL by returning NULL + return __cxa_demangle(name, 0, 0, 0); +} diff --git a/libbacktrace/demangle.h b/libbacktrace/demangle.h new file mode 100644 index 0000000..a5318ac --- /dev/null +++ b/libbacktrace/demangle.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _DEMANGLE_H +#define _DEMANGLE_H + +/* Called to demangle a symbol name to be printed. Returns an allocated + * string that must be freed by the caller. + */ +char* demangle_symbol_name(const char* name); + +#endif /* _DEMANGLE_H */ diff --git a/libbacktrace/map_info.c b/libbacktrace/map_info.c new file mode 100644 index 0000000..9cc6e01 --- /dev/null +++ b/libbacktrace/map_info.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <pthread.h> +#include <unistd.h> +#include <cutils/log.h> +#include <sys/time.h> + +#include <backtrace/backtrace.h> + +#if defined(__APPLE__) + +// Mac OS vmmap(1) output: +// __TEXT 0009f000-000a1000 [ 8K 8K] r-x/rwx SM=COW /Volumes/android/dalvik-dev/out/host/darwin-x86/bin/libcorkscrew_test\n +// 012345678901234567890123456789012345678901234567890123456789 +// 0 1 2 3 4 5 +static backtrace_map_info_t* parse_vmmap_line(const char* line) { + unsigned long int start; + unsigned long int end; + char permissions[4]; + int name_pos; + if (sscanf(line, "%*21c %lx-%lx [%*13c] %3c/%*3c SM=%*3c %n", + &start, &end, permissions, &name_pos) != 3) { + return NULL; + } + + const char* name = line + name_pos; + size_t name_len = strlen(name); + + backtrace_map_info_t* mi = calloc(1, sizeof(backtrace_map_info_t) + name_len); + if (mi != NULL) { + mi->start = start; + mi->end = end; + mi->is_readable = permissions[0] == 'r'; + mi->is_writable = permissions[1] == 'w'; + mi->is_executable = permissions[2] == 'x'; + memcpy(mi->name, name, name_len); + mi->name[name_len - 1] = '\0'; + ALOGV("Parsed map: start=0x%08x, end=0x%08x, " + "is_readable=%d, is_writable=%d is_executable=%d, name=%s", + mi->start, mi->end, + mi->is_readable, mi->is_writable, mi->is_executable, mi->name); + } + return mi; +} + +backtrace_map_info_t* backtrace_create_map_info_list(pid_t pid) { + char cmd[1024]; + if (pid < 0) { + pid = getpid(); + } + snprintf(cmd, sizeof(cmd), "vmmap -w -resident -submap -allSplitLibs -interleaved %d", pid); + FILE* fp = popen(cmd, "r"); + if (fp == NULL) { + return NULL; + } + + char line[1024]; + backtrace_map_info_t* milist = NULL; + while (fgets(line, sizeof(line), fp) != NULL) { + backtrace_map_info_t* mi = parse_vmmap_line(line); + if (mi != NULL) { + mi->next = milist; + milist = mi; + } + } + pclose(fp); + return milist; +} + +#else + +// Linux /proc/<pid>/maps lines: +// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so\n +// 012345678901234567890123456789012345678901234567890123456789 +// 0 1 2 3 4 5 +static backtrace_map_info_t* parse_maps_line(const char* line) +{ + unsigned long int start; + unsigned long int end; + char permissions[5]; + int name_pos; + if (sscanf(line, "%lx-%lx %4s %*x %*x:%*x %*d%n", &start, &end, + permissions, &name_pos) != 3) { + return NULL; + } + + while (isspace(line[name_pos])) { + name_pos += 1; + } + const char* name = line + name_pos; + size_t name_len = strlen(name); + if (name_len && name[name_len - 1] == '\n') { + name_len -= 1; + } + + backtrace_map_info_t* mi = calloc(1, sizeof(backtrace_map_info_t) + name_len + 1); + if (mi) { + mi->start = start; + mi->end = end; + mi->is_readable = strlen(permissions) == 4 && permissions[0] == 'r'; + mi->is_writable = strlen(permissions) == 4 && permissions[1] == 'w'; + mi->is_executable = strlen(permissions) == 4 && permissions[2] == 'x'; + memcpy(mi->name, name, name_len); + mi->name[name_len] = '\0'; + ALOGV("Parsed map: start=0x%08x, end=0x%08x, " + "is_readable=%d, is_writable=%d, is_executable=%d, name=%s", + mi->start, mi->end, + mi->is_readable, mi->is_writable, mi->is_executable, mi->name); + } + return mi; +} + +backtrace_map_info_t* backtrace_create_map_info_list(pid_t tid) { + char path[PATH_MAX]; + char line[1024]; + FILE* fp; + backtrace_map_info_t* milist = NULL; + + if (tid < 0) { + tid = getpid(); + } + snprintf(path, PATH_MAX, "/proc/%d/maps", tid); + fp = fopen(path, "r"); + if (fp) { + while(fgets(line, sizeof(line), fp)) { + backtrace_map_info_t* mi = parse_maps_line(line); + if (mi) { + mi->next = milist; + milist = mi; + } + } + fclose(fp); + } + return milist; +} + +#endif + +void backtrace_destroy_map_info_list(backtrace_map_info_t* milist) { + while (milist) { + backtrace_map_info_t* next = milist->next; + free(milist); + milist = next; + } +} + +const backtrace_map_info_t* backtrace_find_map_info( + const backtrace_map_info_t* milist, uintptr_t addr) { + const backtrace_map_info_t* mi = milist; + while (mi && !(addr >= mi->start && addr < mi->end)) { + mi = mi->next; + } + return mi; +} diff --git a/libbacktrace/stubs.c b/libbacktrace/stubs.c new file mode 100644 index 0000000..1741601 --- /dev/null +++ b/libbacktrace/stubs.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "libbacktrace" + +#include <cutils/log.h> +#include <backtrace/backtrace.h> + +bool backtrace_get_data(backtrace_t* backtrace, pid_t tid) { + ALOGW("backtrace_get_data: unsupported architecture.\n"); + return true; +} + +void backtrace_free_data(backtrace_t* backtrace) { + ALOGW("backtrace_free_data: unsupported architecture.\n"); +} + +bool backtrace_read_word(const backtrace_t* backtrace, uintptr_t ptr, + uint32_t* out_value) { + ALOGW("backtrace_read_word: unsupported architecture.\n"); + return false; +} + +const char *backtrace_get_map_info(const backtrace_t* backtrace, + uintptr_t pc, uintptr_t* start_pc) { + ALOGW("backtrace_get_map_info: unsupported architecture.\n"); + return NULL; +} + +char* backtrace_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset) { + ALOGW("backtrace_get_proc_name: unsupported architecture.\n"); + return NULL; +} + +void backtrace_format_frame_data( + const backtrace_frame_data_t* frame, size_t frame_num, char *buf, size_t buf_size) { + ALOGW("backtrace_format_frame_data: unsupported architecture.\n"); + buf[0] = '\0'; +} diff --git a/libbacktrace/unwind.c b/libbacktrace/unwind.c new file mode 100644 index 0000000..f75e518 --- /dev/null +++ b/libbacktrace/unwind.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <backtrace/backtrace.h> + +#include "common.h" +#include "unwind.h" + +bool backtrace_get_data(backtrace_t* backtrace, pid_t tid) { + backtrace->num_frames = 0; + backtrace->tid = tid; + + backtrace->map_info_list = backtrace_create_map_info_list(tid); + if (tid < 0) { + return local_get_data(backtrace); + } else { + return remote_get_data(backtrace); + } +} + +/* Free any memory related to the frame data. */ +void backtrace_free_data(backtrace_t* backtrace) { + free_frame_data(backtrace); + + if (backtrace->map_info_list) { + backtrace_destroy_map_info_list(backtrace->map_info_list); + backtrace->map_info_list = NULL; + } + + if (backtrace->tid < 0) { + local_free_data(backtrace); + } else { + remote_free_data(backtrace); + } +} + +char* backtrace_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset) { + if (backtrace->tid < 0) { + return local_get_proc_name(backtrace, pc, offset); + } else { + return remote_get_proc_name(backtrace, pc, offset); + } +} diff --git a/libbacktrace/unwind.h b/libbacktrace/unwind.h new file mode 100644 index 0000000..9ba96a4 --- /dev/null +++ b/libbacktrace/unwind.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UNWIND_H +#define _UNWIND_H + +bool local_get_data(backtrace_t* backtrace); + +void local_free_data(backtrace_t* backtrace); + +char* local_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset); + +bool remote_get_data(backtrace_t* backtrace); + +void remote_free_data(backtrace_t* backtrace); + +char* remote_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset); + +#endif /* _UNWIND_H */ diff --git a/libbacktrace/unwind_local.c b/libbacktrace/unwind_local.c new file mode 100644 index 0000000..e0b72d8 --- /dev/null +++ b/libbacktrace/unwind_local.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "libbacktrace" + +#include <string.h> + +#include <cutils/log.h> +#include <backtrace/backtrace.h> + +#define UNW_LOCAL_ONLY +#include <libunwind.h> +#include <libunwind-ptrace.h> + +#include "common.h" +#include "demangle.h" + +static bool local_get_frames(backtrace_t* backtrace) { + unw_context_t* context = (unw_context_t*)backtrace->private_data; + unw_cursor_t cursor; + + int ret = unw_getcontext(context); + if (ret < 0) { + ALOGW("local_get_frames: unw_getcontext failed %d\n", ret); + return false; + } + + ret = unw_init_local(&cursor, context); + if (ret < 0) { + ALOGW("local_get_frames: unw_init_local failed %d\n", ret); + return false; + } + + backtrace_frame_data_t* frame; + bool returnValue = true; + backtrace->num_frames = 0; + uintptr_t map_start; + do { + frame = &backtrace->frames[backtrace->num_frames]; + frame->stack_size = 0; + frame->map_name = NULL; + frame->map_offset = 0; + frame->proc_name = NULL; + frame->proc_offset = 0; + + ret = unw_get_reg(&cursor, UNW_REG_IP, &frame->pc); + if (ret < 0) { + ALOGW("get_frames: Failed to read IP %d\n", ret); + returnValue = false; + break; + } + ret = unw_get_reg(&cursor, UNW_REG_SP, &frame->sp); + if (ret < 0) { + ALOGW("get_frames: Failed to read IP %d\n", ret); + returnValue = false; + break; + } + if (backtrace->num_frames) { + backtrace_frame_data_t* prev = &backtrace->frames[backtrace->num_frames-1]; + prev->stack_size = frame->sp - prev->sp; + } + + frame->proc_name = backtrace_get_proc_name(backtrace, frame->pc, &frame->proc_offset); + + frame->map_name = backtrace_get_map_info(backtrace, frame->pc, &map_start); + if (frame->map_name) { + frame->map_offset = frame->pc - map_start; + } + + backtrace->num_frames++; + ret = unw_step (&cursor); + } while (ret > 0 && backtrace->num_frames < MAX_BACKTRACE_FRAMES); + + return returnValue; +} + +bool local_get_data(backtrace_t* backtrace) { + unw_context_t *context = (unw_context_t*)malloc(sizeof(unw_context_t)); + backtrace->private_data = context; + + if (!local_get_frames(backtrace)) { + backtrace_free_data(backtrace); + return false; + } + + return true; +} + +void local_free_data(backtrace_t* backtrace) { + if (backtrace->private_data) { + free(backtrace->private_data); + backtrace->private_data = NULL; + } +} + +char* local_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset) { + unw_context_t* context = (unw_context_t*)backtrace->private_data; + char buf[512]; + + if (unw_get_proc_name_by_ip(unw_local_addr_space, pc, buf, sizeof(buf), + offset, context) >= 0 && buf[0] != '\0') { + char* symbol = demangle_symbol_name(buf); + if (!symbol) { + symbol = strdup(buf); + } + return symbol; + } + return NULL; +} diff --git a/libbacktrace/unwind_remote.c b/libbacktrace/unwind_remote.c new file mode 100644 index 0000000..ebbde2e --- /dev/null +++ b/libbacktrace/unwind_remote.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "libbacktrace" + +#include <sys/ptrace.h> +#include <string.h> + +#include <cutils/log.h> +#include <backtrace/backtrace.h> + +#include <libunwind.h> +#include <libunwind-ptrace.h> + +#include "common.h" +#include "demangle.h" + +typedef struct { + unw_addr_space_t addr_space; + struct UPT_info* upt_info; +} backtrace_private_t; + +static bool remote_get_frames(backtrace_t* backtrace) { + backtrace_private_t* data = (backtrace_private_t*)backtrace->private_data; + unw_cursor_t cursor; + int ret = unw_init_remote(&cursor, data->addr_space, data->upt_info); + if (ret < 0) { + ALOGW("remote_get_frames: unw_init_remote failed %d\n", ret); + return false; + } + + backtrace_frame_data_t* frame; + bool returnValue = true; + backtrace->num_frames = 0; + uintptr_t map_start; + do { + frame = &backtrace->frames[backtrace->num_frames]; + frame->stack_size = 0; + frame->map_name = NULL; + frame->map_offset = 0; + frame->proc_name = NULL; + frame->proc_offset = 0; + + ret = unw_get_reg(&cursor, UNW_REG_IP, &frame->pc); + if (ret < 0) { + ALOGW("remote_get_frames: Failed to read IP %d\n", ret); + returnValue = false; + break; + } + ret = unw_get_reg(&cursor, UNW_REG_SP, &frame->sp); + if (ret < 0) { + ALOGW("remote_get_frames: Failed to read SP %d\n", ret); + returnValue = false; + break; + } + if (backtrace->num_frames) { + backtrace_frame_data_t* prev = &backtrace->frames[backtrace->num_frames-1]; + prev->stack_size = frame->sp - prev->sp; + } + + frame->proc_name = backtrace_get_proc_name(backtrace, frame->pc, &frame->proc_offset); + + frame->map_name = backtrace_get_map_info(backtrace, frame->pc, &map_start); + if (frame->map_name) { + frame->map_offset = frame->pc - map_start; + } + + backtrace->num_frames++; + ret = unw_step (&cursor); + } while (ret > 0 && backtrace->num_frames < MAX_BACKTRACE_FRAMES); + + return returnValue; +} + +bool remote_get_data(backtrace_t* backtrace) { + backtrace_private_t* data = (backtrace_private_t*)malloc(sizeof(backtrace_private_t)); + if (!data) { + ALOGW("remote_get_data: Failed to allocate memory.\n"); + backtrace_free_data(backtrace); + return false; + } + data->addr_space = NULL; + data->upt_info = NULL; + + backtrace->private_data = data; + data->addr_space = unw_create_addr_space(&_UPT_accessors, 0); + if (!data->addr_space) { + ALOGW("remote_get_data: Failed to create unw address space.\n"); + backtrace_free_data(backtrace); + return false; + } + + data->upt_info = _UPT_create(backtrace->tid); + if (!data->upt_info) { + ALOGW("remote_get_data: Failed to create upt info.\n"); + backtrace_free_data(backtrace); + return false; + } + + if (!remote_get_frames(backtrace)) { + backtrace_free_data(backtrace); + return false; + } + + return true; +} + +void remote_free_data(backtrace_t* backtrace) { + if (backtrace->private_data) { + backtrace_private_t* data = (backtrace_private_t*)backtrace->private_data; + if (data->upt_info) { + _UPT_destroy(data->upt_info); + data->upt_info = NULL; + } + if (data->addr_space) { + unw_destroy_addr_space(data->addr_space); + } + + free(backtrace->private_data); + backtrace->private_data = NULL; + } +} + +char* remote_get_proc_name(const backtrace_t* backtrace, uintptr_t pc, + uintptr_t* offset) { + backtrace_private_t* data = (backtrace_private_t*)backtrace->private_data; + char buf[512]; + + if (unw_get_proc_name_by_ip(data->addr_space, pc, buf, sizeof(buf), offset, + data->upt_info) >= 0 && buf[0] != '\0') { + char* symbol = demangle_symbol_name(buf); + if (!symbol) { + symbol = strdup(buf); + } + return symbol; + } + return NULL; +} |