Add a new library for collecting stack traces.

Supports collecting the stack trace of the current thread,
another thread in the same process, or a thread in a
different process (using ptrace).

Change-Id: Ica2594e4436edde4ceb7bcc3d78e6c31a7902cbf
This commit is contained in:
Jeff Brown 2011-10-19 20:35:35 -07:00
parent 08dedcfd5c
commit 501edd29b8
19 changed files with 2026 additions and 0 deletions

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2011 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.
*/
/* A stack unwinder. */
#ifndef _CORKSCREW_BACKTRACE_H
#define _CORKSCREW_BACKTRACE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/types.h>
#include <corkscrew/ptrace.h>
#include <corkscrew/map_info.h>
#include <corkscrew/symbol_table.h>
/*
* Describes a single frame of a backtrace.
*/
typedef struct {
uintptr_t absolute_pc; /* absolute PC offset */
uintptr_t stack_top; /* top of stack for this frame */
size_t stack_size; /* size of this stack frame */
} backtrace_frame_t;
/*
* Describes the symbols associated with a backtrace frame.
*/
typedef struct {
uintptr_t relative_pc; /* relative PC offset from the start of the library,
or the absolute PC if the library is unknown */
const map_info_t* map_info; /* memory map of the library, or NULL if unknown */
const char* name; /* symbol name, or NULL if unknown */
char* demangled_name; /* demangled symbol name, or NULL if unknown */
} backtrace_symbol_t;
/*
* Unwinds the call stack for the current thread of execution.
* Populates the backtrace array with the program counters from the call stack.
* Returns the number of frames collected, or -1 if an error occurred.
*/
ssize_t unwind_backtrace(backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
/*
* Unwinds the call stack for a thread within this process.
* Populates the backtrace array with the program counters from the call stack.
* Returns the number of frames collected, or -1 if an error occurred.
*
* The task is briefly suspended while the backtrace is being collected.
*/
ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth);
/*
* Unwinds the call stack of a task within a remote process using ptrace().
* Populates the backtrace array with the program counters from the call stack.
* Returns the number of frames collected, or -1 if an error occurred.
*/
ssize_t unwind_backtrace_ptrace(pid_t tid, const ptrace_context_t* context,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
/*
* Gets the symbols for each frame of a backtrace.
* The symbols array must be big enough to hold one symbol record per frame.
* The symbols must later be freed using free_backtrace_symbols.
*/
void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
backtrace_symbol_t* backtrace_symbols);
/*
* Gets the symbols for each frame of a backtrace from a remote process.
* The symbols array must be big enough to hold one symbol record per frame.
* The symbols must later be freed using free_backtrace_symbols.
*/
void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
const backtrace_frame_t* backtrace, size_t frames,
backtrace_symbol_t* backtrace_symbols);
/*
* Frees the storage associated with backtrace symbols.
*/
void free_backtrace_symbols(backtrace_symbol_t* backtrace_symbols, size_t frames);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_BACKTRACE_H

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2011 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.
*/
/* C++ symbol name demangling. */
#ifndef _CORKSCREW_DEMANGLE_H
#define _CORKSCREW_DEMANGLE_H
#include <sys/types.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Demangles a C++ symbol name.
* If name is NULL or if the name cannot be demangled, returns NULL.
* Otherwise, returns a newly allocated string that contains the demangled name.
*
* The caller must free the returned string using free().
*/
char* demangle_symbol_name(const char* name);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_DEMANGLE_H

View file

@ -0,0 +1,54 @@
/*
* Copyright (C) 2011 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.
*/
/* Process memory map. */
#ifndef _CORKSCREW_MAP_INFO_H
#define _CORKSCREW_MAP_INFO_H
#include <sys/types.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct map_info {
struct map_info* next;
uintptr_t start;
uintptr_t end;
bool is_executable;
void* data; // arbitrary data associated with the map by the user, initially NULL
char name[];
} map_info_t;
/* Loads memory map from /proc/<tid>/maps. */
map_info_t* load_map_info_list(pid_t tid);
/* Frees memory map. */
void free_map_info_list(map_info_t* milist);
/* Finds the memory map that contains the specified address. */
const map_info_t* find_map_info(const map_info_t* milist, uintptr_t addr);
/* Gets the memory map for this process. (result is cached) */
const map_info_t* my_map_info_list();
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_MAP_INFO_H

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2011 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.
*/
/* Useful ptrace() utility functions. */
#ifndef _CORKSCREW_PTRACE_H
#define _CORKSCREW_PTRACE_H
#include <corkscrew/map_info.h>
#include <corkscrew/symbol_table.h>
#include <sys/types.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Stores information about a process that is used for several different
* ptrace() based operations. */
typedef struct {
map_info_t* map_info_list;
} ptrace_context_t;
#if __i386__
/* ptrace() register context. */
typedef struct pt_regs_x86 {
uint32_t ebx;
uint32_t ecx;
uint32_t edx;
uint32_t esi;
uint32_t edi;
uint32_t ebp;
uint32_t eax;
uint32_t xds;
uint32_t xes;
uint32_t xfs;
uint32_t xgs;
uint32_t orig_eax;
uint32_t eip;
uint32_t xcs;
uint32_t eflags;
uint32_t esp;
uint32_t xss;
} pt_regs_x86_t;
#endif
/*
* Reads a word of memory safely.
* Uses ptrace() if tid >= 0, local memory otherwise.
* Returns false if the word could not be read.
*/
bool try_get_word(pid_t tid, uintptr_t ptr, uint32_t* out_value);
/*
* Loads information needed for examining a remote process using ptrace().
* The caller must already have successfully attached to the process
* using ptrace().
*
* The context can be used for any threads belonging to that process
* assuming ptrace() is attached to them before performing the actual
* unwinding. The context can continue to be used to decode backtraces
* even after ptrace() has been detached from the process.
*/
ptrace_context_t* load_ptrace_context(pid_t pid);
/*
* Frees a ptrace context.
*/
void free_ptrace_context(ptrace_context_t* context);
/*
* Finds a symbol using ptrace.
* Returns the containing map and information about the symbol, or
* NULL if one or the other is not available.
*/
void find_symbol_ptrace(const ptrace_context_t* context,
uintptr_t addr, const map_info_t** out_map_info, const symbol_t** out_symbol);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_PTRACE_H

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2011 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 _CORKSCREW_SYMBOL_TABLE_H
#define _CORKSCREW_SYMBOL_TABLE_H
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uintptr_t start;
uintptr_t end;
char* name;
} symbol_t;
typedef struct {
symbol_t* symbols;
size_t num_symbols;
} symbol_table_t;
/*
* Loads a symbol table from a given file.
* Returns NULL on error.
*/
symbol_table_t* load_symbol_table(const char* filename);
/*
* Frees a symbol table.
*/
void free_symbol_table(symbol_table_t* table);
/*
* Finds a symbol associated with an address in the symbol table.
* Returns NULL if not found.
*/
const symbol_t* find_symbol(const symbol_table_t* table, uintptr_t addr);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_SYMBOL_TABLE_H

