From 5dc80dd07ad75d68cfea2babe64d421eb7b07ba3 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Wed, 1 Aug 2018 14:50:40 -0400 Subject: [PATCH 1/2] webgl: Add feature to store backtraces for each WebGL API call for easier debugging. --- Cargo.lock | 1 + components/canvas/Cargo.toml | 3 ++ components/canvas/gl_context.rs | 13 ++++++--- components/canvas/webgl_thread.rs | 25 +++++++++++++---- components/canvas_traits/Cargo.toml | 3 ++ components/canvas_traits/webgl.rs | 14 ++++++++-- components/script/Cargo.toml | 2 ++ .../script/dom/webglrenderingcontext.rs | 28 +++++++++++++++++-- components/script/lib.rs | 2 ++ components/servo/Cargo.toml | 5 ++++ ports/libsimpleservo/Cargo.toml | 1 + ports/servo/Cargo.toml | 1 + 12 files changed, 82 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85859bf7c723..6d839d69c0e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2981,6 +2981,7 @@ version = "0.0.1" dependencies = [ "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "audio-video-metadata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "bluetooth_traits 0.0.1", diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index d65c1b4b1107..4006947acf66 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -9,6 +9,9 @@ publish = false name = "canvas" path = "lib.rs" +[features] +webgl_backtrace = ["canvas_traits/webgl_backtrace"] + [dependencies] azure = {git = "https://github.com/servo/rust-azure"} canvas_traits = {path = "../canvas_traits"} diff --git a/components/canvas/gl_context.rs b/components/canvas/gl_context.rs index 00e3f3ba4a9b..c90ecec9ccab 100644 --- a/components/canvas/gl_context.rs +++ b/components/canvas/gl_context.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use canvas_traits::webgl::{WebGLCommand, WebGLVersion}; +use canvas_traits::webgl::{WebGLCommand, WebGLVersion, WebGLCommandBacktrace}; use compositing::compositor_thread::{CompositorProxy, self}; use euclid::Size2D; use gleam::gl; @@ -144,13 +144,18 @@ impl GLContextWrapper { } } - pub fn apply_command(&self, cmd: WebGLCommand, state: &mut GLState) { + pub fn apply_command( + &self, + cmd: WebGLCommand, + backtrace: WebGLCommandBacktrace, + state: &mut GLState + ) { match *self { GLContextWrapper::Native(ref ctx) => { - WebGLImpl::apply(ctx, state, cmd); + WebGLImpl::apply(ctx, state, cmd, backtrace); } GLContextWrapper::OSMesa(ref ctx) => { - WebGLImpl::apply(ctx, state, cmd); + WebGLImpl::apply(ctx, state, cmd, backtrace); } } } diff --git a/components/canvas/webgl_thread.rs b/components/canvas/webgl_thread.rs index 9256692599a2..4a2466d64e36 100644 --- a/components/canvas/webgl_thread.rs +++ b/components/canvas/webgl_thread.rs @@ -137,8 +137,8 @@ impl WebGLThread { WebGLMsg::RemoveContext(ctx_id) => { self.remove_webgl_context(ctx_id); }, - WebGLMsg::WebGLCommand(ctx_id, command) => { - self.handle_webgl_command(ctx_id, command); + WebGLMsg::WebGLCommand(ctx_id, command, backtrace) => { + self.handle_webgl_command(ctx_id, command, backtrace); }, WebGLMsg::WebVRCommand(ctx_id, command) => { self.handle_webvr_command(ctx_id, command); @@ -164,10 +164,15 @@ impl WebGLThread { } /// Handles a WebGLCommand for a specific WebGLContext - fn handle_webgl_command(&mut self, context_id: WebGLContextId, command: WebGLCommand) { + fn handle_webgl_command( + &mut self, + context_id: WebGLContextId, + command: WebGLCommand, + backtrace: WebGLCommandBacktrace, + ) { let data = Self::make_current_if_needed_mut(context_id, &mut self.contexts, &mut self.bound_context_id); if let Some(data) = data { - data.ctx.apply_command(command, &mut data.state); + data.ctx.apply_command(command, backtrace, &mut data.state); } } @@ -670,7 +675,8 @@ impl WebGLImpl { pub fn apply( ctx: &GLContext, state: &mut GLState, - command: WebGLCommand + command: WebGLCommand, + _backtrace: WebGLCommandBacktrace, ) { match command { WebGLCommand::GetContextAttributes(ref sender) => @@ -1191,7 +1197,14 @@ impl WebGLImpl { // TODO: update test expectations in order to enable debug assertions let error = ctx.gl().get_error(); if error != gl::NO_ERROR { - error!("Last GL operation failed: {:?}", command) + error!("Last GL operation failed: {:?}", command); + #[cfg(feature = "webgl_backtrace")] + { + error!("Backtrace from failed WebGL API:\n{}", _backtrace.backtrace); + if let Some(backtrace) = _backtrace.js_backtrace { + error!("JS backtrace from failed WebGL API:\n{}", backtrace); + } + } } assert_eq!(error, gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error); } diff --git a/components/canvas_traits/Cargo.toml b/components/canvas_traits/Cargo.toml index b5599232b5ce..187b46d617e1 100644 --- a/components/canvas_traits/Cargo.toml +++ b/components/canvas_traits/Cargo.toml @@ -9,6 +9,9 @@ publish = false name = "canvas_traits" path = "lib.rs" +[features] +webgl_backtrace = [] + [dependencies] cssparser = "0.24.0" euclid = "0.19" diff --git a/components/canvas_traits/webgl.rs b/components/canvas_traits/webgl.rs index 8f9f4e70a31c..ad10ed7edd0f 100644 --- a/components/canvas_traits/webgl.rs +++ b/components/canvas_traits/webgl.rs @@ -24,6 +24,14 @@ pub use ::webgl_channel::WebGLPipeline; /// Entry point channel type used for sending WebGLMsg messages to the WebGL renderer. pub use ::webgl_channel::WebGLChan; +#[derive(Clone, Deserialize, Serialize)] +pub struct WebGLCommandBacktrace { + #[cfg(feature = "webgl_backtrace")] + pub backtrace: String, + #[cfg(feature = "webgl_backtrace")] + pub js_backtrace: Option, +} + /// WebGL Message API #[derive(Deserialize, Serialize)] pub enum WebGLMsg { @@ -35,7 +43,7 @@ pub enum WebGLMsg { /// Drops a WebGLContext. RemoveContext(WebGLContextId), /// Runs a WebGLCommand in a specific WebGLContext. - WebGLCommand(WebGLContextId, WebGLCommand), + WebGLCommand(WebGLContextId, WebGLCommand, WebGLCommandBacktrace), /// Runs a WebVRCommand in a specific WebGLContext. WebVRCommand(WebGLContextId, WebVRCommand), /// Locks a specific WebGLContext. Lock messages are used for a correct synchronization @@ -121,8 +129,8 @@ impl WebGLMsgSender { /// Send a WebGLCommand message #[inline] - pub fn send(&self, command: WebGLCommand) -> WebGLSendResult { - self.sender.send(WebGLMsg::WebGLCommand(self.ctx_id, command)) + pub fn send(&self, command: WebGLCommand, backtrace: WebGLCommandBacktrace) -> WebGLSendResult { + self.sender.send(WebGLMsg::WebGLCommand(self.ctx_id, command, backtrace)) } /// Send a WebVRCommand message diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 1c8feb0f17ed..5ad8c3df8fff 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -16,6 +16,7 @@ debugmozjs = ['mozjs/debugmozjs'] unstable = [] unrooted_must_root_lint = ["script_plugins/unrooted_must_root_lint"] default = ["unrooted_must_root_lint"] +webgl_backtrace = ["backtrace", "canvas_traits/webgl_backtrace"] [build-dependencies] cmake = "0.1" @@ -29,6 +30,7 @@ tinyfiledialogs = "3.0" [dependencies] app_units = "0.7" audio-video-metadata = "0.1.4" +backtrace = {version = "0.3", optional = true} base64 = "0.6" bitflags = "1.0" bluetooth_traits = {path = "../bluetooth_traits"} diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 7367a38fdcf2..754637ad52b8 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -2,9 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#[cfg(feature = "webgl_backtrace")] +use backtrace::Backtrace; use byteorder::{ByteOrder, NativeEndian, WriteBytesExt}; use canvas_traits::canvas::{byte_swap, multiply_u8_pixel}; -use canvas_traits::webgl::{DOMToTextureCommand, Parameter}; +use canvas_traits::webgl::{DOMToTextureCommand, Parameter, WebGLCommandBacktrace}; use canvas_traits::webgl::{TexParameter, WebGLCommand, WebGLContextShareMode, WebGLError}; use canvas_traits::webgl::{WebGLFramebufferBindingRequest, WebGLMsg, WebGLMsgSender}; use canvas_traits::webgl::{WebGLProgramId, WebGLResult, WebGLSLVersion, WebGLSender}; @@ -316,7 +318,7 @@ impl WebGLRenderingContext { #[inline] pub fn send_command(&self, command: WebGLCommand) { - self.webgl_sender.send(command).unwrap(); + self.webgl_sender.send(command, capture_webgl_backtrace(self)).unwrap(); } #[inline] @@ -1189,6 +1191,25 @@ impl WebGLRenderingContext { } } +#[cfg(not(feature = "webgl_backtrace"))] +#[inline] +pub fn capture_webgl_backtrace(_: &T) -> WebGLCommandBacktrace { + WebGLCommandBacktrace {} +} + +#[cfg(feature = "webgl_backtrace")] +#[cfg_attr(feature = "webgl_backtrace", allow(unsafe_code))] +pub fn capture_webgl_backtrace(obj: &T) -> WebGLCommandBacktrace { + let bt = Backtrace::new(); + unsafe { + capture_stack!(in(obj.global().get_cx()) let stack); + WebGLCommandBacktrace { + backtrace: format!("{:?}", bt), + js_backtrace: stack.and_then(|s| s.as_string(None)), + } + } +} + impl Drop for WebGLRenderingContext { fn drop(&mut self) { let _ = self.webgl_sender.send_remove(); @@ -1521,9 +1542,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext { let (sender, receiver) = webgl_channel().unwrap(); // If the send does not succeed, assume context lost + let backtrace = capture_webgl_backtrace(self); if self .webgl_sender - .send(WebGLCommand::GetContextAttributes(sender)) + .send(WebGLCommand::GetContextAttributes(sender), backtrace) .is_err() { return None; diff --git a/components/script/lib.rs b/components/script/lib.rs index f8341323466a..66bc4ac40ebc 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -19,6 +19,8 @@ extern crate app_units; extern crate audio_video_metadata; +#[cfg(feature = "webgl_backtrace")] +extern crate backtrace; extern crate base64; #[macro_use] extern crate bitflags; diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 423d6814810e..ea0a3d1d3a26 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -24,6 +24,11 @@ unstable = [ "profile/unstable", "script/unstable", ] +webgl_backtrace = [ + "script/webgl_backtrace", + "canvas/webgl_backtrace", + "canvas_traits/webgl_backtrace", +] [dependencies] bluetooth_traits = {path = "../bluetooth_traits"} diff --git a/ports/libsimpleservo/Cargo.toml b/ports/libsimpleservo/Cargo.toml index 6cb4d4f6c3cd..75cde40fd23a 100644 --- a/ports/libsimpleservo/Cargo.toml +++ b/ports/libsimpleservo/Cargo.toml @@ -42,3 +42,4 @@ debugmozjs = ["libservo/debugmozjs"] unstable = ["libservo/unstable"] googlevr = ["libservo/googlevr"] oculusvr = ["libservo/oculusvr"] +webgl_backtrace = ["libservo/webgl_backtrace"] \ No newline at end of file diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index a31f63065149..e128738b3658 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -30,6 +30,7 @@ webdriver = ["libservo/webdriver"] energy-profiling = ["libservo/energy-profiling"] debugmozjs = ["libservo/debugmozjs"] unstable = ["libservo/unstable"] +webgl_backtrace = ["libservo/webgl_backtrace"] [target.'cfg(not(target_os = "android"))'.dependencies] backtrace = "0.3" From 06bca43aeef990da8f02cd8b814329f7607365dd Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Wed, 1 Aug 2018 14:54:08 -0400 Subject: [PATCH 2/2] script: Optionally store backtraces when throwing DOM exceptions. --- components/script/Cargo.toml | 1 + components/script/dom/bindings/error.rs | 30 +++++++++++++++++++++++++ components/script/lib.rs | 2 +- components/servo/Cargo.toml | 1 + ports/libsimpleservo/Cargo.toml | 3 ++- ports/servo/Cargo.toml | 1 + python/servo/build_commands.py | 5 +++++ python/servo/command_base.py | 2 ++ servobuild.example | 8 +++++++ 9 files changed, 51 insertions(+), 2 deletions(-) diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 5ad8c3df8fff..ca859bb472a5 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -17,6 +17,7 @@ unstable = [] unrooted_must_root_lint = ["script_plugins/unrooted_must_root_lint"] default = ["unrooted_must_root_lint"] webgl_backtrace = ["backtrace", "canvas_traits/webgl_backtrace"] +js_backtrace = ["backtrace"] [build-dependencies] cmake = "0.1" diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index ce045b2c9d3d..64b581f316df 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -4,6 +4,10 @@ //! Utilities to throw exceptions from Rust bindings. +#[cfg(feature = "js_backtrace")] +use backtrace::Backtrace; +#[cfg(feature = "js_backtrace")] +use dom::bindings::cell::DomRefCell; use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods; use dom::bindings::codegen::PrototypeList::proto_id_to_name; use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, ToJSValConvertible}; @@ -24,6 +28,11 @@ use js::rust::wrappers::JS_SetPendingException; use libc::c_uint; use std::slice::from_raw_parts; +/// An optional stringified JS backtrace and stringified native backtrace from the +/// the last DOM exception that was reported. +#[cfg(feature = "js_backtrace")] +thread_local!(static LAST_EXCEPTION_BACKTRACE: DomRefCell, String)>> = DomRefCell::new(None)); + /// DOM exceptions that can be thrown by a native DOM method. #[derive(Clone, Debug, MallocSizeOf)] pub enum Error { @@ -90,6 +99,16 @@ pub type ErrorResult = Fallible<()>; /// Set a pending exception for the given `result` on `cx`. pub unsafe fn throw_dom_exception(cx: *mut JSContext, global: &GlobalScope, result: Error) { + #[cfg(feature = "js_backtrace")] + { + capture_stack!(in(cx) let stack); + let js_stack = stack.and_then(|s| s.as_string(None)); + let rust_stack = Backtrace::new(); + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + *backtrace.borrow_mut() = Some((js_stack, format!("{:?}", rust_stack))); + }); + } + let code = match result { Error::IndexSize => DOMErrorName::IndexSizeError, Error::NotFound => DOMErrorName::NotFoundError, @@ -244,6 +263,17 @@ pub unsafe fn report_pending_exception(cx: *mut JSContext, dispatch_event: bool) "Error at {}:{}:{} {}", error_info.filename, error_info.lineno, error_info.column, error_info.message ); + #[cfg(feature = "js_backtrace")] + { + LAST_EXCEPTION_BACKTRACE.with(|backtrace| { + if let Some((js_backtrace, rust_backtrace)) = backtrace.borrow_mut().take() { + if let Some(stack) = js_backtrace { + eprintln!("JS backtrace:\n{}", stack); + } + eprintln!("Rust backtrace:\n{}", rust_backtrace); + } + }); + } if dispatch_event { GlobalScope::from_context(cx).report_an_error(error_info, value.handle()); diff --git a/components/script/lib.rs b/components/script/lib.rs index 66bc4ac40ebc..097f69d78083 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -19,7 +19,7 @@ extern crate app_units; extern crate audio_video_metadata; -#[cfg(feature = "webgl_backtrace")] +#[cfg(any(feature = "webgl_backtrace", feature = "js_backtrace"))] extern crate backtrace; extern crate base64; #[macro_use] diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index ea0a3d1d3a26..7d2a78952159 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -17,6 +17,7 @@ webdriver = ["webdriver_server"] energy-profiling = ["profile_traits/energy-profiling"] debugmozjs = ["script/debugmozjs"] googlevr = ["webvr/googlevr"] +js_backtrace = ["script/js_backtrace"] webrender_debugger = ["webrender/debugger"] oculusvr = ["webvr/oculusvr"] unstable = [ diff --git a/ports/libsimpleservo/Cargo.toml b/ports/libsimpleservo/Cargo.toml index 75cde40fd23a..5af57fe8e698 100644 --- a/ports/libsimpleservo/Cargo.toml +++ b/ports/libsimpleservo/Cargo.toml @@ -42,4 +42,5 @@ debugmozjs = ["libservo/debugmozjs"] unstable = ["libservo/unstable"] googlevr = ["libservo/googlevr"] oculusvr = ["libservo/oculusvr"] -webgl_backtrace = ["libservo/webgl_backtrace"] \ No newline at end of file +webgl_backtrace = ["libservo/webgl_backtrace"] +js_backtrace = ["libservo/js_backtrace"] diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index e128738b3658..298dae298f96 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -31,6 +31,7 @@ energy-profiling = ["libservo/energy-profiling"] debugmozjs = ["libservo/debugmozjs"] unstable = ["libservo/unstable"] webgl_backtrace = ["libservo/webgl_backtrace"] +js_backtrace = ["libservo/js_backtrace"] [target.'cfg(not(target_os = "android"))'.dependencies] backtrace = "0.3" diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index 12287a0c8dc1..ea9424740634 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -257,6 +257,11 @@ def build(self, target=None, release=False, dev=False, jobs=None, if debug_mozjs: features += ["debugmozjs"] + if self.config["build"]["webgl-backtrace"]: + features += ["webgl-backtrace"] + if self.config["build"]["dom-backtrace"]: + features += ["dom-backtrace"] + if features: opts += ["--features", "%s" % ' '.join(features)] diff --git a/python/servo/command_base.py b/python/servo/command_base.py index b335dcd69ebb..8c9d5c61cbd4 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -288,6 +288,8 @@ def resolverelative(category, key): self.config["build"].setdefault("rustflags", "") self.config["build"].setdefault("incremental", None) self.config["build"].setdefault("thinlto", False) + self.config["build"].setdefault("webgl-backtrace", False) + self.config["build"].setdefault("dom-backtrace", False) self.config.setdefault("android", {}) self.config["android"].setdefault("sdk", "") diff --git a/servobuild.example b/servobuild.example index 2df2dc256e1f..2070da1a59c6 100644 --- a/servobuild.example +++ b/servobuild.example @@ -32,6 +32,14 @@ android = false # Set "debug-mozjs" or use `mach build --debug-mozjs` to build a debug spidermonkey. debug-mozjs = false +# When a GL error occurs as a result of a WebGL operation, print the stack trace for the content +# JS and native Rust code that triggered the failed operation. Warning: very slow. +webgl-backtrace = false + +# When a DOM exception is reported, print the stack trace for the content JS and native Rust code +# that triggered it. +dom-backtrace = false + # Set to the path to your ccache binary to enable caching of compiler outputs #ccache = "/usr/local/bin/ccache"