| /* Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. */ |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| #ifdef __GLIBC__ |
| #include <elf.h> |
| #include <link.h> |
| #endif /* __GLIBC__ */ |
| |
| #include "irt.h" |
| #include "nacl/nacl_exception.h" |
| |
| #include "error_handling/error_handling.h" |
| #include "error_handling/string_stream.h" |
| |
| |
| #define PAGE_CHUNK_SIZE (64 * 1024) |
| #define PAGE_CHUNK_MASK (PAGE_CHUNK_SIZE - 1) |
| #define STACK_SIZE_MIN (PAGE_CHUNK_SIZE * 4) |
| |
| #define MAX_FRAME_SIZE (10 * 1024) |
| #define MAX_FRAME_CAP 128 |
| |
| static pthread_key_t s_eh_stack_info_key; |
| static EHJsonHandler s_eh_json_callback = NULL; |
| static int s_eh_exception_enabled = 0; |
| static struct nacl_irt_exception_handling s_exception_handling; |
| |
| |
| typedef struct { |
| void* stack; |
| size_t size; |
| } EHStackInfo; |
| |
| static uintptr_t EHReadPointer(uintptr_t offset) { |
| return *((uintptr_t*) offset); |
| } |
| |
| static void EHPrintArch(sstream_t* ss, struct NaClExceptionContext* context) { |
| #if defined(__x86_64__) |
| ssprintf(ss, "\"arch\": \"x86_64\",\n"); |
| #elif defined(__i386__) |
| ssprintf(ss, "\"arch\": \"x86_32\",\n"); |
| #elif defined(__arm__) |
| ssprintf(ss, "\"arch\": \"arm\",\n"); |
| #elif defined(__mips__) |
| ssprintf(ss, "\"arch\": \"mips\",\n"); |
| #else |
| #error Unknown ARCH |
| #endif |
| } |
| |
| static void EHPrintSegments(sstream_t* ss, |
| struct NaClExceptionContext* context) { |
| ssprintf(ss, "\"segments\": ["); |
| ssprintf(ss, "],\n"); |
| } |
| |
| static void EHPrintFrame(sstream_t* ss, EHFrame* frame) { |
| uintptr_t start; |
| uintptr_t i; |
| |
| ssprintf(ss, "{\n"); |
| ssprintf(ss, "\"frame_ptr\": %u,\n", frame->frame_ptr); |
| ssprintf(ss, "\"prog_ctr\": %u,\n", frame->prog_ctr); |
| ssprintf(ss, "\"data\": [\n"); |
| |
| #if defined(__x86_64__) |
| start = frame->frame_ptr + 8; |
| #else |
| start = frame->frame_ptr + 16; |
| #endif |
| /* Capture the stack, no mare than 128 bytes to keep the size sane. */ |
| for (i = start; i < frame->next_ptr && i - start < MAX_FRAME_CAP; i += 4) { |
| if (i != start) { |
| ssprintf(ss, ","); |
| } |
| ssprintf(ss, "%u\n", EHReadPointer(i)); |
| } |
| ssprintf(ss, "]\n}\n"); |
| } |
| |
| |
| static void EHPrintMainContext(sstream_t* ss, |
| struct NaClExceptionContext* context) { |
| struct NaClExceptionPortableContext* portable_context = |
| nacl_exception_context_get_portable(context); |
| ssprintf(ss, "\"handler\": {\n"); |
| ssprintf(ss, "\"prog_ctr\": %u,\n", portable_context->prog_ctr); |
| ssprintf(ss, "\"stack_ptr\": %u,\n", portable_context->stack_ptr); |
| ssprintf(ss, "\"frame_ptr\": %u\n", portable_context->frame_ptr); |
| ssprintf(ss, "},\n"); |
| } |
| |
| |
| int EHGetTopFrame(sstream_t* ss, struct NaClExceptionContext* context, |
| EHFrame* frame) { |
| struct NaClExceptionPortableContext* portable_context = |
| nacl_exception_context_get_portable(context); |
| |
| frame->prog_ctr = portable_context->prog_ctr; |
| frame->frame_ptr = portable_context->frame_ptr; |
| frame->next_ptr = EHReadPointer(portable_context->frame_ptr); |
| return 1; |
| } |
| |
| |
| int EHUnwindFrame(EHFrame* frame) { |
| uintptr_t frame_ptr; |
| uintptr_t next; |
| |
| // Verify the current frame |
| if (NULL == frame) return 0; |
| |
| frame_ptr = frame->frame_ptr; |
| next = frame->next_ptr; |
| |
| // Abort if done or unwind moves us in the wrong direction |
| if (next <= frame_ptr || next == 0) return 0; |
| |
| // Abort if frame is > 10K |
| if (next - frame_ptr > MAX_FRAME_SIZE) return 0; |
| |
| // Unwind the frame |
| frame->frame_ptr = next; |
| frame->next_ptr = EHReadPointer(frame->frame_ptr); |
| #if defined(__x86_64__) |
| frame->prog_ctr = EHReadPointer(frame_ptr + 8); |
| #else |
| frame->prog_ctr = EHReadPointer(frame_ptr + 4); |
| #endif |
| return 1; |
| } |
| |
| |
| static void EHStackInfoDestructor(void *arg) { |
| EHStackInfo* info = (EHStackInfo*) arg; |
| |
| if (info) { |
| munmap(info->stack, info->size); |
| } |
| free(info); |
| } |
| |
| |
| void EHDefaultJsonHandler(struct NaClExceptionContext* context) { |
| if (s_eh_json_callback) { |
| EHFrame frame; |
| sstream_t ss; |
| ssinit(&ss); |
| |
| ssprintf(&ss, "{\n"); |
| EHPrintArch(&ss, context); |
| EHPrintSegments(&ss, context); |
| EHPrintMainContext(&ss, context); |
| |
| EHGetTopFrame(&ss, context, &frame); |
| int first = 1; |
| |
| ssprintf(&ss, "\"frames\": [\n"); |
| do { |
| if (!first) ssprintf(&ss, ","); |
| EHPrintFrame(&ss, &frame); |
| first = 0; |
| } while (EHUnwindFrame(&frame)); |
| |
| /* End frame LIST and context DICT */ |
| ssprintf(&ss, "]\n}\n"); |
| s_eh_json_callback(ss.data); |
| |
| ssfree(&ss); |
| while(1) sleep(9999); |
| } |
| } |
| |
| |
| void EHRequestExceptionsRaw(EHRawHandler callback) { |
| size_t interface_size = sizeof(s_exception_handling); |
| if (s_eh_exception_enabled) { |
| fprintf(stderr, "ERROR: EHInit already initialized.\n"); |
| return; |
| } |
| if (NULL == callback) { |
| fprintf(stderr, "ERROR: EHInit called with NULL callback.\n"); |
| return; |
| } |
| |
| /* Expect an exact match on the interface structure size. */ |
| if (nacl_interface_query(NACL_IRT_EXCEPTION_HANDLING_v0_1, |
| &s_exception_handling, |
| interface_size) != interface_size) { |
| fprintf(stderr, "ERROR: EHInit failed nacl_interface_query\n"); |
| return; |
| } |
| |
| if (s_exception_handling.exception_handler(callback, NULL) != 0) { |
| fprintf(stderr, "ERROR: EHInit failed to install exception_handler\n"); |
| return; |
| } |
| |
| s_eh_exception_enabled = 1; |
| |
| // Create a TLS key for storing per thread stack info |
| pthread_key_create(&s_eh_stack_info_key, EHStackInfoDestructor); |
| } |
| |
| |
| void *EHRequestExceptionStackOnThread(size_t stack_size) { |
| void* stack; |
| void* guard; |
| EHStackInfo* stack_info; |
| |
| // Set the stack size |
| stack_size = (stack_size + PAGE_CHUNK_MASK) & PAGE_CHUNK_MASK; |
| if (stack_size < STACK_SIZE_MIN) stack_size = STACK_SIZE_MIN; |
| |
| // Allocate stack + guard page |
| stack = mmap(NULL, stack_size + PAGE_CHUNK_SIZE, |
| PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (MAP_FAILED == stack) return MAP_FAILED; |
| |
| // Remap to mprotect which may not be available |
| guard = mmap(stack, PAGE_CHUNK_SIZE, |
| PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
| if (guard != stack) { |
| fprintf(stderr, "WARNING: Failed to add guard page for alt stack.\n"); |
| } |
| |
| if (!s_exception_handling.exception_stack(stack, stack_size)) { |
| fprintf(stderr, "ERROR: Failed to assign stack.\n"); |
| munmap(stack, stack_size); |
| return MAP_FAILED; |
| } |
| |
| // Allocate stack tracking information for this thread |
| stack_info = (EHStackInfo*) malloc(sizeof(EHStackInfo)); |
| stack_info->stack = stack; |
| stack_info->size = stack_size + PAGE_CHUNK_SIZE; |
| pthread_setspecific(s_eh_stack_info_key, stack_info); |
| return stack; |
| } |
| |
| |
| void EHRequestExceptionsJson(EHJsonHandler callback) { |
| if (NULL == callback) return; |
| |
| EHRequestExceptionsRaw(EHDefaultJsonHandler); |
| if (s_eh_exception_enabled) s_eh_json_callback = callback; |
| } |
| |
| |
| int EHHanderInstalled() { |
| return s_eh_exception_enabled; |
| } |