41
libcorkscrew/Android.mk Normal file
View file

@ -0,0 +1,41 @@
# Copyright (C) 2011 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
backtrace.c \
backtrace-helper.c \
demangle.c \
map_info.c \
ptrace.c \
symbol_table.c
ifeq ($(TARGET_ARCH),arm)
LOCAL_SRC_FILES += \
arch-arm/backtrace-arm.c \
arch-arm/ptrace-arm.c
LOCAL_CFLAGS += -DCORKSCREW_HAVE_ARCH -DCORKSCREW_HAVE_LIBIBERTY
LOCAL_LDFLAGS += -liberty
endif
LOCAL_SHARED_LIBRARIES += libdl libcutils
LOCAL_CFLAGS += -std=gnu99 -Werror
LOCAL_MODULE := libcorkscrew
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)

View file

190
libcorkscrew/NOTICE Normal file
View file

@ -0,0 +1,190 @@
Copyright (c) 2011, 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.
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -0,0 +1,516 @@
/*
* Copyright (C) 2011 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.
*/
/*
* Backtracing functions for ARM.
*
* This implementation uses the exception unwinding tables provided by
* the compiler to unwind call frames. Refer to the ARM Exception Handling ABI
* documentation (EHABI) for more details about what's going on here.
*
* An ELF binary may contain an EXIDX section that provides an index to
* the exception handling table of each function, sorted by program
* counter address.
*
* When the executable is statically linked, the EXIDX section can be
* accessed by querying the values of the __exidx_start and __exidx_end
* symbols. That said, this library is currently only compiled as
* a dynamic library, so we will not trouble ourselves with statically
* linked executables any further.
*
* When the Bionic dynamic linker is used, it exports a function called
* dl_unwind_find_exidx that obtains the EXIDX section for a given
* absolute program counter address.
*
* This implementation also supports unwinding other processes via ptrace().
* In that case, the EXIDX section is found by reading the ELF section table
* structures using ptrace().
*
* Because the tables are used for exception handling, it can happen that
* a given function will not have an exception handling table. In particular,
* exceptions are assumes to only ever be thrown at call sites. Therefore,
* by definition leaf functions will not have exception handling tables.
* This may make unwinding impossible in some cases although we can still get
* some idea of the call stack by examining the PC and LR registers.
*
* As we are only interested in backtrace information, we do not need
* to perform all of the work of unwinding such as restoring register
* state and running cleanup functions. Unwinding is performed virtually on
* an abstract machine context consisting of just the ARM core registers.
* Furthermore, we do not run generic "personality functions" because
* we may not be in a position to execute arbitrary code, especially if
* we are running in a signal handler or using ptrace()!
*/
#define LOG_TAG "Corkscrew"
//#define LOG_NDEBUG 0
#include "../backtrace-arch.h"
#include "../backtrace-helper.h"
#include "../ptrace-arch.h"
#include <corkscrew/ptrace.h>
#include <stdlib.h>
#include <signal.h>
#include <stdbool.h>
#include <limits.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <sys/exec_elf.h>
#include <cutils/log.h>
/* Machine context at the time a signal was raised. */
typedef struct ucontext {
uint32_t uc_flags;
struct ucontext* uc_link;
stack_t uc_stack;
struct sigcontext {
uint32_t trap_no;
uint32_t error_code;
uint32_t oldmask;
uint32_t gregs[16];
uint32_t arm_cpsr;
uint32_t fault_address;
} uc_mcontext;
uint32_t uc_sigmask;
} ucontext_t;
/* Unwind state. */
typedef struct {
uint32_t gregs[16];
} unwind_state_t;
static const int R_SP = 13;
static const int R_LR = 14;
static const int R_PC = 15;
/* Special EXIDX value that indicates that a frame cannot be unwound. */
static const uint32_t EXIDX_CANTUNWIND = 1;
/* The function exported by the Bionic linker to find the EXIDX
* table for a given program counter address. */
extern uintptr_t dl_unwind_find_exidx(uintptr_t pc, size_t* out_exidx_size);
/* Transforms a 31-bit place-relative offset to an absolute address.
* We assume the most significant bit is clear. */
static uintptr_t prel_to_absolute(uintptr_t place, uint32_t prel_offset) {
return place + (((int32_t)(prel_offset << 1)) >> 1);
}
static uintptr_t get_exception_handler(
const ptrace_context_t* context, pid_t tid, uintptr_t pc) {
uintptr_t exidx_start;
size_t exidx_size;
if (tid < 0) {
exidx_start = dl_unwind_find_exidx(pc, &exidx_size);
} else {
const map_info_t* mi = find_map_info(context->map_info_list, pc);
if (mi && mi->data) {
const map_info_data_t* data = (const map_info_data_t*)mi->data;
exidx_start = data->exidx_start;
exidx_size = data->exidx_size;
} else {
exidx_start = 0;
exidx_size = 0;
}
}
// The PC points to the instruction following the branch.
// We want to find the exception handler entry that corresponds to the branch itself,
// so we offset the PC backwards into the previous instruction.
// ARM instructions are 4 bytes, Thumb are 2, so we just subtract two so we either
// end up in the middle (ARM) or at the beginning of the instruction (Thumb).
if (pc >= 2) {
pc -= 2;
}
uint32_t handler = 0;
if (exidx_start) {
uint32_t low = 0;
uint32_t high = exidx_size;
while (low < high) {
uint32_t index = (low + high) / 2;
uintptr_t entry = exidx_start + index * 8;
uint32_t entry_prel_pc;
if (!try_get_word(tid, entry, &entry_prel_pc)) {
break;
}
uintptr_t entry_pc = prel_to_absolute(entry, entry_prel_pc);
if (pc < entry_pc) {
high = index;
continue;
}
if (index + 1 < exidx_size) {
uintptr_t next_entry = entry + 8;
uint32_t next_entry_prel_pc;
if (!try_get_word(tid, next_entry, &next_entry_prel_pc)) {
break;
}
uintptr_t next_entry_pc = prel_to_absolute(next_entry, next_entry_prel_pc);
if (pc >= next_entry_pc) {
low = index + 1;
continue;
}
}
uintptr_t entry_handler_ptr = entry + 4;
uint32_t entry_handler;
if (!try_get_word(tid, entry_handler_ptr, &entry_handler)) {
break;
}
if (entry_handler & (1L << 31)) {
handler = entry_handler_ptr; // in-place handler data
} else if (entry_handler != EXIDX_CANTUNWIND) {
handler = prel_to_absolute(entry_handler_ptr, entry_handler);
}
break;
}
}
LOGV("get handler: pc=0x%08x, exidx_start=0x%08x, exidx_size=%d, handler=0x%08x",
pc, exidx_start, exidx_size, handler);
return handler;
}
typedef struct {
uintptr_t ptr;
uint32_t word;
} byte_stream_t;
static bool try_next_byte(pid_t tid, byte_stream_t* stream, uint8_t* out_value) {
uint8_t result;
switch (stream->ptr & 3) {
case 0:
if (!try_get_word(tid, stream->ptr, &stream->word)) {
*out_value = 0;
return false;
}
*out_value = stream->word >> 24;
break;
case 1:
*out_value = stream->word >> 16;
break;
case 2:
*out_value = stream->word >> 8;
break;
default:
*out_value = stream->word;
break;
}
LOGV("next_byte: ptr=0x%08x, value=0x%02x", stream->ptr, *out_value);
stream->ptr += 1;
return true;
}
static void set_reg(unwind_state_t* state, uint32_t reg, uint32_t value) {
LOGV("set_reg: reg=%d, value=0x%08x", reg, value);
state->gregs[reg] = value;
}
static bool try_pop_registers(pid_t tid, unwind_state_t* state, uint32_t mask) {
uint32_t sp = state->gregs[R_SP];
bool sp_updated = false;
for (int i = 0; i < 16; i++) {
if (mask & (1 << i)) {
uint32_t value;
if (!try_get_word(tid, sp, &value)) {
return false;
}
if (i == R_SP) {
sp_updated = true;
}
set_reg(state, i, value);
sp += 4;
}
}
if (!sp_updated) {
set_reg(state, R_SP, sp);
}
return true;
}
/* Executes a built-in personality routine as defined in the EHABI.
* Returns true if unwinding should continue.
*
* The data for the built-in personality routines consists of a sequence
* of unwinding instructions, followed by a sequence of scope descriptors,
* each of which has a length and offset encoded using 16-bit or 32-bit
* values.
*
* We only care about the unwinding instructions. They specify the
* operations of an abstract machine whose purpose is to transform the
* virtual register state (including the stack pointer) such that
* the call frame is unwound and the PC register points to the call site.
*/
static bool execute_personality_routine(pid_t tid, unwind_state_t* state,
byte_stream_t* stream, int pr_index) {
size_t size;
switch (pr_index) {
case 0: // Personality routine #0, short frame, descriptors have 16-bit scope.
size = 3;
break;
case 1: // Personality routine #1, long frame, descriptors have 16-bit scope.
case 2: { // Personality routine #2, long frame, descriptors have 32-bit scope.
uint8_t size_byte;
if (!try_next_byte(tid, stream, &size_byte)) {
return false;
}
size = (uint32_t)size_byte * sizeof(uint32_t) + 2;
break;
}
default: // Unknown personality routine. Stop here.
return false;
}
bool pc_was_set = false;
while (size--) {
uint8_t op;
if (!try_next_byte(tid, stream, &op)) {
return false;
}
if ((op & 0xc0) == 0x00) {
// "vsp = vsp + (xxxxxx << 2) + 4"
set_reg(state, R_SP, state->gregs[R_SP] + ((op & 0x3f) << 2) + 4);
} else if ((op & 0xc0) == 0x40) {
// "vsp = vsp - (xxxxxx << 2) - 4"
set_reg(state, R_SP, state->gregs[R_SP] - ((op & 0x3f) << 2) - 4);
} else if ((op & 0xf0) == 0x80) {
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
uint32_t mask = (((uint32_t)op & 0x0f) << 12) | ((uint32_t)op2 << 4);
if (mask) {
// "Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}"
if (!try_pop_registers(tid, state, mask)) {
return false;
}
if (mask & (1 << R_PC)) {
pc_was_set = true;
}
} else {
// "Refuse to unwind"
return false;
}
} else if ((op & 0xf0) == 0x90) {
if (op != 0x9d && op != 0x9f) {
// "Set vsp = r[nnnn]"
set_reg(state, R_SP, state->gregs[op & 0x0f]);
} else {
// "Reserved as prefix for ARM register to register moves"
// "Reserved as prefix for Intel Wireless MMX register to register moves"
return false;
}
} else if ((op & 0xf8) == 0xa0) {
// "Pop r4-r[4+nnn]"
uint32_t mask = (0x0ff0 >> (7 - (op & 0x07))) & 0x0ff0;
if (!try_pop_registers(tid, state, mask)) {
return false;
}
} else if ((op & 0xf8) == 0xa8) {
// "Pop r4-r[4+nnn], r14"
uint32_t mask = ((0x0ff0 >> (7 - (op & 0x07))) & 0x0ff0) | 0x4000;
if (!try_pop_registers(tid, state, mask)) {
return false;
}
} else if (op == 0xb0) {
// "Finish"
break;
} else if (op == 0xb1) {
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
if (op2 != 0x00 && (op2 & 0xf0) == 0x00) {
// "Pop integer registers under mask {r3, r2, r1, r0}"
if (!try_pop_registers(tid, state, op2)) {
return false;
}
} else {
// "Spare"
return false;
}
} else if (op == 0xb2) {
// "vsp = vsp + 0x204 + (uleb128 << 2)"
uint32_t value = 0;
uint32_t shift = 0;
uint8_t op2;
do {
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
value |= (op2 & 0x7f) << shift;
shift += 7;
} while (op2 & 0x80);
set_reg(state, R_SP, state->gregs[R_SP] + (value << 2) + 0x204);
} else if (op == 0xb3) {
// "Pop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDX"
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 12);
} else if ((op & 0xf8) == 0xb8) {
// "Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by FSTMFDX"
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op & 0x07) * 8 + 12);
} else if ((op & 0xf8) == 0xc0) {
// "Intel Wireless MMX pop wR[10]-wR[10+nnn]"
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op & 0x07) * 8 + 8);
} else if (op == 0xc6) {
// "Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc]"
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
} else if (op == 0xc7) {
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
if (op2 != 0x00 && (op2 & 0xf0) == 0x00) {
// "Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0}"
set_reg(state, R_SP, state->gregs[R_SP] + __builtin_popcount(op2) * 4);
} else {
// "Spare"
return false;
}
} else if (op == 0xc8) {
// "Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc]
// saved (as if) by FSTMFD"
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
} else if (op == 0xc9) {
// "Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) by FSTMFDD"
uint8_t op2;
if (!(size--) || !try_next_byte(tid, stream, &op2)) {
return false;
}
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op2 & 0x0f) * 8 + 8);
} else if ((op == 0xf8) == 0xd0) {
// "Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by FSTMFDD"
set_reg(state, R_SP, state->gregs[R_SP] + (uint32_t)(op & 0x07) * 8 + 8);
} else {
// "Spare"
return false;
}
}
if (!pc_was_set) {
set_reg(state, R_PC, state->gregs[R_LR]);
}
return true;
}
static ssize_t unwind_backtrace_common(pid_t tid, const ptrace_context_t* context,
unwind_state_t* state, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth) {
size_t ignored_frames = 0;
size_t returned_frames = 0;
uintptr_t handler = get_exception_handler(context, tid, state->gregs[R_PC]);
if (!handler) {
// If there is no handler for the PC, the program may have branched to
// an invalid address. Check whether we have a handler for the LR
// where we came from and use that instead.
backtrace_frame_t* frame = add_backtrace_entry(state->gregs[R_PC], backtrace,
ignore_depth, max_depth, &ignored_frames, &returned_frames);
if (frame) {
frame->stack_top = state->gregs[R_SP];
}
handler = get_exception_handler(context, tid, state->gregs[R_LR]);
if (!handler) {
// We don't have a handler here either. Unwinding will not be possible.
// Return the PC and LR (if it looks sane) and call it good.
if (state->gregs[R_LR] && state->gregs[R_LR] != state->gregs[R_PC]) {
// Don't return the SP for this second frame because we don't
// know how big the first one is so we don't know where this
// one starts.
frame = add_backtrace_entry(state->gregs[R_LR], backtrace,
ignore_depth, max_depth, &ignored_frames, &returned_frames);
}
return returned_frames;
}
// Ok, continue from the LR.
set_reg(state, R_PC, state->gregs[R_LR]);
}
while (handler && returned_frames < max_depth) {
backtrace_frame_t* frame = add_backtrace_entry(state->gregs[R_PC], backtrace,
ignore_depth, max_depth, &ignored_frames, &returned_frames);
if (frame) {
frame->stack_top = state->gregs[R_SP];
}
byte_stream_t stream;
stream.ptr = handler;
uint8_t pr;
if (!try_next_byte(tid, &stream, &pr)) {
break;
}
if ((pr & 0xf0) != 0x80) {
// The first word is a place-relative pointer to a generic personality
// routine function. We don't support invoking such functions, so stop here.
break;
}
// The first byte indicates the personality routine to execute.
// Following bytes provide instructions to the personality routine.
if (!execute_personality_routine(tid, state, &stream, pr & 0x0f)) {
break;
}
if (frame && state->gregs[R_SP] > frame->stack_top) {
frame->stack_size = state->gregs[R_SP] - frame->stack_top;
}
handler = get_exception_handler(context, tid, state->gregs[R_PC]);
}
return returned_frames;
}
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
const ucontext_t* uc = (const ucontext_t*)sigcontext;
unwind_state_t state;
for (int i = 0; i < 16; i++) {
state.gregs[i] = uc->uc_mcontext.gregs[i];
}
return unwind_backtrace_common(-1, NULL, &state, backtrace, ignore_depth, max_depth);
}
ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
struct pt_regs regs;
if (ptrace(PTRACE_GETREGS, tid, 0, &regs)) {
return -1;
}
unwind_state_t state;
for (int i = 0; i < 16; i++) {
state.gregs[i] = regs.uregs[i];
}
return unwind_backtrace_common(tid, context, &state, backtrace, ignore_depth, max_depth);
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include "../ptrace-arch.h"
#include <sys/exec_elf.h>
#include <cutils/log.h>
#ifndef PT_ARM_EXIDX
#define PT_ARM_EXIDX 0x70000001
#endif
static void load_exidx_header(pid_t pid, map_info_t* mi,
uintptr_t* out_exidx_start, size_t* out_exidx_size) {
uint32_t elf_phoff;
uint32_t elf_phnum;
if (try_get_word(pid, mi->start + offsetof(Elf32_Ehdr, e_phoff), &elf_phoff)
&& try_get_word(pid, mi->start + offsetof(Elf32_Ehdr, e_phnum), &elf_phnum)) {
for (uint32_t i = 0; i < elf_phnum; i++) {
uintptr_t elf_phdr = mi->start + elf_phoff + i * sizeof(Elf32_Phdr);
uint32_t elf_phdr_type;
if (!try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_type), &elf_phdr_type)) {
break;
}
if (elf_phdr_type == PT_ARM_EXIDX) {
uint32_t elf_phdr_offset;
uint32_t elf_phdr_filesz;
if (!try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_offset),
&elf_phdr_offset)
|| !try_get_word(pid, elf_phdr + offsetof(Elf32_Phdr, p_filesz),
&elf_phdr_filesz)) {
break;
}
*out_exidx_start = mi->start + elf_phdr_offset;
*out_exidx_size = elf_phdr_filesz;
return;
}
}
}
*out_exidx_start = 0;
*out_exidx_size = 0;
}
void load_ptrace_map_info_data_arch(pid_t pid, map_info_t* mi, map_info_data_t* data) {
load_exidx_header(pid, mi, &data->exidx_start, &data->exidx_size);
}
void free_ptrace_map_info_data_arch(map_info_t* mi, map_info_data_t* data) {
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2011 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.
*/
/* Architecture dependent functions. */
#ifndef _CORKSCREW_BACKTRACE_ARCH_H
#define _CORKSCREW_BACKTRACE_ARCH_H
#include "ptrace-arch.h"
#include <corkscrew/backtrace.h>
#include <signal.h>
#ifdef __cplusplus
extern "C" {
#endif
ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
ssize_t unwind_backtrace_ptrace_arch(pid_t tid, const ptrace_context_t* context,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_BACKTRACE_ARCH_H

View file

@ -0,0 +1,40 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include "backtrace-helper.h"
#include <cutils/log.h>
backtrace_frame_t* add_backtrace_entry(uintptr_t pc, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth,
size_t* ignored_frames, size_t* returned_frames) {
if (*ignored_frames < ignore_depth) {
*ignored_frames += 1;
return NULL;
}
if (*returned_frames >= max_depth) {
return NULL;
}
backtrace_frame_t* frame = &backtrace[*returned_frames];
frame->absolute_pc = pc;
frame->stack_top = 0;
frame->stack_size = 0;
*returned_frames += 1;
return frame;
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (C) 2011 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.
*/
/* Backtrace helper functions. */
#ifndef _CORKSCREW_BACKTRACE_HELPER_H
#define _CORKSCREW_BACKTRACE_HELPER_H
#include <corkscrew/backtrace.h>
#include <sys/types.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Add a program counter to a backtrace if it will fit.
* Returns the newly added frame, or NULL if none.
*/
backtrace_frame_t* add_backtrace_entry(uintptr_t pc,
backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth,
size_t* ignored_frames, size_t* returned_frames);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_BACKTRACE_HELPER_H

205
libcorkscrew/backtrace.c Normal file
View file

@ -0,0 +1,205 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include "backtrace-arch.h"
#include "backtrace-helper.h"
#include "ptrace-arch.h"
#include <corkscrew/map_info.h>
#include <corkscrew/symbol_table.h>
#include <corkscrew/ptrace.h>
#include <corkscrew/demangle.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <unwind.h>
#include <sys/exec_elf.h>
#include <cutils/log.h>
#if HAVE_DLADDR
#include <dlfcn.h>
#endif
typedef struct {
backtrace_frame_t* backtrace;
size_t ignore_depth;
size_t max_depth;
size_t ignored_frames;
size_t returned_frames;
} backtrace_state_t;
static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg) {
backtrace_state_t* state = (backtrace_state_t*)arg;
uintptr_t pc = _Unwind_GetIP(context);
if (pc) {
// TODO: Get information about the stack layout from the _Unwind_Context.
// This will require a new architecture-specific function to query
// the appropriate registers. Current callers of unwind_backtrace
// don't need this information, so we won't bother collecting it just yet.
add_backtrace_entry(pc, state->backtrace,
state->ignore_depth, state->max_depth,
&state->ignored_frames, &state->returned_frames);
}
return state->returned_frames < state->max_depth ? _URC_NO_REASON : _URC_END_OF_STACK;
}
ssize_t unwind_backtrace(backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
backtrace_state_t state;
state.backtrace = backtrace;
state.ignore_depth = ignore_depth;
state.max_depth = max_depth;
state.ignored_frames = 0;
state.returned_frames = 0;
_Unwind_Reason_Code rc =_Unwind_Backtrace(unwind_backtrace_callback, &state);
if (state.returned_frames) {
return state.returned_frames;
}
return rc == _URC_END_OF_STACK ? 0 : -1;
}
#ifdef CORKSCREW_HAVE_ARCH
static pthread_mutex_t g_unwind_signal_mutex = PTHREAD_MUTEX_INITIALIZER;
static volatile struct {
backtrace_frame_t* backtrace;
size_t ignore_depth;
size_t max_depth;
size_t returned_frames;
bool done;
} g_unwind_signal_state;
static void unwind_backtrace_thread_signal_handler(int n, siginfo_t* siginfo, void* sigcontext) {
backtrace_frame_t* backtrace = g_unwind_signal_state.backtrace;
if (backtrace) {
g_unwind_signal_state.backtrace = NULL;
g_unwind_signal_state.returned_frames = unwind_backtrace_signal_arch(
siginfo, sigcontext, backtrace,
g_unwind_signal_state.ignore_depth,
g_unwind_signal_state.max_depth);
g_unwind_signal_state.done = true;
}
}
#endif
ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace,
size_t ignore_depth, size_t max_depth) {
#ifdef CORKSCREW_HAVE_ARCH
struct sigaction act;
struct sigaction oact;
memset(&act, 0, sizeof(act));
act.sa_sigaction = unwind_backtrace_thread_signal_handler;
act.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&act.sa_mask);
pthread_mutex_lock(&g_unwind_signal_mutex);
g_unwind_signal_state.backtrace = backtrace;
g_unwind_signal_state.ignore_depth = ignore_depth;
g_unwind_signal_state.max_depth = max_depth;
g_unwind_signal_state.returned_frames = 0;
g_unwind_signal_state.done = false;
ssize_t frames = -1;
if (!sigaction(SIGURG, &act, &oact)) {
if (!kill(tid, SIGURG)) {
while (!g_unwind_signal_state.done) {
usleep(1000);
}
frames = g_unwind_signal_state.returned_frames;
}
sigaction(SIGURG, &oact, NULL);
}
g_unwind_signal_state.backtrace = NULL;
pthread_mutex_unlock(&g_unwind_signal_mutex);
return frames;
#else
return -1;
#endif
}
ssize_t unwind_backtrace_ptrace(pid_t tid, const ptrace_context_t* context,
backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) {
#ifdef CORKSCREW_HAVE_ARCH
return unwind_backtrace_ptrace_arch(tid, context, backtrace, ignore_depth, max_depth);
#else
return -1;
#endif
}
static void init_backtrace_symbol(backtrace_symbol_t* symbol, uintptr_t pc) {
symbol->relative_pc = pc;
symbol->map_info = NULL;
symbol->name = NULL;
symbol->demangled_name = NULL;
}
void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
backtrace_symbol_t* backtrace_symbols) {
const map_info_t* milist = my_map_info_list();
for (size_t i = 0; i < frames; i++) {
const backtrace_frame_t* frame = &backtrace[i];
backtrace_symbol_t* symbol = &backtrace_symbols[i];
init_backtrace_symbol(symbol, frame->absolute_pc);
const map_info_t* mi = find_map_info(milist, frame->absolute_pc);
if (mi) {
symbol->relative_pc = frame->absolute_pc - mi->start;
symbol->map_info = mi;
#if HAVE_DLADDR
Dl_info info;
if (dladdr((const void*)frame->absolute_pc, &info) && info.dli_sname) {
symbol->name = info.dli_sname;
symbol->demangled_name = demangle_symbol_name(symbol->name);
}
#endif
}
}
}
void get_backtrace_symbols_ptrace(const ptrace_context_t* context,
const backtrace_frame_t* backtrace, size_t frames,
backtrace_symbol_t* backtrace_symbols) {
for (size_t i = 0; i < frames; i++) {
const backtrace_frame_t* frame = &backtrace[i];
backtrace_symbol_t* symbol = &backtrace_symbols[i];
init_backtrace_symbol(symbol, frame->absolute_pc);
const map_info_t* mi;
const symbol_t* s;
find_symbol_ptrace(context, frame->absolute_pc, &mi, &s);
if (mi) {
symbol->relative_pc = frame->absolute_pc - mi->start;
symbol->map_info = mi;
}
if (s) {
symbol->name = s->name;
symbol->demangled_name = demangle_symbol_name(symbol->name);
}
}
}
void free_backtrace_symbols(backtrace_symbol_t* backtrace_symbols, size_t frames) {
for (size_t i = 0; i < frames; i++) {
backtrace_symbol_t* symbol = &backtrace_symbols[i];
free(symbol->demangled_name);
init_backtrace_symbol(symbol, 0);
}
}

