diff options
Diffstat (limited to 'native_client_sdk')
10 files changed, 1217 insertions, 1 deletions
diff --git a/native_client_sdk/src/examples/debugging/Makefile b/native_client_sdk/src/examples/debugging/Makefile new file mode 100644 index 0000000..a896f37 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/Makefile @@ -0,0 +1,109 @@ +# Copyright (c) 2012 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# +# GNU Make based build file. For details on GNU Make see: +# http://www.gnu.org/software/make/manual/make.html +# + + +# +# Get pepper directory for toolchain and includes. +# +# If NACL_SDK_ROOT is not set, then assume it can be found a two directories up, +# from the default example directory location. +# +THIS_MAKEFILE:=$(abspath $(lastword $(MAKEFILE_LIST))) +NACL_SDK_ROOT?=$(abspath $(dir $(THIS_MAKEFILE))../..) +CHROME_PATH?=Undefined + +# +# Project Build flags +# +# Turns on warnings (-Wxxx), builds with zero optimization (-O0) and adds debug +# information (-g) for correctness and ease of debugging. +WARNINGS:=-Wno-long-long -Wall -Wswitch-enum -Werror -pedantic +CFLAGS:=-pthread -O0 -g $(WARNINGS) -fno-omit-frame-pointer + +# +# Compute path to compiler +# +OSNAME:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py) +TC_PATH:=$(abspath $(NACL_SDK_ROOT)/toolchain/$(OSNAME)_x86_newlib) + +# +# Verify we have a valid NACL_SDK_ROOT +# +ifeq (,$(wildcard $(TC_PATH))) +$(warning No valid NACL_SDK_ROOT at $(NACL_SDK_ROOT)) +ifeq ($(origin NACL_SDK_ROOT), 'file') +$(error Override the default value via enviornment variable, or command-line.) +else +$(error Fix the NACL_SDK_ROOT specified in the environment or command-line.) +endif +endif + +# Alias for C compiler +CC:=$(TC_PATH)/bin/i686-nacl-gcc + + +# +# Disable DOS PATH warning when using Cygwin based tools Windows +# +CYGWIN ?= nodosfilewarning +export CYGWIN + + +# Default target is everything +all : hello_world_x86_32.nexe hello_world_x86_64.nexe + +# Define compile and link rule for 32 bit (-m32) nexe +hello_world_x86_32.nexe : hello_world.c untrusted_crash_dump.c string_stream.c + $(CC) -o $@ $^ -m32 -O0 -g $(CFLAGS) -lppapi + +# Define compile and link rule for 64 bit (-m64) nexe +hello_world_x86_64.nexe : hello_world.c untrusted_crash_dump.c string_stream.c + $(CC) -o $@ $^ -m64 -O0 -g $(CFLAGS) -lppapi + +# Define a phony rule so it always runs, to build nexe and start up server. +.PHONY: RUN +RUN: all + python ../httpd.py + + +# +# Setup environment to use SDK's version of the NaCl plugin, instead of the +# one built into Chrome. This requries launching Chrome which means we must +# be able to find it. It also means that we must determine which versions +# of the plugin, loader, irt, and any helps we must use. +# +HELPER_PATH:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py --helper) +IRTBIN_PATH:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py --irtbin) +LOADER_PATH:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py --loader) +PLUGIN_PATH:=$(shell python $(NACL_SDK_ROOT)/tools/getos.py --plugin) +CHROME_ARGS:=--incognito --no-sandbox +CHROME_ARGS+="--register-pepper-plugins=$(PLUGIN_PATH);application/x-nacl" +CHROME_ARGS+="localhost:5103/debugging.html" + + +.PHONY: SETUP_FOR_CHROME +SETUP_FOR_CHROME: +ifeq (,$(wildcard $(CHROME_PATH))) + $(warning No valid Chrome found at CHROME_PATH=$(CHROME_PATH)) + $(error Set CHROME_PATH via an environment variable, or command-line.) +endif + + +ifneq (,$(wildcard $(CHROME_PATH))) +export NACL_DANGEROUS_ENABLE_FILE_ACCESS=1 +export NACL_SECURITY_DISABLE=1 +export NACL_UNTRUSTED_EXCEPTION_HANDLING=1 +export NACL_SEL_LDR=$(LOADER_PATH) +export NACL_IRT_LIBRARY=$(IRTBIN_PATH) +export NACL_SEL_LDR_BOOTSTRAP=$(HELPER_PATH) +endif + + +TRACE: SETUP_FOR_CHROME all + $(CHROME_PATH) $(CHROME_ARGS) diff --git a/native_client_sdk/src/examples/debugging/debugging.html b/native_client_sdk/src/examples/debugging/debugging.html new file mode 100644 index 0000000..6636945 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/debugging.html @@ -0,0 +1,144 @@ +<!DOCTYPE html> +<html> + <!-- + Copyright (c) 2012 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. + --> +<head> + <meta http-equiv="Pragma" content="no-cache" /> + <meta http-equiv="Expires" content="-1" /> + <title>Logging and Stack Trace</title> + <script type="text/javascript"> + statusText = 'NO-STATUS'; + tick = ''; + boomTime = null; + crashed = false; + // Handle a message coming from the NaCl module. + function handleMessage(message_event) { + msg_type = message_event.data.substring(0,4) + msg_data = message_event.data.substring(5, message_event.data.length) + if (msg_type == 'POP:') { + alert(message_event.data); + return + } + if (msg_type == 'LOG:') { + document.Logging.log.value += msg_data + '\n'; + return + } + if (msg_type == 'TRC:') { + crashed = true; + updateStatus('Crash Reported') + xmlhttp = new XMLHttpRequest(); + xmlhttp.open('POST',document.nacl_module.src,false); + xmlhttp.send(msg_data) + document.Logging.trace.value = xmlhttp.responseText + '\n'; + return + } + } + + function pageDidLoad() { + updateStatus("Page Loaded") + heartBeat(); + updateStatus("Page Submitted") + } + + // Indicate success when the NaCl module has loaded. + function moduleDidLoad() { + updateStatus('LOADED'); + t=setTimeout('boom()', 4000); + } + // Set the global status message. If the element with id 'statusField' + // exists, then set its HTML to the status message as well. + // opt_message The message test. If this is null or undefined, then + // attempt to set the element with id 'statusField' to the value of + // |statusText|. + function updateStatus(opt_message) { + if (opt_message) + statusText = opt_message; + document.Logging.log.value += opt_message + '\n' + var statusField = document.getElementById('statusField'); + if (statusField) { + statusField.innerHTML = statusText; + } + } + + function boom() { + if (!crashed) { + updateStatus('Send BOOM'); + document.nacl_module.postMessage('BOOM'); + t=setTimeout('boom()', 1000); + } + } + + function heartBeat() { + if (document.nacl_module.lastError) { + if (tick != document.nacl_module.lastError) { + tick = document.nacl_module.lastError; + updateStatus('Missed heartbeat: ' + document.nacl_module.lastError); + crashed = true; + } + } + else { t=setTimeout('heartBeat()', 1000); } + } + </script> + <form name="Status"> + <input type="hidden" name="Data" value=""> + </form> +</head> +<body onload="pageDidLoad()"> +<h1>Native Client Getting Started: Debugging.</h1> +<p>This example shows how to trap an untrusted exception, and then send that +information to the webserver to generate a stack trace. This can only be used +for development since it requires several command-line switches, environment +variables and a special version of the plugin. The test works, by loading the +module and communicating with it through PostMessage. Messages from the module +are sent to the status line and/or the log window. Four seconds after loading, +the script will send a 'BOOM' message to the module which will cause it to +dereference an illegal location in memory.</p> + +<p>If Chrome is launched correctly with the appropriate command-line arguments +and environment variables, the Log will show the crash dump facilities were +turn on and when the crash data arrives, it will be forwarded to the http server +which will drive the decoder and send a stack trace back to the web page.</p> + +<p>If setup incorrectly, the page may or may not load. If it does, it will +send a "LOADED" message to the log and eventual will crash. The script will +detect this crash by detecting a missed heartbeat the the running application +would normal send, and then print the current module status. +</p> + +<h2>Running the example</h2> +In one console window, to start the server: +<ul> +<li>Set CHROME_PATH to the fully qualified path.</li> +<li>From the example directory type: <b>make RUN</b></li> +</ul> +In another console window, to start Chrome: +<ul> +<li>Set CHROME_PATH to the fully qualified path.</li> +<li>From the example directory type: <b>make TRACE</b></li> +</ul> + +<div id="listener"> + <script type="text/javascript"> + var listener = document.getElementById('listener') + listener.addEventListener('load', moduleDidLoad, true); + listener.addEventListener('message', handleMessage, true); + </script> + <embed name="nacl_module" + id="hello_world" + width=100 height=100 + src="hello_world.nmf" + type="application/x-nacl" /> +</div> +<h2>Status: <code id="statusField">NO-STATUS</code></h2> + <form name="Logging"> + <h2>Log</h2> + <textarea rows="10" cols="130" name="log" readonly="readonly"></textarea> + <br /> + <h2>Stack Trace</h2> + <textarea rows="10" cols="130" name="trace" readonly="readonly"></textarea> + </form> +</body> +</html> diff --git a/native_client_sdk/src/examples/debugging/hello_world.c b/native_client_sdk/src/examples/debugging/hello_world.c new file mode 100644 index 0000000..b081062 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/hello_world.c @@ -0,0 +1,289 @@ +/* Copyright (c) 2012 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. + */ + +/** @file hello_world.c + * This example, is a modified version of hello world. It will start a second + * thread and cause that thread to crash via a NULL dereference. + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_module.h" +#include "ppapi/c/pp_var.h" +#include "ppapi/c/ppb.h" +#include "ppapi/c/ppb_core.h" +#include "ppapi/c/ppb_instance.h" +#include "ppapi/c/ppb_messaging.h" +#include "ppapi/c/ppb_var.h" +#include "ppapi/c/ppp.h" +#include "ppapi/c/ppp_instance.h" +#include "ppapi/c/ppp_messaging.h" + +#include <pthread.h> + +#include "string_stream.h" +#include "untrusted_crash_dump.h" + +PPB_Messaging* ppb_messaging_interface = NULL; +PPB_Var* ppb_var_interface = NULL; +PPB_Core* ppb_core_interface = NULL; + +pthread_t g_NexeThread; +pthread_t g_PPAPIThread; +PP_Instance g_Instance; + +volatile int g_CrashTime = 0; + + +void PostMessage(const char *str); + + +void layer5(int x, int y) { + if (g_CrashTime) { + *(volatile int *) x = y; + } +} + +void layer4(int x) { + layer5(x, 1); +} + +void layer3(int a, int b, int c) { + layer4(a + b + c); +} + +void layer2(int i, int j) { + layer3(i, j, 7); +} + +void layer1(int s, int t) { + int *junk = (int*)alloca(sizeof(int)* 1234); + junk[0] = s + 5; + layer2(junk[0], t + 1); +} + +void *NexeMain(void *data) { + PostMessage("Running Boom thread."); + while (1) { + layer1(2, 9); + } + return NULL; +} + +/** + * Creates new string PP_Var from C string. The resulting object will be a + * refcounted string object. It will be AddRef()ed for the caller. When the + * caller is done with it, it should be Release()d. + * @param[in] str C string to be converted to PP_Var + * @return PP_Var containing string. + */ +static struct PP_Var CStrToVar(const char* str) { + if (ppb_var_interface != NULL) { + return ppb_var_interface->VarFromUtf8(str, strlen(str)); + } + return PP_MakeUndefined(); +} + + +static void PostCompletionCallback(void* user_data, int32_t result) { + ppb_messaging_interface->PostMessage(g_Instance, CStrToVar(user_data)); + free(user_data); +} + +void PostMessage(const char *str) { + struct PP_CompletionCallback cb; + + if (NULL == str) return; + if (NULL == ppb_messaging_interface) return; + if (0 == g_Instance) return; + + if (strncmp(str, "ERR:", 4)) { + fprintf(stderr, "%s\n", str); + fflush(stderr); + } + else { + fprintf(stdout, "%s\n", str); + fflush(stdout); + } + + /* If on Main Pepper thread, then call interface directly. */ + if (pthread_self() == g_PPAPIThread) { + ppb_messaging_interface->PostMessage(g_Instance, CStrToVar(str)); + return; + } + + /* Otherwise use call on main thread. */ + cb = PP_MakeCompletionCallback(PostCompletionCallback, strdup(str)); + ppb_core_interface->CallOnMainThread(0, cb, 0); +} + + +/** + * Called when the NaCl module is instantiated on the web page. The identifier + * of the new instance will be passed in as the first argument (this value is + * generated by the browser and is an opaque handle). This is called for each + * instantiation of the NaCl module, which is each time the <embed> tag for + * this module is encountered. + * + * If this function reports a failure (by returning @a PP_FALSE), the NaCl + * module will be deleted and DidDestroy will be called. + * @param[in] instance The identifier of the new instance representing this + * NaCl module. + * @param[in] argc The number of arguments contained in @a argn and @a argv. + * @param[in] argn An array of argument names. These argument names are + * supplied in the <embed> tag, for example: + * <embed id="nacl_module" dimensions="2"> + * will produce two arguments, one named "id" and one named "dimensions". + * @param[in] argv An array of argument values. These are the values of the + * arguments listed in the <embed> tag. In the above example, there will + * be two elements in this array, "nacl_module" and "2". The indices of + * these values match the indices of the corresponding names in @a argn. + * @return @a PP_TRUE on success. + */ +static PP_Bool Instance_DidCreate(PP_Instance instance, + uint32_t argc, + const char* argn[], + const char* argv[]) { + g_Instance = instance; + g_PPAPIThread = pthread_self(); + + PostMessage("LOG: DidCreate"); + if (!NaClCrashDumpInit()) { + PostMessage("LOG: Failed to set up crash dump."); + } + else { + PostMessage("LOG: Crash Dump On"); + } + pthread_create(&g_NexeThread, NULL, NexeMain, NULL); + return PP_TRUE; +} + + +/** + * Called when the NaCl module is destroyed. This will always be called, + * even if DidCreate returned failure. This routine should deallocate any data + * associated with the instance. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + */ +static void Instance_DidDestroy(PP_Instance instance) { +} + +/** + * Called when the position, the size, or the clip rect of the element in the + * browser that corresponds to this NaCl module has changed. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] position The location on the page of this NaCl module. This is + * relative to the top left corner of the viewport, which changes as the + * page is scrolled. + * @param[in] clip The visible region of the NaCl module. This is relative to + * the top left of the plugin's coordinate system (not the page). If the + * plugin is invisible, @a clip will be (0, 0, 0, 0). + */ +static void Instance_DidChangeView(PP_Instance instance, + PP_Resource view_resource) { +} + +/** + * Notification that the given NaCl module has gained or lost focus. + * Having focus means that keyboard events will be sent to the NaCl module + * represented by @a instance. A NaCl module's default condition is that it + * will not have focus. + * + * Note: clicks on NaCl modules will give focus only if you handle the + * click event. You signal if you handled it by returning @a true from + * HandleInputEvent. Otherwise the browser will bubble the event and give + * focus to the element on the page that actually did end up consuming it. + * If you're not getting focus, check to make sure you're returning true from + * the mouse click in HandleInputEvent. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] has_focus Indicates whether this NaCl module gained or lost + * event focus. + */ +static void Instance_DidChangeFocus(PP_Instance instance, + PP_Bool has_focus) { +} + +/** + * Handler that gets called after a full-frame module is instantiated based on + * registered MIME types. This function is not called on NaCl modules. This + * function is essentially a place-holder for the required function pointer in + * the PPP_Instance structure. + * @param[in] instance The identifier of the instance representing this NaCl + * module. + * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance. + * @return PP_FALSE. + */ +static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, + PP_Resource url_loader) { + /* NaCl modules do not need to handle the document load function. */ + return PP_FALSE; +} + + +/** + * Handles message from JavaScript. + * + * Any message from JS is a request to cause the main thread to crash. + */ +static void Messaging_HandleMessage(PP_Instance instance, + struct PP_Var message) { + PostMessage("LOG: Got BOOM"); + g_CrashTime = 1; +} + +/** + * Entry points for the module. + * Initialize needed interfaces: PPB_Core, PPB_Messaging and PPB_Var. + * @param[in] a_module_id module ID + * @param[in] get_browser pointer to PPB_GetInterface + * @return PP_OK on success, any other value on failure. + */ +PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id, + PPB_GetInterface get_browser) { + ppb_messaging_interface = + (PPB_Messaging*)(get_browser(PPB_MESSAGING_INTERFACE)); + ppb_var_interface = (PPB_Var*)(get_browser(PPB_VAR_INTERFACE)); + ppb_core_interface = (PPB_Core*)(get_browser(PPB_CORE_INTERFACE)); + return PP_OK; +} + + +/** + * Returns an interface pointer for the interface of the given name, or NULL + * if the interface is not supported. + * @param[in] interface_name name of the interface + * @return pointer to the interface + */ +PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { + if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) { + static PPP_Instance instance_interface = { + &Instance_DidCreate, + &Instance_DidDestroy, + &Instance_DidChangeView, + &Instance_DidChangeFocus, + &Instance_HandleDocumentLoad, + }; + return &instance_interface; + } + if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) { + static PPP_Messaging messaging_interface = { + &Messaging_HandleMessage, + }; + return &messaging_interface; + } + return NULL; +} + + +/** + * Called before the plugin module is unloaded. + */ +PP_EXPORT void PPP_ShutdownModule() { +} diff --git a/native_client_sdk/src/examples/debugging/hello_world.nmf b/native_client_sdk/src/examples/debugging/hello_world.nmf new file mode 100644 index 0000000..78df8e1 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/hello_world.nmf @@ -0,0 +1,6 @@ +{ + "program": { + "x86-64": {"url": "hello_world_x86_64.nexe"}, + "x86-32": {"url": "hello_world_x86_32.nexe"} + } +} diff --git a/native_client_sdk/src/examples/debugging/string_stream.c b/native_client_sdk/src/examples/debugging/string_stream.c new file mode 100644 index 0000000..08783cc --- /dev/null +++ b/native_client_sdk/src/examples/debugging/string_stream.c @@ -0,0 +1,46 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "string_stream.h" + + +void ssinit(sstream_t *stream) { + stream->data = NULL; + stream->length = 0; +} + +void ssfree(sstream_t *stream) { + free(stream->data); + stream->data = 0; + stream->length = 0; +} + +int ssvprintf(sstream_t *stream, const char *format, va_list args) { + va_list hold; + int len; + char *outstr; + + va_copy(hold, args); + len = vsnprintf(NULL, 0, format, args); + + outstr = malloc(stream->length + len + 1); + if (stream->data) { + memcpy(outstr, stream->data, stream->length); + free(stream->data); + } + + stream->data = outstr; + vsprintf(&stream->data[stream->length], format, hold); + stream->length += len; + return len; +} + +int ssprintf(sstream_t *stream, const char *format, ...) { + int out; + va_list args; + va_start(args, format); + out = ssvprintf(stream, format, args); + va_end(args); + + return out; +}
\ No newline at end of file diff --git a/native_client_sdk/src/examples/debugging/string_stream.h b/native_client_sdk/src/examples/debugging/string_stream.h new file mode 100644 index 0000000..a985516 --- /dev/null +++ b/native_client_sdk/src/examples/debugging/string_stream.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012 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. + */ + +#ifndef STRING_STREAM_H +#define STRING_STREAM_H + +/* + * Support for a stream stream in 'C', which is appened to via an sprintf-like + * function. + */ + +#include <stdarg.h> +#include <stdint.h> + +typedef struct { + char *data; + size_t length; +} sstream_t; + +void ssinit(sstream_t *stream); +void ssfree(sstream_t *stream); + +/* Returns the number of bytes added to the stream. */ +int ssvprintf(sstream_t *sstream, const char *format, va_list args); +int ssprintf(sstream_t *sstream, const char *format, ...); + + +#endif diff --git a/native_client_sdk/src/examples/debugging/untrusted_crash_dump.c b/native_client_sdk/src/examples/debugging/untrusted_crash_dump.c new file mode 100644 index 0000000..959ad9e --- /dev/null +++ b/native_client_sdk/src/examples/debugging/untrusted_crash_dump.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2012 The Native Client 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> + +#ifdef __GLIBC__ +#include <elf.h> +#include <link.h> +#endif /* __GLIBC__ */ + +#include "untrusted_crash_dump.h" +#include "string_stream.h" +#include "irt.h" + +struct NaClExceptionContext { + uint32_t prog_ctr; + uint32_t stack_ptr; + uint32_t frame_ptr; + /* + * Pad this up to an even multiple of 8 bytes so this struct will add + * a predictable amount of space to the various ExceptionFrame structures + * used on each platform. This allows us to verify stack layout with dead + * reckoning, without access to the ExceptionFrame structure used to set up + * the call stack. + */ + uint32_t pad; +}; + +#define CRASH_PAGE_CHUNK (64 * 1024) +#define CRASH_STACK_SIZE (CRASH_PAGE_CHUNK * 4) +#define CRASH_STACK_GUARD_SIZE CRASH_PAGE_CHUNK +#define CRASH_STACK_COMPLETE_SIZE (CRASH_STACK_GUARD_SIZE + CRASH_STACK_SIZE) + + +static pthread_key_t g_CrashStackKey; +static struct nacl_irt_dev_exception_handling g_ExceptionHandling; +static int g_ExceptionHandlingEnabled = 0; + + +#ifdef __GLIBC__ + +struct ProgramTableData { + sstream_t *core; + int first; +}; + +static void WriteJsonString(const char *str, sstream_t *core) { + char ch; + + ssprintf(core, "\""); + for (;;) { + ch = *str++; + if (ch == '\0') { + break; + } else if (ch == '"') { + ssprintf(core, "\\\""); + } else if (ch == '\\') { + ssprintf(core, "\\\\"); + } else if (ch < 32 || ch > 126) { + ssprintf(core, "\\x%02x", (uint8_t)ch); + } else { + ssprintf(core, "%c", ch); + } + } + ssprintf(core, "\""); +} + +static int PrintSegmentsOne( + struct dl_phdr_info *info, size_t size, void *data) { + int i; + struct ProgramTableData *ptd = (struct ProgramTableData*) data; + + if (ptd->first) { + ptd->first = 0; + } else { + fprintf(ptd->core, ",\n"); + } + fprintf(ptd->core, "{\n"); + fprintf(ptd->core, "\"dlpi_name\": "); + WriteJsonString(info->dlpi_name, ptd->core); + fprintf(ptd->core, ",\n"); + fprintf(ptd->core, "\"dlpi_addr\": %u,\n", + (uintptr_t) info->dlpi_addr); + fprintf(ptd->core, "\"dlpi_phdr\": [\n"); + for (i = 0; i < info->dlpi_phnum; i++) { + /* Skip non-LOAD type segments. */ + if (info->dlpi_phdr[i].p_type != PT_LOAD) { + continue; + } + if (i != 0) { + fprintf(ptd->core, ",\n"); + } + fprintf(ptd->core, "{\n"); + fprintf(ptd->core, "\"p_vaddr\": %u,\n", + (uintptr_t) info->dlpi_phdr[i].p_vaddr); + fprintf(ptd->core, "\"p_memsz\": %u\n", + (uintptr_t) info->dlpi_phdr[i].p_memsz); + fprintf(ptd->core, "}\n"); + } + fprintf(ptd->core, "]\n"); + fprintf(ptd->core, "}\n"); + return 0; +} + +static void PrintSegments(sstream_t *core) { + struct ProgramTableData data; + data.core = core; + data.first = 1; + dl_iterate_phdr(PrintSegmentsOne, &data); +} + +#else /* __GLIBC__ */ + +static void PrintSegments(sstream_t *core) { +} + +#endif /* __GLIBC__ */ + + +void PostMessage(const char *str); + + +uintptr_t SafeRead(uintptr_t a) { + /* TODO(bradnelson): use exception handling to recover from reads. */ + return *(uintptr_t*)a; +} + +static void StackWalk(sstream_t *core, struct NaClExceptionContext *context) { + uintptr_t next; + uintptr_t i; + int first = 1; + uintptr_t prog_ctr = context->prog_ctr; + uintptr_t frame_ptr = context->frame_ptr; + uintptr_t args_start; + + ssprintf(core, "\"frames\": [\n"); + for (;;) { + next = SafeRead(frame_ptr); + if (next <= frame_ptr || next == 0) { + break; + } + if (first) { + first = 0; + } else { + ssprintf(core, ","); + } + ssprintf(core, "{\n"); + ssprintf(core, "\"frame_ptr\": %u,\n", frame_ptr); + ssprintf(core, "\"prog_ctr\": %u,\n", prog_ctr); + ssprintf(core, "\"data\": [\n"); +#if defined(__x86_64__) + args_start = frame_ptr + 8; +#else + args_start = frame_ptr + 16; +#endif + for (i = args_start; i < next && i - args_start < 100; i += 4) { + if (i != args_start) { + ssprintf(core, ","); + } + ssprintf(core, "%u\n", SafeRead(i)); + } + ssprintf(core, "]\n"); + ssprintf(core, "}\n"); + + if (next - frame_ptr > 10000) break; +#if defined(__x86_64__) + prog_ctr = SafeRead(frame_ptr + 8); +#else + prog_ctr = SafeRead(frame_ptr + 4); +#endif + frame_ptr = next; + } + + ssprintf(core, "]\n"); +} + +void CrashHandler(struct NaClExceptionContext *context) { + sstream_t ss; + FILE* handle = fopen("naclcorejson", "wb"); + + ssinit(&ss); + ssprintf(&ss, "TRC: {\n"); + + ssprintf(&ss, "\"segments\": ["); + PrintSegments(&ss); + ssprintf(&ss, "],\n"); + + ssprintf(&ss, "\"handler\": {\n"); + ssprintf(&ss, "\"prog_ctr\": %u,\n", context->prog_ctr); + ssprintf(&ss, "\"stack_ptr\": %u,\n", context->stack_ptr); + ssprintf(&ss, "\"frame_ptr\": %u\n", context->frame_ptr); + ssprintf(&ss, "},\n"); + + StackWalk(&ss, context); + + ssprintf(&ss, "}\n"); + + if (handle != NULL) { + fprintf(handle, "%s", &ss.data[5]); + fclose(handle); + } + + PostMessage(ss.data); + + while(1); +} + +void NaClCrashDumpThreadDestructor(void *arg) { + munmap(arg, CRASH_STACK_COMPLETE_SIZE); +} + +int NaClCrashDumpInit(void) { + int result; + + assert(g_ExceptionHandlingEnabled == 0); + if (nacl_interface_query(NACL_IRT_DEV_EXCEPTION_HANDLING_v0_1, + &g_ExceptionHandling, + sizeof(g_ExceptionHandling)) == 0) { + fprintf(stderr, "ERROR: failed nacl_interface_query\n"); + return 0; + } + result = pthread_key_create(&g_CrashStackKey, NaClCrashDumpThreadDestructor); + assert(result == 0); + if (g_ExceptionHandling.exception_handler(CrashHandler, NULL) != 0) { + fprintf(stderr, "ERROR: failed exception_handler\n"); + return 0; + } + g_ExceptionHandlingEnabled = 1; + if (!NaClCrashDumpInitThread()) { + g_ExceptionHandlingEnabled = 0; + fprintf(stderr, "ERROR: failed InitThread\n"); + return 0; + } + return 1; +} + +int NaClCrashDumpInitThread(void) { + void *stack; + void *guard; + int result; + + if (!g_ExceptionHandlingEnabled) { + return 0; + } + /* + * NOTE: Setting up a per thread stack is only particularly interesting + * for stack overflow. + */ + stack = mmap(NULL, CRASH_STACK_COMPLETE_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(stack != MAP_FAILED); + guard = mmap(stack, CRASH_STACK_GUARD_SIZE, + PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(guard == stack); + pthread_setspecific(g_CrashStackKey, stack); + result = g_ExceptionHandling.exception_stack( + stack, CRASH_STACK_COMPLETE_SIZE); + return result == 0; +} diff --git a/native_client_sdk/src/examples/debugging/untrusted_crash_dump.h b/native_client_sdk/src/examples/debugging/untrusted_crash_dump.h new file mode 100644 index 0000000..8cd973a --- /dev/null +++ b/native_client_sdk/src/examples/debugging/untrusted_crash_dump.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012 The Native Client Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Untrusted crash dumper. + */ + +#ifndef NATIVE_CLIENT_SRC_UNTRUSTED_CRASH_DUMP_UNTRUSTED_CRASH_DUMP_H__ +#define NATIVE_CLIENT_SRC_UNTRUSTED_CRASH_DUMP_UNTRUSTED_CRASH_DUMP_H__ 1 + + +#ifdef __cplusplus +extern "C" { +#endif + +/* These return non-zero on success. */ +int NaClCrashDumpInit(void); +int NaClCrashDumpInitThread(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NATIVE_CLIENT_SRC_UNTRUSTED_CRASH_DUMP_UNTRUSTED_CRASH_DUMP_H__ */ diff --git a/native_client_sdk/src/examples/httpd.py b/native_client_sdk/src/examples/httpd.py index 9865129..2ad1d49 100755 --- a/native_client_sdk/src/examples/httpd.py +++ b/native_client_sdk/src/examples/httpd.py @@ -18,6 +18,21 @@ import SocketServer import sys import urlparse + +EXAMPLE_PATH=os.path.dirname(os.path.abspath(__file__)) +NACL_SDK_ROOT = os.getenv('NACL_SDK_ROOT', os.path.dirname(EXAMPLE_PATH)) + + +if os.path.exists(NACL_SDK_ROOT): + sys.path.append(os.path.join(NACL_SDK_ROOT, 'tools')) + import decode_dump + import getos +else: + NACL_SDK_ROOT=None + +last_nexe = None +last_nmf = None + logging.getLogger().setLevel(logging.INFO) # Using 'localhost' means that we only accept connections @@ -72,8 +87,55 @@ def KeyValuePair(str, sep='='): # A small handler that looks for '?quit=1' query in the path and shuts itself # down if it finds that parameter. class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + """ + path = self.translate_path(self.path) + f = None + if os.path.isdir(path): + if not self.path.endswith('/'): + # redirect browser - doing basically what apache does + self.send_response(301) + self.send_header("Location", self.path + "/") + self.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path) + ctype = self.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + self.send_error(404, "File not found") + return None + self.send_response(200) + self.send_header("Content-type", ctype) + fs = os.fstat(f.fileno()) + self.send_header("Content-Length", str(fs[6])) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.send_header('Cache-Control','no-cache, must-revalidate') + self.send_header('Expires','-1') + self.end_headers() + return f + def do_GET(self): - (_, _, _, query, _) = urlparse.urlsplit(self.path) + global last_nexe, last_nmf + (_, _, path, query, _) = urlparse.urlsplit(self.path) url_params = dict([KeyValuePair(key_value) for key_value in query.split('&')]) if 'quit' in url_params and '1' in url_params['quit']: @@ -84,8 +146,31 @@ class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): self.server.shutdown() return + if path.endswith('.nexe'): + last_nexe = path + if path.endswith('.nmf'): + last_nmf = path + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + def do_POST(self): + (_, _,path, query, _) = urlparse.urlsplit(self.path) + if 'Content-Length' in self.headers: + if not NACL_SDK_ROOT: + self.wfile('Could not find NACL_SDK_ROOT to decode trace.') + return + data = self.rfile.read(int(self.headers['Content-Length'])) + nexe = '.' + last_nexe + nmf = '.' + last_nmf + addr = os.path.join(NACL_SDK_ROOT, 'toolchain', + getos.GetPlatform() + '_x86_newlib', + 'bin', 'x86_64-nacl-addr2line') + decoder = decode_dump.CoreDecoder(nexe, nmf, addr, None, None) + info = decoder.Decode(data) + trace = decoder.StackTrace(info) + decoder.PrintTrace(trace, sys.stdout) + decoder.PrintTrace(trace, self.wfile) + def Run(server_address, server_class=QuittableHTTPServer, diff --git a/native_client_sdk/src/tools/decode_dump.py b/native_client_sdk/src/tools/decode_dump.py new file mode 100755 index 0000000..e2717ff --- /dev/null +++ b/native_client_sdk/src/tools/decode_dump.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# Copyright (c) 2012 The Native Client Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Utility to decode a crash dump generated by untrusted_crash_dump.[ch] + +Currently this produces a simple stack trace. +""" + +import json +import optparse +import os +import posixpath +import subprocess +import sys + + +class CoreDecoder(object): + """Class to process core dumps.""" + + def __init__(self, main_nexe, nmf_filename, + addr2line, library_paths, platform): + """Construct and object to process core dumps. + + Args: + main_nexe: nexe to resolve NaClMain references from. + nmf_filename: nmf to resovle references from. + addr2line: path to appropriate addr2line. + library_paths: list of paths to search for libraries. + platform: platform string to use in nmf files. + """ + self.main_nexe = main_nexe + self.nmf_filename = nmf_filename + if nmf_filename == '-': + self.nmf_data = {} + else: + self.nmf_data = json.load(open(nmf_filename)) + self.addr2line = addr2line + self.library_paths = library_paths + self.platform = platform + + def _SelectModulePath(self, filename): + """Select which path to get a module from. + + Args: + filename: filename of a module (as appears in phdrs). + Returns: + Full local path to the file. + Derived by consulting the manifest. + """ + # For some names try the main nexe. + # NaClMain is the argv[0] setup in sel_main.c + # (null) shows up in chrome. + if self.main_nexe is not None and filename in ['NaClMain', '(null)']: + return self.main_nexe + filepart = posixpath.basename(filename) + nmf_entry = self.nmf_data.get('files', {}).get(filepart, {}) + nmf_url = nmf_entry.get(self.platform, {}).get('url') + # Try filename directly if not in manifest. + if nmf_url is None: + return filename + # Look for the module relative to the manifest (if any), + # then in other search paths. + paths = [] + if self.nmf_filename != '-': + paths.append(os.path.dirname(self.nmf_filename)) + paths.extend(self.library_paths) + for path in paths: + pfilename = os.path.join(path, nmf_url) + if os.path.exists(pfilename): + return pfilename + # If nothing else, try the path directly. + return filename + + def _DecodeAddressSegment(self, segments, address): + """Convert an address to a segment relative one, plus filename. + + Args: + segments: a list of phdr segments. + address: a process wide code address. + Returns: + A tuple of filename and segment relative address. + """ + for segment in segments: + for phdr in segment['dlpi_phdr']: + start = segment['dlpi_addr'] + phdr['p_vaddr'] + end = start + phdr['p_memsz'] + if address >= start and address < end: + return (segment['dlpi_name'], address - segment['dlpi_addr']) + return ('(null)', address) + + def _Addr2Line(self, segments, address): + """Use addr2line to decode a code address. + + Args: + segments: A list of phdr segments. + address: a code address. + Returns: + A list of dicts containing: function, filename, lineno. + """ + filename, address = self._DecodeAddressSegment(segments, address) + filename = self._SelectModulePath(filename) + if not os.path.exists(filename): + return [{ + 'function': 'Unknown_function', + 'filename': 'unknown_file', + 'lineno': -1, + }] + # Use address - 1 to get the call site instead of the line after. + address -= 1 + cmd = [ + self.addr2line, '-f', '--inlines', '-e', filename, '0x%08x' % address, + ] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + process_stdout, _ = process.communicate() + assert process.returncode == 0 + lines = process_stdout.splitlines() + assert len(lines) % 2 == 0 + results = [] + for index in xrange(len(lines) / 2): + func = lines[index * 2] + afilename, lineno = lines[index * 2 + 1].split(':', 1) + results.append({ + 'function': func, + 'filename': afilename, + 'lineno': int(lineno), + }) + return results + + def Decode(self, text): + core = json.loads(text) + for frame in core['frames']: + frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr']) + return core + + + def LoadAndDecode(self, core_path): + """Given a core.json file, load and embellish with decoded addresses. + + Args: + core_path: source file containing a dump. + Returns: + An embelished core dump dict (decoded code addresses). + """ + core = json.load(open(core_path)) + for frame in core['frames']: + frame['scopes'] = self._Addr2Line(core['segments'], frame['prog_ctr']) + return core + + def StackTrace(self, info): + """Convert a decoded core.json dump to a simple stack trace. + + Args: + info: core.json info with decoded code addresses. + Returns: + A list of dicts with filename, lineno, function (deepest first). + """ + trace = [] + for frame in info['frames']: + for scope in frame['scopes']: + trace.append(scope) + return trace + + def PrintTrace(self, trace, out): + """Print a trace to a file like object. + + Args: + trace: A list of [filename, lineno, function] (deepest first). + out: file like object to output the trace to. + """ + for scope in trace: + out.write('%s at %s:%d\n' % ( + scope['function'], + scope['filename'], + scope['lineno'])) + + +def Main(args): + parser = optparse.OptionParser( + usage='USAGE: %prog [options] <core.json>') + parser.add_option('-m', '--main-nexe', dest='main_nexe', + help='nexe to resolve NaClMain references from') + parser.add_option('-n', '--nmf', dest='nmf_filename', default='-', + help='nmf to resolve references from') + parser.add_option('-a', '--addr2line', dest='addr2line', + help='path to appropriate addr2line') + parser.add_option('-L', '--library-path', dest='library_paths', + action='append', default=[], + help='path to search for shared libraries') + parser.add_option('-p', '--platform', dest='platform', + help='platform in a style match nmf files') + options, args = parser.parse_args(args) + if len(args) != 1: + parser.print_help() + sys.exit(1) + decoder = CoreDecoder( + main_nexe=options.main_nexe, + nmf_filename=options.nmf_filename, + addr2line=options.add2line, + library_paths=options.library_paths, + platform=options.platform) + info = decoder.LoadAndDecode(args[0]) + trace = decoder.StackTrace(info) + decoder.PrintTrace(trace, sys.stdout) + + +if __name__ == '__main__': + Main(sys.argv[1:]) |