Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f41cf208 authored by Christopher Ferris's avatar Christopher Ferris Committed by Gerrit Code Review
Browse files

Merge "Create a single backtrace library."

parents aa62be65 7fb22878
Loading
Loading
Loading
Loading
+104 −0
Original line number Diff line number Diff line
/*
 * 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 */
+176 −0
Original line number Diff line number Diff line
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
+217 −0
Original line number Diff line number Diff line
/*
 * 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;
}
+56 −0
Original line number Diff line number Diff line
/*
 * 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;
}

libbacktrace/common.c

0 → 100644
+113 −0
Original line number Diff line number Diff line
/*
 * 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;
}
Loading