35
libcorkscrew/demangle.c Normal file
View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include <corkscrew/demangle.h>
#include <cutils/log.h>
#ifdef CORKSCREW_HAVE_LIBIBERTY
// Defined in libiberty.a
extern char *cplus_demangle(const char *mangled, int options);
#endif
char* demangle_symbol_name(const char* name) {
#ifdef CORKSCREW_HAVE_LIBIBERTY
return name ? cplus_demangle(name, 0) : NULL;
#else
return NULL;
#endif
}

112
libcorkscrew/map_info.c Normal file
View file

@ -0,0 +1,112 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include <corkscrew/map_info.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <pthread.h>
#include <unistd.h>
#include <cutils/log.h>
// 6f000000-6f01e000 rwxp 00000000 00:0c 16389419 /system/lib/libcomposer.so\n
// 012345678901234567890123456789012345678901234567890123456789
// 0 1 2 3 4 5
static 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;
}
map_info_t* mi = calloc(1, sizeof(map_info_t) + name_len + 1);
if (mi) {
mi->start = start;
mi->end = end;
mi->is_executable = strlen(permissions) == 4 && permissions[2] == 'x';
mi->data = NULL;
memcpy(mi->name, name, name_len);
mi->name[name_len] = '\0';
}
return mi;
}
map_info_t* load_map_info_list(pid_t tid) {
char path[PATH_MAX];
char line[1024];
FILE* fp;
map_info_t* milist = NULL;
snprintf(path, PATH_MAX, "/proc/%d/maps", tid);
fp = fopen(path, "r");
if (fp) {
while(fgets(line, sizeof(line), fp)) {
map_info_t* mi = parse_maps_line(line);
if (mi) {
mi->next = milist;
milist = mi;
}
}
fclose(fp);
}
return milist;
}
void free_map_info_list(map_info_t* milist) {
while (milist) {
map_info_t* next = milist->next;
free(milist);
milist = next;
}
}
const map_info_t* find_map_info(const map_info_t* milist, uintptr_t addr) {
const map_info_t* mi = milist;
while (mi && !(addr >= mi->start && addr < mi->end)) {
mi = mi->next;
}
return mi;
}
static pthread_once_t g_my_milist_once = PTHREAD_ONCE_INIT;
static map_info_t* g_my_milist = NULL;
static void init_my_milist_once() {
g_my_milist = load_map_info_list(getpid());
}
const map_info_t* my_map_info_list() {
pthread_once(&g_my_milist_once, init_my_milist_once);
return g_my_milist;
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2011 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.
*/
/* Architecture dependent functions. */
#ifndef _CORKSCREW_PTRACE_ARCH_H
#define _CORKSCREW_PTRACE_ARCH_H
#include <corkscrew/ptrace.h>
#include <corkscrew/map_info.h>
#include <corkscrew/symbol_table.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Custom extra data we stuff into map_info_t structures as part
* of our ptrace_context_t. */
typedef struct {
#ifdef __arm__
uintptr_t exidx_start;
size_t exidx_size;
#endif
symbol_table_t* symbol_table;
} map_info_data_t;
void load_ptrace_map_info_data_arch(pid_t pid, map_info_t* mi, map_info_data_t* data);
void free_ptrace_map_info_data_arch(map_info_t* mi, map_info_data_t* data);
#ifdef __cplusplus
}
#endif
#endif // _CORKSCREW_PTRACE_ARCH_H

