summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
authornoelallen@google.com <noelallen@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-22 20:33:15 +0000
committernoelallen@google.com <noelallen@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-22 20:33:15 +0000
commit283fa9d004f8468823261622b62ad772f0211a6a (patch)
treee3e4664f559639991449f1d42fca81fdc93802ac /native_client_sdk
parentffe4614b9a9b779bbe1afb91c430dc6de5d4a093 (diff)
downloadchromium_src-283fa9d004f8468823261622b62ad772f0211a6a.zip
chromium_src-283fa9d004f8468823261622b62ad772f0211a6a.tar.gz
chromium_src-283fa9d004f8468823261622b62ad772f0211a6a.tar.bz2
Add stack trace example to NaCl SDK
Add example directory and files Add example to bundle (build_sdk.py) Add functions to find sel_ldr, plugin, irt, etc Add decode_dump.py to decode untrusted crash information. Update httpd to support XMLHttpRequests to generate stack trace R=bradnelson@chromium.org BUG=114444 Review URL: https://chromiumcodereview.appspot.com/9716024 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128283 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
-rw-r--r--native_client_sdk/src/examples/debugging/Makefile109
-rw-r--r--native_client_sdk/src/examples/debugging/debugging.html144
-rw-r--r--native_client_sdk/src/examples/debugging/hello_world.c289
-rw-r--r--native_client_sdk/src/examples/debugging/hello_world.nmf6
-rw-r--r--native_client_sdk/src/examples/debugging/string_stream.c46
-rw-r--r--native_client_sdk/src/examples/debugging/string_stream.h31
-rw-r--r--native_client_sdk/src/examples/debugging/untrusted_crash_dump.c270
-rw-r--r--native_client_sdk/src/examples/debugging/untrusted_crash_dump.h27
-rwxr-xr-xnative_client_sdk/src/examples/httpd.py87
-rwxr-xr-xnative_client_sdk/src/tools/decode_dump.py209
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:])