134
libcorkscrew/ptrace.c Normal file
View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include "ptrace-arch.h"
#include <corkscrew/ptrace.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <cutils/log.h>
static const uint32_t ELF_MAGIC = 0x464C457f; // "ELF\0177"
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#ifndef PAGE_MASK
#define PAGE_MASK (~(PAGE_SIZE - 1))
#endif
bool try_get_word(pid_t tid, uintptr_t ptr, uint32_t* out_value) {
if (ptr & 3) {
LOGV("try_get_word: invalid pointer 0x%08x", ptr);
*out_value = 0;
return false;
}
if (tid < 0) {
#if 0 /*unreliable, unclear whether this is safe from a signal handler context*/
// Determine whether the pointer is likely to be valid before dereferencing it.
unsigned char vec[1];
while (mincore((void*)(ptr & PAGE_MASK), sizeof(uint32_t), vec)) {
if (errno != EAGAIN && errno != EINTR) {
LOGV("try_get_word: invalid pointer 0x%08x, mincore() errno=%d", ptr, errno);
*out_value = 0;
return false;
}
}
#endif
*out_value = *(uint32_t*)ptr;
return true;
} 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, tid, (void*)ptr, NULL);
if (*out_value == 0xffffffffL && errno) {
LOGV("try_get_word: invalid pointer 0x%08x, ptrace() errno=%d", ptr, errno);
*out_value = 0;
return false;
}
return true;
}
}
static void load_ptrace_map_info_data(pid_t pid, map_info_t* mi) {
if (mi->is_executable) {
uint32_t elf_magic;
if (try_get_word(pid, mi->start, &elf_magic) && elf_magic == ELF_MAGIC) {
map_info_data_t* data = (map_info_data_t*)calloc(1, sizeof(map_info_data_t));
if (data) {
mi->data = data;
if (mi->name[0]) {
data->symbol_table = load_symbol_table(mi->name);
}
#ifdef CORKSCREW_HAVE_ARCH
load_ptrace_map_info_data_arch(pid, mi, data);
#endif
}
}
}
}
ptrace_context_t* load_ptrace_context(pid_t pid) {
ptrace_context_t* context =
(ptrace_context_t*)calloc(1, sizeof(ptrace_context_t));
if (context) {
context->map_info_list = load_map_info_list(pid);
for (map_info_t* mi = context->map_info_list; mi; mi = mi->next) {
load_ptrace_map_info_data(pid, mi);
}
}
return context;
}
static void free_ptrace_map_info_data(map_info_t* mi) {
map_info_data_t* data = (map_info_data_t*)mi->data;
if (data) {
if (data->symbol_table) {
free_symbol_table(data->symbol_table);
}
#ifdef CORKSCREW_HAVE_ARCH
free_ptrace_map_info_data_arch(mi, data);
#endif
free(data);
mi->data = NULL;
}
}
void free_ptrace_context(ptrace_context_t* context) {
for (map_info_t* mi = context->map_info_list; mi; mi = mi->next) {
free_ptrace_map_info_data(mi);
}
free_map_info_list(context->map_info_list);
}
void find_symbol_ptrace(const ptrace_context_t* context,
uintptr_t addr, const map_info_t** out_map_info, const symbol_t** out_symbol) {
const map_info_t* mi = find_map_info(context->map_info_list, addr);
const symbol_t* symbol = NULL;
if (mi) {
const map_info_data_t* data = (const map_info_data_t*)mi->data;
if (data && data->symbol_table) {
symbol = find_symbol(data->symbol_table, addr - mi->start);
}
}
*out_map_info = mi;
*out_symbol = symbol;
}

204
libcorkscrew/symbol_table.c Normal file
View file

@ -0,0 +1,204 @@
/*
* Copyright (C) 2011 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 "Corkscrew"
//#define LOG_NDEBUG 0
#include <corkscrew/symbol_table.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/exec_elf.h>
#include <cutils/log.h>
// Compare function for qsort
static int qcompar(const void *a, const void *b) {
const symbol_t* asym = (const symbol_t*)a;
const symbol_t* bsym = (const symbol_t*)b;
if (asym->start > bsym->start) return 1;
if (asym->start < bsym->start) return -1;
return 0;
}
// Compare function for bsearch
static int bcompar(const void *key, const void *element) {
uintptr_t addr = *(const uintptr_t*)key;
const symbol_t* symbol = (const symbol_t*)element;
if (addr < symbol->start) return -1;
if (addr >= symbol->end) return 1;
return 0;
}
symbol_table_t* load_symbol_table(const char *filename) {
symbol_table_t* table = NULL;
int fd = open(filename, O_RDONLY);
if (fd < 0) {
goto out;
}
struct stat sb;
if (fstat(fd, &sb)) {
goto out_close;
}
size_t length = sb.st_size;
char* base = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (base == MAP_FAILED) {
goto out_close;
}
// Parse the file header
Elf32_Ehdr *hdr = (Elf32_Ehdr*)base;
if (!IS_ELF(*hdr)) {
goto out_close;
}
Elf32_Shdr *shdr = (Elf32_Shdr*)(base + hdr->e_shoff);
// Search for the dynamic symbols section
int sym_idx = -1;
int dynsym_idx = -1;
for (Elf32_Half i = 0; i < hdr->e_shnum; i++) {
if (shdr[i].sh_type == SHT_SYMTAB) {
sym_idx = i;
}
if (shdr[i].sh_type == SHT_DYNSYM) {
dynsym_idx = i;
}
}
if (dynsym_idx == -1 && sym_idx == -1) {
goto out_unmap;
}
table = malloc(sizeof(symbol_table_t));
if(!table) {
goto out_unmap;
}
table->num_symbols = 0;
Elf32_Sym *dynsyms = NULL;
int dynnumsyms = 0;
char *dynstr = NULL;
if (dynsym_idx != -1) {
dynsyms = (Elf32_Sym*)(base + shdr[dynsym_idx].sh_offset);
dynnumsyms = shdr[dynsym_idx].sh_size / shdr[dynsym_idx].sh_entsize;
int dynstr_idx = shdr[dynsym_idx].sh_link;
dynstr = base + shdr[dynstr_idx].sh_offset;
}
Elf32_Sym *syms = NULL;
int numsyms = 0;
char *str = NULL;
if (sym_idx != -1) {
syms = (Elf32_Sym*)(base + shdr[sym_idx].sh_offset);
numsyms = shdr[sym_idx].sh_size / shdr[sym_idx].sh_entsize;
int str_idx = shdr[sym_idx].sh_link;
str = base + shdr[str_idx].sh_offset;
}
int dynsymbol_count = 0;
if (dynsym_idx != -1) {
// Iterate through the dynamic symbol table, and count how many symbols
// are actually defined
for (int i = 0; i < dynnumsyms; i++) {
if (dynsyms[i].st_shndx != SHN_UNDEF) {
dynsymbol_count++;
}
}
}
size_t symbol_count = 0;
if (sym_idx != -1) {
// Iterate through the symbol table, and count how many symbols
// are actually defined
for (int i = 0; i < numsyms; i++) {
if (syms[i].st_shndx != SHN_UNDEF
&& str[syms[i].st_name]
&& syms[i].st_value
&& syms[i].st_size) {
symbol_count++;
}
}
}
// Now, create an entry in our symbol table structure for each symbol...
table->num_symbols += symbol_count + dynsymbol_count;
table->symbols = malloc(table->num_symbols * sizeof(symbol_t));
if (!table->symbols) {
free(table);
table = NULL;
goto out_unmap;
}
size_t symbol_index = 0;
if (dynsym_idx != -1) {
// ...and populate them
for (int i = 0; i < dynnumsyms; i++) {
if (dynsyms[i].st_shndx != SHN_UNDEF) {
table->symbols[symbol_index].name = strdup(dynstr + dynsyms[i].st_name);
table->symbols[symbol_index].start = dynsyms[i].st_value;
table->symbols[symbol_index].end = dynsyms[i].st_value + dynsyms[i].st_size;
symbol_index += 1;
}
}
}
if (sym_idx != -1) {
// ...and populate them
for (int i = 0; i < numsyms; i++) {
if (syms[i].st_shndx != SHN_UNDEF
&& str[syms[i].st_name]
&& syms[i].st_value
&& syms[i].st_size) {
table->symbols[symbol_index].name = strdup(str + syms[i].st_name);
table->symbols[symbol_index].start = syms[i].st_value;
table->symbols[symbol_index].end = syms[i].st_value + syms[i].st_size;
symbol_index += 1;
}
}
}
// Sort the symbol table entries, so they can be bsearched later
qsort(table->symbols, table->num_symbols, sizeof(symbol_t), qcompar);
out_unmap:
munmap(base, length);
out_close:
close(fd);
out:
return table;
}
void free_symbol_table(symbol_table_t* table) {
if (table) {
for (size_t i = 0; i < table->num_symbols; i++) {
free(table->symbols[i].name);
}
free(table->symbols);
free(table);
}
}
const symbol_t* find_symbol(const symbol_table_t* table, uintptr_t addr) {
if (!table) return NULL;
return (const symbol_t*)bsearch(&addr, table->symbols, table->num_symbols,
sizeof(symbol_t), bcompar);
}