ebook img

Rivet: Browser-agnostic Remote Debugging for Web Applications PDF

13 Pages·2012·0.5 MB·English
by  
Save to my drive
Quick download
Download
Most books are stored in the elastic cloud where traffic is expensive. For this reason, we have a limit on daily download.

Preview Rivet: Browser-agnostic Remote Debugging for Web Applications

Rivet: Browser-agnostic Remote Debugging for Web Applications JamesMickens MicrosoftResearch Abstract cationbugs[13,14]. Thus,aneffectiveremotedebugger mustbeabletoinspectpagesthatruninsidearbitrary,un- Rivet is the first fully-featured, browser-agnostic re- modifiedcommoditybrowsers. mote debugger for web applications. Using Rivet, de- velopers can inspect and modify the state of live web 1.1 OurSolution: Rivet pages that are running inside unmodified end-user web browsers. Thisallowsdeveloperstoexplorerealapplica- In this paper, we introduce Rivet, a new framework tionbugsinthecontextoftheactualmachinesonwhich for remotely debugging web applications. Rivet lever- those bugs occur. To make an application Rivet-aware, agesJavaScript’sbuilt-inreflectioncapabilitiestoprovide developerssimplyaddtheRivetJavaScriptlibrarytothe a browser-agnostic debugging system. To use Rivet, an client-sideportionoftheapplication. Later, whenauser application includes the Rivet JavaScript library. When detects a problem with the application, the user informs thepageloads,theRivetlibraryinstrumentstheruntime, Rivet; in turn, Rivet pauses the application and notifies trackingthecreationofvarioustypesofstatethattheap- aremotedebugserverthatadebuggablesessionisavail- plicationwouldotherwisebeunabletoexplicitlyenumer- able. Theservercanlaunchaninteractivedebuggerfront- ate. Later, if the user detects a problem with the web end for a human developer, or use Rivet’s live patching page, she can instruct the page to open a debugging ses- mechanism to automatically install a fix on the client or sionwitharemotedeveloper. Thedeveloper-sidedebug- run diagnostics for offline analysis. Experiments show ger communicates with the client-side Rivet framework thatRivetimposesnegligibleoverheadduringnormalap- using standard HTTP requests. The debugger is fully- plicationoperation. Atdebugtime,Rivet’snetworkfoot- featuredandsupportsstandardfacilitieslikebreakpoints, printissmall,andRivetiscomputationallyfastenoughto stacktraces,anddynamicvariablemodification. supportnon-trivialdiagnosticsandlivepatches. Many laypeople users will not be interested in assist- ingdeveloperswithlongdebuggingsessions. Thus,Rivet also supports an automated diagnostic mode. In this 1 Introduction mode, when the user or the application detects a prob- lem,thedebugserverautomaticallypushespre-generated Asanapplicationbecomesmorecomplex,itinevitably test scripts to the client. The client executes the scripts accumulates more bugs, and a sophisticated debugging and sends the results back to the server, where they can frameworkbecomesinvaluableforunderstandingtheap- beanalyzedlater. Inthisfashion,Rivetsupportsthegen- plication’sbehavior. Withmodernwebbrowsersprovid- erationofquickerrorreportsforliveapplicationdeploy- ingincreasinglypowerfulprogrammingabstractionslike ments. Thismechanismalsofacilitatesthedistributionof threading[21]andbitmaprendering[9],webpageshave livepatchestoclientmachines. become more complex, and thus more difficult to de- bug. Unfortunately, current debuggers for web applica- 1.2 Contributions tionshaveseverallimitations. Although modern browsers ship with feature-rich In summary, Rivet is the first fully-featured, browser- JavaScriptdebuggers,thesedebuggerscantypicallyonly agnosticremotedebuggerforwebapplications. Rivetof- be used to examine pages running on the local browser. fers several advantages beyond its browser agnosticism. This makes it impossible for developers to examine real With respect to security, Rivet only allows a remote de- bugsfrompagesrunningonwebbrowsersinthewild. A velopertodebugpagesfromherorigin;incontrast,prior fewbrowsersdosupportaninterfaceforremotedebugger (browser-specific)remotedebuggersallowadeveloperto attachment[4];however,thatinterfaceistiedtoaspecific inspectanypageinanybrowsertab. Withrespecttous- browser engine, meaning that the remote debugger can- ability, Rivet does not require end-users to reconfigure notbeusedwithotherbrowsertypes.Giventheempirical theirbrowsersormanageotherclient-sidedebuggingin- diversityofthebrowsersusedinthewild[20],andthein- frastructure. Thisisimportant,sincewidelydeployedap- abilityofwebdeveloperstodictatewhichbrowserstheir plications are primarily used by non-technical laypeople userswillemploy,remotedebuggingframeworksthatare wholackthesophisticationtoconfigurenetworkportsor tied to a particular browser engine will have poor cover- attach their browser to a remote debugging server. Prior age for exposing bugs in the wild. Indeed, the quirks of remotedebuggersrequireenduserstoperformsuchcon- individualbrowsersareimportantinducersofwebappli- figurationtasks. Unmodified Browser ofthreadingcontextthatwediscussinmoredetailbelow. JavaScriptisanevent-drivenlanguage,andeachframeor Rivet.js webworkerhasitsowneventdispatchloop.Eachcontext Interactive alsohasitsownJavaScriptnamespace. Remote debugging Event loop debug server console InaRivet-enabledapplication,eacheventcontextcon- tains its own copy of the Rivet library. When the appli- cation loads, each instance of the library will instrument Rivet.js Rivet.js HTTP its local JavaScript runtime. Later, at debug time, these modules will coordinate to pause the application, com- municatewiththeremotedebugserver,andexaminelocal stateonbehalfofthatserver. Automated On the developer side, the debug server may be con- test scripts nected to a front-end debugging GUI that allows the de- veloper to interactively examine the client portion of the Figure1:ExampleofaRivetwebpagewiththreeframes. application.Alternatively,thedevelopercanconfigurethe debugservertorunautomateddiagnosticsonamisbehav- ing web page. The latter facility is useful for debugging Rivet hides configuration details from the user by im- widelydeployedapplicationsinwhichmostusersarenot plementingitsclient-sidecomponenttotallyinJavaScript. professionaltestersandlackaninterestinactivelyassist- This gives the web developer complete control over the ingthedeveloperinherdebuggingefforts. Forapopular client-side debugging settings, while restricting the de- web page, the debug server may interface with a larger veloper to accessing content from that developer’s ori- distributedsystemthatcollatesandanalyzesmassivesets gin. Rivet’s client-side library is partially inspired by oferrorreports[10]. Mugshot [14], a logging-and-replay framework for web applications that also leverages JavaScript reflection to 2.1 ExposingApplicationState work on unmodified browsers. However, as we describe in Section 5, Rivet has three significant advantages over Thepurposeofthe client-sideRivetlibraryis tocom- Mugshot. First, Rivet does not require developers to de- municatewitharemotedebuggerandgivethatdebugger ploy a proxy web server to assist with application intro- awaytoinspecttheclient-sidestate. Inparticular,Rivet spection;suchproxiesmaybeexpensiveinbothcostand must allow the debugger to walk the JavaScript object performance. Second,Rivetallowsdeveloperstoexplore graph that represents the application’s state. Each event bugsontheactualend-userbrowsersinwhichthosebugs context has its own JavaScript namespace and possesses occur. Thisin-situperspectiveincreasesRivet’sbugcov- one or more partially overlapping object graphs. Below, eragerelativetoMugshot—Mugshottriestorecreatebugs we describe each type of object graph and how Rivet on the developer-side, but it cannot reliably replay bugs providesanintrospectionframeworkforthatgraph. whichdependonclient-sidestatethatresidesbeneaththe JavaScriptlayer.Third,Rivetperformslessruntimeinter- The global namespace forest: JavaScript supports a positioning than Mugshot. Since browser interposition- powerful reflection model. By calling the built-in ingcanbefragile[13],thismakesRivetmorerobustthan Object.getOwnPropertyNames()method,anap- Mugshot. plication can discover all of the properties defined on Ourevaluationshowsthat,throughcarefuldesign,stan- an object. 1 JavaScript also makes the global names- dardHTTPconnectionscansupportafast,interactivere- paceexplicitlyaccessibleviathespecialwindowobject. motedebuggingprotocol. WealsoshowthatRivetintro- Thus, at debugging time, Rivet can discover the current duces negligible client-side overhead during normal ap- set of global variables by enumerating the properties of plicationoperation. Thus, Rivetisperformantenoughto the window variable. Rivet can then recursively reflect shipinsiderealapplicationdeployments. over the properties of each global variable, mapping the entireobjecttreerootedateachglobal. The Document Object Model (DOM) is a browser- 2 Architecture neutral API for exposing browser state to JavaScript programs [24]. With some notable exceptions that we Figure1depictsthearchitectureofaRivet-enabledweb application. Theclientportionoftheapplicationrunsin- 1JavaScript uses a prototype-based object model (x2.5), and sideanunmodifiedcommoditybrowser. Theapplication Object.getOwnPropertyNames() only enumerates properties founddirectlyontheobject. Thus, todiscoveranobject’sfullprop- consistsofoneormoreeventcontexts.Aneventcontextis erty list, Rivet must call Object.getOwnPropertyNames() on eitheraniframeorawebworker[21],aconstrainedtype theobjectitselfandoneachobjectintheprototypechain. function closureGenerator(obj){ objectgraphrootedinaglobalvariable;unfortunately,in var y = 41; non-trivialprograms,itisdifficultorimpossibleforade- function closure(){ veloper(orstaticcodeanalysis)toidentifysuch aliasing //closure() binds to non-local relationships. Furthermore, some closure variables are //variables obj and y . . . return obj.x + y; completely unreachable from the global namespace; in } Figure2,yissuchavariable. return closure; In JavaScript, all functions, including closures, are } first-class objects. To expose closure state to remote de- var globalObj = {x: 1}; //Create object literal var c = closureGenerator(globalObj); buggers,RivetusesaJavaScriptrewritertoassociateeach alert(c()); //Displays 42 closurewithtwodiagnosticfunctionsthatexposethenor- mally hidden scope. Figure 3 shows how Rivet rewrites the closure from Figure 2. The getScope () func- Figure2: ExampleofaJavaScriptclosure. tionreturnsanobjectwhosepropertiesreferencetheclo- sure variables. Note that getScope () is itself a closure, which lets it access the protected scope of the function closureGenerator(obj){ var y = 41; application-defined closure. When the remote debugger function closure(){ calls getScope (),itcanusestandardJavaScriptre- return obj.x + y; flectiontoenumeratethereturnedobject’spropertiesand } readtheclosurevariables. closure.__getScope__ = function(){ return {"obj": obj, "y": y}; Rivet adds a second diagnostic method to each clo- }; sure called evalInScope (). This method al- closure.__evalInScope__ = function(src){ lows a remote debugger to dynamically evaluate a piece return eval(src); of JavaScript code within the closure function’s scope }; return closure; chain. This allows the debugger to modify the value } of a closure variable (or any other variable within the function’s scope). As we explain in Section 2.5, evalInScope () also allows the debugger to dy- Figure3: RivetrewritestheclosurefromFigure2tosup- namicallygenerateanewfunctionthatisboundtoaclo- portexplorationbyremotedebuggers. sure’s hidden scope. This is useful in several scenarios, e.g.,whenintroducinganewdebuggingversionofaclo- describe below, all DOM state is accessible via prop- surethatautomaticallyproducesdiagnosticmessages. erties on the window object. For example, persistent Note that getScope () and local storage areas like cookies and DOM storage [25] evalInScope () are only invoked during a are exposed via window.document.cookie and debuggingsession,sorewrittenclosuresincurnoperfor- window.localStorage, respectively. For DOM mancepenaltyduringnormalapplicationexecution. Also state that is not explicitly accessible in this way, Rivet note that the closure rewriter can be implemented in a instruments the runtime to expose the state to remote variety of ways. For example, a developer-side IDE can debuggers. automatically rewrite JavaScript files. Alternatively, the client-side Rivet library can dynamically rewrite script Closurestate: ManyJavaScriptapplicationsmakeheavy code by interposing on eval() and DOM-mediated use of closures. A closure is a function that remembers injectionpointsforgeneratingscriptcode[15]. thevaluesofnon-localvariablesintheenclosingscopes. Importantly, Rivet will not break if some or all of an Figure2providesanexampleofaclosure. application’s closures are not rewritten. However, Rivet TheglobalJavaScriptnamespaceisexplicitlyboundto willnotbeabletodynamicallymodifythoseclosurefunc- the window variable. In contrast, a closure namespace tions. This will prevent Rivet from inserting dynamic is not bound to an explicit namespace object. Thus, in breakpoints into them (x2.3) or otherwise live-patching Figure 2, once closureGenerator(1) returns, nei- them. Rivet can use lexical analysis to identify closures therthereturnedclosurenoranyothercodecanexplicitly and prevent the developer from issuing forbidden opera- refer to the bound closure scope—only the closure itself tionsonnon-rewrittenclosures. Thisisanimportantfea- can access the bound variables obj and y. This is un- ture,sinceanapplicationmayimportexternalJavaScript fortunatefromtheperspectiveofdebugging,sinceknow- that the developer does not control (and thus cannot en- ing the values of closure variables can greatly assist the surewillberewritten). debugging of closure functions. Some closure variables In JavaScript, calling a function’s toString() maybealiasedbyglobalvariables,orreachablefromthe method conveniently returns the source code for that var button = document.getElementById("clicker"); remote debugger can enumerate the application-defined button.onclick = function(){ callbacks. Thus, when the Rivet library first loads, alert("DOM 0 handler!"); it wraps setTimeout(), setInterval(), and }; the AJAX object in logging code that records the button.addEventListener("onclick", function(){ application-defined handlers that are passed through alert("DOM 2 handler!"); the aforementioned interfaces. The wrapper code }); is similar to that used by the Mugshot logging and replay service [14]. Later, at debug time, the de- Figure 4: Registering GUI callbacks using the DOM 0 bugger can access the timer callbacks by examining modelandtheDOM2model. the special lists Rivet.timeoutCallbacks, Rivet.intervalCallbacks, and var req = new XMLHttpRequest(); Rivet.AJAXCallbacks. req.open(’GET’, "http://foo.com"); Rivet does not need to do anything to expose DOM req.onReadyStateChange = function(){ 0 handlers to the remote debugger—these handlers are if(req.readyState == 4){ attached to enumerable properties of the DOM nodes. alert(req.responseText); } In contrast, DOM 2 handlers are not discoverable by } reflection,soRivetmodifiestheclassdefinitionforDOM req.send(); nodes, wrapping the addEventListener() function that is used to register DOM 2 handlers. The wrapper Figure5: AnAJAXcallback. adds each newly registered handler to a list of functions associatedwitheachDOMnode;thislistproperty,called DOMNode.DOM2handlers, is a regular field that can function. Thus, Rivet does not need to maintain extra beaccessedviastandardJavaScriptreflection. metadata to store function source for subsequent inspec- tionbytheremotedebugger. Web Workers: A web application can launch a con- current JavaScript activity using the web worker facil- Event handler state: JavaScript applications can define ity [21]. Although a web worker runs in parallel with threetypesofcallbackfunctions. the spawning context, it has several limitations. Unlike (cid:15) Timer callbacks execute after a specified pe- a traditional thread, a web worker does not share the riod of time has elapsed. Callbacks regis- namespaceofitsparent;instead,theparentandthechild tered via setTimeout(f, waitMs) only ex- exchange pass-by-value strings over the asynchronous ecute once, whereas callbacks registered via postMessage() channel. Web workers can generate setInterval(f, periodMs) fire once every AJAXrequestsandregistertimercallbacks,buttheycan- periodMsmilliseconds. notinteractwiththeDOMtree. (cid:15) To respond to user input, applications define GUI An application launches a web worker by creating a callbacks on DOM nodes (each DOM node is new Worker object. The application calls the object’s theJavaScriptrepresentationofanHTMLtaginthe postMessage() function to send data to the worker; web page). Modern web browsers support two dif- the application receives data from the worker by reg- ferent registration models for GUI events [7]. The istering DOM 0 or DOM 2 handlers for the worker’s “DOM 0” model allows an application to register message event. At page load time, the Rivet library at most one handler for a given DOM node/event wraps the Worker class in logging code. Similar to type pair. Using the “DOM 2” model, an applica- Rivet’s wrapper code for the XMLHttpRequest class, tioncanregistermultipleeventhandlersforagiven the Worker shim allows Rivet to track the extant in- DOM node/event type pair. A node can simultane- stancesoftheclassandtheeventhandlersthathavebeen ouslyhaveaDOM0handlerandoneormoreDOM addedtothoseinstances.RivetalsoshimstheAJAXclass 2handlers. Figure4providesanexampleofthetwo and the timer callback registration functions inside each registrationmodels. workercontext. (cid:15) To asynchronously fetch new web data, an applica- tion creates an XMLHttpRequest object and de- 2.2 PausinganApplication fines an AJAX callback for the object [7]. The browser will fire this callback whenever it receives In the text above, we described how the client-side bytesfromtheremotewebserver. Figure5provides Rivet library exposes application state to the remote de- anexample. bugger. However, before the debugger can examine this The client-side Rivet framework must ensure that the state, the application must reach a stablepoint. A stable point occurs when Rivet-defined code is running within var lastEvalResult = "Start of breakpoint"; eachclient-sideeventcontext. Eachframeorwebworker var exprToEval = ""; canonlyexecuteasinglecallbackatanygiventime,soif while(lastEvalResult){ exprToEval = Rivet.breakpoint(lastEvalResult); aRivet-definedcallbackisrunninginaparticularcontext, //Uses a synchronous AJAX connection Rivetcanbeconfidentthatnootherapplicationcodeissi- //to return the result of the prior multaneouslyrunninginthatcontext.IfRivetcodeisrun- //debugger command and fetch a new one. lastEvalResult = eval(exprToEval); ninginallcontexts,thenRivethaseffectivelypausedthe } execution of all application-defined code. Rivet’s client- sideportioncanthencooperatewiththeremotedebugger toinspectandmodifyapplicationstateinanatomicfash- Figure6: TheRivetbreakpointimplementation. ion,withoutfearofconcurrentupdatesissuedbyregular applicationcode. toString()oftheunderlyingfunctionobject. Thede- The top-most frame in a Rivet-enabled application veloperinsertsbreakpointsatoneormorelocationsinthe is the coordinator for the pausing process. When the sourcecodeandtheninstructsthedebuggertoupdatethe user detects a problem with the application, she informs eventhandlerfunctionontheclientside. the Rivet library in the root frame, e.g., by clicking a Whentheclient-sideRivetframeworkreceivesthenew “panic”buttonthatthedeveloperhaslinkedtothespecial handlersourcecode,ittranslateseachbreakpointintothe Rivet.pause() function. Rivet.pause() causes code shown in Figure 6. Each breakpoint is just a loop therootframetosendapauserequesttotheRivetlibrary which receives a command from the remote debugger, ineachchildframeandwebworker;thesepauserequests eval()sthatcommand,andsendstheresultbacktothe aresentusingthepostMessage()JavaScriptfunction. debugger. A breakpoint command might fetch the value Upon receiving a pause command, a parent recursively of a local variable or reset its value to something new. forwards the command to its children. When a context Communication with the debugger uses a synchronous withnochildrenreceivesapauserequest,itsendsapause AJAXconnectiontoensurethatRivetlockstheeventloop confirmation to its parent and then opens a synchronous inthebreakpoint’seventcontext. AJAX connection to the remote debug server. This syn- Once the client-side framework has translated the chronousconnectionallowstheRivetlibrarytofreezethe breakpoints, it dynamically creates a new function rep- event dispatch process, forcing application-defined call- resentingtheinstrumentedeventhandler. Todoso,Rivet backstoqueueup. Aparentwithchildrenwaitsforcon- passesthenewsourcecodetothestandardeval()func- firmationsfromallofitschildrenbeforenotifyingitsown tion if the handler to rewrite is not a closure; otherwise, parentthatitispausedandopeningitsownsynchronous Rivet uses the handler’s evalInScope () function connection to the debug server. When the root frame (x2.1). Equipped with the new callback, Rivet then re- receives pause confirmations from all of its children, it placesallreferencestothecallbackusingthetechniques knows that the entire application is paused. The root wedescribelaterinSection2.5. frame connects to the remote debugger, which can then When the debugger unpauses the application, the ap- inspectthestateoftheapplicationusingtheintrospection plication will execute normally until it hits a call to facilitiesdescribedinSection2.1. Rivet.breakpoint(). Rivet.breakpoint() Tounpausetheapplication,theremotedebuggersends must pause the application, but it must not relinquish a “terminate” message to each AJAX connection that it control of the local event loop while it does so. Thus, hasestablishedwiththeclient. Uponreceivingthismes- Rivet.breakpoint()sendsstandardpauserequests sage,eachRivetcallbackclosesitsAJAXconnectionand to its children, but does not wait for confirmations be- returns,unlockingtheeventloopandallowingtheappli- foremarkingitselfaspaused. Rivet.breakpoint() cationtoreturntoitsnormalexecutionmode. also sends a special “breakpoint” message to its parent. This message is recursively forwarded up the event con- 2.3 Breakpoints text tree until it reaches the root frame. Upon receiving themessage,therootframepausestherestoftheapplica- Rivet allows developers to dynamically insert break- tion. Oncethedebuggerhasdetectedthatalloftheevent pointsintoarunningapplication’seventhandlers. Toset contextsarepaused,itcaninterrogatethemasnecessary. abreakpoint, thedevelopermustfirstidentifytheappro- priate event handler using the remote debugger’s object 2.4 StackTraces enumerationGUI.Thisinterfaceusesastandardtreeview torepresenttheapplication’sobjectgraphs. Oncethede- JavaScriptdoesnotexplicitlyexposefunctioncallstack veloperhasfoundtheappropriatefunction,theremotede- frames to application-level code. Nevertheless, when buggerdisplaysthefunction’ssourcecodebycallingthe an application hits a breakpoint, Rivet can send a stack traceto the remote debugger. Rivetdefines twokindsof //Define a constructor function for class X. stack traces. A lightweight stack trace reports the func- function X(){ tion name and current line number for each active call this.prop1 = ’one’; } frame. Even though Rivet does not have explicit access X.prototype.prop2 = ’two’; to the call stack, it can access function names and line numbers by intentionally generating an exception within var x1 = new X(); var x2 = new X(); a try/catch block, and extracting stack trace information alert(x1.prop1); //’one’: defined in constructor fromthenativeExceptionobjectthatthebrowsergen- alert(x1.prop2); //’two’: defined by prototype erates. This is the same technique used by the stack- trace.jslibrary[23],anditworksrobustlyacrossallmod- x1.prop1 = ’changed’; alert(x1.prop1 == x2.prop1); //False ernbrowsers. Lightweight stack traces do not expose actual call //Changing the value for a prototype frames to Rivet. Thus, although Rivet breakpoints can //property changes the value in all use an eval() loop to provide read/write access to lo- //instances of that class *unless* an //instance has explicitly redefined the cal variables in the topmost stack frame, Rivet cannot //property. uselightweightstacktracestointrospectvariablesincall X.prototype.prop2 = ’cat’; frames that are lower in the stack. Rivet can provide alert(x1.prop2 == ’cat’); //True heavyweight stack traces that provide such functional- x2.prop2 = ’dog’; alert(x2.prop2 == ’cat’); //False ity, but to do so, Rivet must rewrite all functions so that they update a stack of function objects upon each call and return. Furthermore, each function must define the Figure7: Exampleofprototype-basedinheritance. getScope () and evalInScope () that Rivet usestointrospectuponclosurestate(x2.1). useadifferentapproach,sincetheseobjectsareboundto Both closure rewriting and heavyweight stack rewrit- opaqueinternalbrowserstatethatisnoteasilytransferred ingcanbedonestatically,beforeapplicationdeployment. tootherobjects. Thus,tooverwriteanativeobject,Rivet However, the function code that supports heavyweight musttraversetheapplication’sentireobjectgraphand,for stack traces can be dynamically swapped in at debug eachobject, replaceanyreferencestooldObjwithref- time. Thisallowstheapplicationtoavoidthebookkeep- erencestonewObj. Rivetusesstandardtechniquesfrom ingoverheadduringnormaloperation.Notethatrewritten closure code should always be used, lest Rivet miss the garbage collection [12] to avoid infinite recursion when theobjectgraphcontainscycles. creation of a closure scope and be unable to expose the The second convenience function, called boundvariablestotheremotedebugger. Rivet.redefineClass(oldCtor, 2.5 GenericLivePatching makeNewVersion, newCtor), is useful for updating all instance objects of a modified class defini- Rivet defines a patch as a single JavaScript function tion. Whereas C++ and Java implement classes using whichRivetwillevaluateintheglobalscope. Thepatch types, JavaScript implements classes using prototype can access and modify the objects reachable from the objects [7]. Any JavaScript function can serve as a globalvariableswithoutassistancefromRivet. Thepatch constructor if its invocation is preceded by the new accesses closure scopes through the getScope () operator; any references to this inside the constructor function that Rivet adds to each closure. The patch can invocation will refer to the new object that is returned. also access normally non-enumerable event handlers us- Furthermore, by defining a prototype property for ing data structures like Rivet.timeoutCallbacks a function, an application defines an exemplar object andDOMNode.DOM2handlers. whichprovidesdefaultpropertiesforanyinstanceofthat To make patches easier to write, Rivet defines three constructor’sobjects. Figure7providesasimpleexample convenience functions for developers. The first, called ofJavaScript’sprototype-basedclasses. Rivet.overwrite(oldObj, newObj), instructs When a patch invokes Rivet to copy all of newObj’s values into oldObj. If Rivet.redefineClass(oldCtor, neither newObj nor oldObj are a function, the over- makeNewVersion, newCtor), Rivet does the writingprocessistrivial—sinceJavaScriptobjectsarejust following: dictionaries mapping property names to property values, (cid:15) Rivet finds all instances of oldCtor’s class, Rivet can overwrite an object in place by deleting all of i.e., all objects whose proto field is itsoldpropertiesandassigningitallofnewObj’sprop- oldCtor.prototype. erties. If newObj or oldObj is a function (or another (cid:15) For each instance obj, Rivet calls native code object like a regular expression), Rivet must makeNewVersion(obj), creating a new version of the object using application-specific 2.7 Implementation logic. Rivet then update oldObj in-place with the OurRivetprototypeconsistsofaclient-sideJavaScript contentsofnewObj. library and a developer-side debugging framework that (cid:15) Finally, Rivet uses containsaJavaScriptrewriter,adebugserver,andafront- Rivet.overwrite(oldCtor,newCtor) end debugging GUI. After minification (i.e, the removal to replace stale references to oldCtor with of comments and extraneous whitespace), the client-side referencestonewCtor. library is less than 29 KB of source code; thus, it adds Applications that desire finer-grained con- negligible cost to the intrinsic download penalty for a trol over their patching semantics can invoke modernwebapplicationthatcontainshundredsofKBof Rivet.traverseObjectGraph(callback), JavaScript, CSS, and images [18]. The closure rewriter specifyingafunctionthatRivetwillcalluponeachobject and the debug server are both written in Python. Be- intheapplication. fore deploying an application, the developer passes its JavaScript code through the rewriter, which instruments 2.6 DebuggingScenarios closuresasdescribedinSection2.1. Afterdeployingthe application, the debug server listens for debugging re- We envision that Rivet will be used in two basic sce- questsfromremotewebpages. Iftheserverisconfigured narios. In the beta testing scenario, a small number of to run in auto-response mode, it will run a pre-selected professionaltestersormotivatedvolunteersinteractwith diagnostic script on the remote application and store the anunpolishedversionofanapplication. Thesetestersare resultsinadatabase. Otherwise,iftheserverissettoin- notdevelopersthemselves,buttheyarewillingtostartand teractive mode, it will open a front-end debugging GUI stoptheirapplicationsessionsandactivelyhelpthedevel- which the developer can use to inspect the remote ap- opers to debug any problems that arise. In this scenario, plication in real time. The front-end is just a web page whenthebetatesterencountersaproblem,sheinitiatesa thatcommunicateswiththedebugserverviaHTTP.The full-blowndebugginginteractionwitharemotedeveloper. serveractsasarelaybetweenthefront-endandtheremote The developer starts the debugging GUI and engages in application,sendingdebuggercommandstothepageand an iterative process with the beta tester and the remote sendingclient-generatedresultsbacktothefront-end. application,exploringandmodifyingtheprogramstatein Ourcurrentprototypeimplementsallofthefeaturesde- variousways.UsingRivet’slivepatchingmechanism,the scribedinthissectionexceptforheavyweightstacktraces developercandynamicallyaddachatinterfacetothelive (x2.4). We are currently building a rewriting engine to page;thisenablesareal-timedialoguebetweenthetester supportthisfacility.Theclient-sideRivetlibraryhasbeen andthedeveloperwithoutrequiringthetestertoconfigure testedextensivelyonFirefox,Safari,IE,andChrome,and aseparateout-of-bandcommunicationmechanism. itworksrobustlyonthosebrowsers. For a widely deployed application, the typical layper- son user may not want to assist with a remote debug- gingsession. Inthesesituations, theapplicationcanstill 3 PrivacyConcerns contain a “panic” button. However, when this button is pressed,theapplicationdoesnotinitiatearemotedebug- Rivetexposesallofawebpage’sstatetoaremotede- ging session with a human developer—instead, the web bugger.Thisstatemightincludepersonalinformationlike pageconnectstoanautomateddebugserverwhichrunsa passwords, emails, e-commerce shopping carts, and so setofpredefineddiagnosticsonthepage.Eachdiagnostic on.ApplicationscanuseHTTPStosecuretheconnection isimplementedasaRivetpatchwhichsimplyrunsatest between the web browser and the debug server; by do- ontheclient-sidestateanduploadstheresulttoaserver. ingso,aclientcaneasilyverifytheidentityoftheremote Forexample,apatchmightgenerateanapplication-level principal that is inspecting local state. HTTPS also pre- coredump,serializingtheDOMtreeandapplicationheap vents arbitrary network snoopers from inspecting client and sending it to the server for further analysis. As an- data. However, it does not constrain the remote debug- other example, a diagnostic could run integrity checks ger’sabilitytoenumerateandmodifyclient-sidestate. overtheapplication’scookiesandotherpersistentclient- Rivet does not grant fundamentally new inspection side data. These kinds of automatic tests require no as- powers to developers, since applications do not need sistancefromtheenduser,butprovideinvaluabledebug- Rivet to take advantage of JavaScript’s powerful reflec- ging information to application developers. These tests tion capabilities. For example, there are many preex- can also be silently initiated by the Rivet library, e.g., in isting JavaScript libraries that analyze user activity and response to catching an unexpected exception. In some pagestatetodeterminewhichadsauserclicksorwhich applications, such automatic diagnostics may be prefer- parts of a page are accessed the most. Also, much of abletohavingauser-triggered“panic”button. theprivateclient-sideinformationispersistentlystoredin Firefox Safari 25 e slowdown 1.51 milliseconds) 1250 BBrraanncchh ffaaccttoorr:: 23 Relativ 0.50 e time ( 105 us a P 0 0 1 2 3 4 Depth of Event Context Tree Figure8:Microbenchmarks:RelativeslowdownofRivet- enabled versions. A slowdown factor of 1 indicates no Figure9: Timeneededtopauseanestedframetreewith slowdown. noeventloopcontention. server-side databases, with the client application treated (cid:15) Richards and Delta Blue are CPU-bound asanephemeralcache. Thus, developerscanalreadyin- benchmarksfromGoogle’sV8testsuite. spectmuchoftheuser’sprivatedatawithoutinterrogating (cid:15) setTimeout measures how quickly the browser theclientportionoftheapplication. can dispatch timer callbacks that have a delay time ofzero. (cid:15) The AJAX download test downloads a 100 KB 4 Evaluation filefromalocalwebserveronthesamemachineas thebrowser. Usingalocalwebservereliminatesthe Inthissection,weinvestigatefourquestions.First,how impact of external network conditions and isolates much overhead does Rivet add to an application during Rivet’simpactonclient-sideAJAXhandling. normal usage, i.e., when the application is not being de- (cid:15) In the Mouse move test, the web page registers a bugged? Second,howlongdoesittakeforRivettopause nullcallbackformousemovementevents. Ahuman an application? Third, how large are the messages that userthenquicklymovesthemousecursorbackand are exchanged between the client-side Rivet library and forth across the screen for ten seconds. The output thedebugserver? Finally,howlongdoesittakeforRivet ofthebenchmarkishowmanytimesthebrowserin- to patch a live application or run an automated diagnos- vokedtheGUIcallback. tic? Using synthetic benchmarks and real applications, (cid:15) The Worker ping-pong test examines how weshowthatRivetaddsnegligibleoverheadduringnor- quickly a web worker and a parent frame can send mal operation. Furthermore, at debug time, Rivet is fast postMessages()toeachother. enoughtosupportinteractiveremotedebuggingsessions. WiththeexceptionoftheWorker ping-pongtest,the TotestRivet’sperformanceonwebapplicationsinthe performanceoftheRivet-enabledbenchmarkswasstatis- wild, we loaded those applications through a rewriting ticallyindistinguishablefromthatofthestandardbench- webproxy. TheproxyaddedtheRivetJavaScriptlibrary marks. There are several reasons for Rivet’s low over- to each HTML file and web worker; it also passed all head. First, the Rivet initialization code that runs during of the JavaScript code through Rivet’s closure rewriter. page load is extremely fast. This code merely needs to Using this proxy, we could test Rivet’s performance on interpose on some class definitions and global methods, live web applications without requiring control over the and this process takes less than one millisecond (which serversthatactuallydelivereachapplication’scontent. In isthebestresolutionofJavaScripttimestampsonmodern all graphs, each result is the average of ten trials. Each browsers). Post-initialization, Rivet’s bookkeeping code trial was run on a Dell workstation with dual 3.20 GHz isalsoextremelyfast.Forexample,theoverheadoftrack- processors, 4 GB of RAM, and the Windows 7 operat- ingsetTimeout()callbacksisfivelinesofbookkeep- ingsystem. Fortheexperimentsinthissection, weused ingcodeandtwoextrafunctioncalls(onemadefromthe Firefox 6.0.2 and Safari 5.1 to load Rivet-enabled appli- wrappedsetTimeout()tothenativeversion,andone cations. For graphs using only a single browser, Firefox madefromthewrappedcallbacktothereal,application- wasused. suppliedfunction). Rivet’soverheadfortrackingclosure stateisalsolow. AsshowninFigure3,rewrittenclosures 4.1 Microbenchmarks donotexecuteanyRivetcodeduringnormalusage—the getScope () and evalInScope () functions Computational slowdown: Figure 8 shows the rela- areonlyinvokedbytheremotedeveloperatdebugtime. tiveperformanceslowdownforseveralRivet-enabledmi- crobenchmarks. Weusedthefollowingbenchmarks: Figure 8 shows that Rivet makes the Worker ds) 25 Firefox ping-pong test roughly 25% slower. The primary on 20 ec Safari rmeauscohnhiisghthearttbhrroowugsehrpsutdisthpaantchthweorrakteer amteswshagicehs wthiethy e (millis 1105 dispatch zero-delay timer callbacks or mouse events. m 5 As a concrete example, on our dual-core test machine, use ti 0 Firefox 6 could issue roughly 44 mouse events a sec- Pa ond and 163 zero-delay timer callbacks a second, but over 16,000 ping-pong exchanges per second. Thus, Rivet’s postMessage() overheads are compara- Figure10: Timeneededtopauseseveralapplications. tively larger. In a Rivet application, a single round of ping-pong involves three wrapped function calls: the postMessage() on the worker object in the parent context, themessagecallbackintheworkercontext, and ing this list, the master frame could send pause requests the message callback in the parent context that handles toeachdescendentcontextdirectly(insteadofsendingre- theworker’sresponse. questsdownthecontexttree). Afteratimeout,themaster frame would declare any silent contexts to be hung. At Pause latency: A web application consists of a tree of thatpoint,themasterframewouldyieldtotheremotede- event contexts. Rivet pauses an application by dissemi- bugger. The remote debugger could inspect both paused natingpauserequestsacrossthistreeandwaitingforchild contextsandhungcontextsasnormal. However,allcon- contexts to acknowledge that they are paused; the entire textswouldbeinpotentiallyvolatilestates,sinceahung application is paused once the root frame has received event handler could unhang at any moment and mutate pauseconfirmationsfromallofitschildren. Figure9de- somecontext’sstatebeforerelinquishingtheprocessor. pictsthepauselatencyasperceivedbytherootframefor 4.2 Macrobenchmarks eventtreesofvariousdepthsandbranchingfactors. None of the event contexts defined any application-level han- Pause latency: Figure 10 depicts the time needed to dlers,soRivet’spausehandlerscouldexecuteasquickly pause eight real applications. Crazy Flies simulates as possible. Thus, the results in Figure 9 represent the an insect swarm, using a web worker to calculate insect lowestpossiblepauselatencies. movements and a frame to depict an animation of the Figure 9 shows that the intrinsic pause delay is ex- simulated insects’ movements. Pixastic is an image tremelysmall. Evenforanunrealisticallydensetreewith manipulation program that supports standard operations abranchingfactorofthreeandadepthoffour,thepause like hue adjustment, noise removal, and edge detection. processonlytakes25milliseconds.Ofcourse,fullypaus- Super Mario is a JavaScript port of the popular 8- inganapplicationcantakeanunboundedamountoftime bit Nintendo game. TinyMCE is a full-featured word if an event handler contains code that runs for an ex- processor. Theremainingwebpages(MSNBC,Amazon, tremelylongtime. However,weexpectsuchsituationsto Yahoo,andYouTube)arethestartpagesfortheassoci- be rare, at least for handlers defined in frames, since de- atedwebportals. velopersknowthatlong-running, non-yieldingcomputa- Figure 10 shows that in all but two cases, application tionsmayfreezethebrowser’sUI.Webworkerswerede- pausetimesareessentiallyzero. Thisisbecauseinmost signedspecificallysothatapplicationscouldexecutesuch applications, the depth of the event context tree is either computations without affecting the user interface; thus, zero or one, and in each event context, there is little web workers are a more likely source of long-running contention for the local event loop. The former means event handlers. However, in these situations, developers that the spanning tree for pause dissemination messages can explicitly insert Rivet breakpoints in worker code to is small; the latter means that the Rivet library in each placeanupper-boundonanapplication’spausetime. contextdoesnotneedtowaitlongbeforeitcangrabthe Rivet’scurrentpausingschemeguaranteesthatallcon- local event loop and pause the context. Thus, pausing is texts are in non-volatile states when debugging occurs. often very fast. Both Crazy Flies and Amazon had Rivetcouldtradethisguaranteefortheabilitytodiagnose event context trees of depth one (Crazy Flies has a hungcontexts. Atapplicationloadtime,Rivetcouldcre- webworker,andAmazonhasseveralframes). However, ateaninvisiblemasterframethatresidedatopthecontext inbothcases,pausetimeswerelessthan25milliseconds. hierarchy. Thismasterframe,controlledbyRivet,would always be guaranteed to be live. By coordinating with Message sizes: Figure 11 depicts the size of the mes- the Rivet libraries in each descendant event context, the sagesthatRivetgeneratedduringadebuggingsessionfor master frame could build a list of all such contexts. Us- the Yahoo page. At the start of the session, the debug- 1000000 Debugger request ds) 15 n Application response o e Size (bytes) 101010000000000 50 KB ersal time (sec 1050 ag 100 av ess Tr M 10 1 0 10 20 30 40 Sequence number Figure12: Timeneededtovisiteveryapplicationobject. Figure 11: Size of messages exchanged between the re- 15 300 motedebuggerandtheclientapplication. ms) erialize ( 10 e (KB) 200 ger automatically fetched the list of global variables and e to s 5 Siz 100 informationaboutthechildrenoftheroot<html>DOM m Ti 0 0 node; thisallowedthedebuggerGUItopopulatetheini- tialtreeviewelementsthatahumandeveloperusestoex- plore the DOM tree and the object graph rooted by the (a) Generationtime(LW) (b) Serializedsize(LW) windowobject. Afterthisinitializationcompleted,ahu- 2000 12 mandeveloperusedthedebuggerGUItoexplorevarious ms) 10 DpanaOuCaMsovenedntrraocogdllieeesmnstiaeznsaedspaopagflpeipc5sal3itfcirobaotnymitoewnst-ehdareeenfidrenexaemtdrmoeomtaebxejieldmycetubssm.umgagsleilz,rehtooavfit7nh4ge e to serialize ( 11055000000 Size (MB) 2468 m bytes. Theprimarycontentofeachcontrolmessagewas Ti 0 0 a list of property strings that indicated the path to the object whose contents should be returned. Compared to debugger requests, client responses were more variable (c) Generationtime(HW) (d) Serializedsize(HW) insize,sinceobjectshadwidelyvaryingpropertycounts. Figure 13: Computational overheads for lightweight However, these messages were also small. The average (LW)andheavyweight(HW)DOMtreeserialization. client response was 13 KB, and all but one message was smaller than 50 KB. The outlier was the initial fetch of the global variable list and the properties of nostics. Figure 12 shows how long it takes Rivet to the associated objects. The browser defines over 200 invoke a null function upon each object belonging to built-in global variables, and a given application often a particular application. This time is roughly linear in addstenortwentymore. Thus,thenumberofproperties the number of objects that the application contains. In to fetch for the initial object view is often much larger most cases, a full traversal only takes a few seconds. than subsequent ones. Also note that for each function, Thismeans that aRivetpage supporting non-interactive, the Rivet GUI fetches the associated source code by full graph diagnostics will only have to ask the user calling that function’s toString(). This source code to keep a malfunctioning page open for a few seconds comprisesthebulkoftheinitialviewfetch. before the user can close it. Note that MSNBC has the largest traversal time (almost 15 seconds) because it has Diagnostics: Once a web page is paused, a developer both a complex graph of application-defined JavaScript canrundiagnosticsonitorinstallalivepatch. Rivetal- objects, and a complex, deep DOM tree. Although lowsarbitrarydynamiccodetoberunonorinsertedinto applications like TinyMCE and Super Mario also theclient-side. Inthissection,wediscussafewconcrete havelargenon-DOMobjectgraphs,theirDOMtreesare examplesofwhatadiagnosticmightlooklike. comparativelysimpler,leadingtosmallertraversaltimes. Rivet.traverseObjectGraph(f) al- Visual layout bugs are a common source of frustra- lows the debugger to evaluate the function f tion in web applications [13]. Thus, developers will over every object in an application. Thus, often be uninterested in viewing an application’s en- Rivet.traverseObjectGraph() is a useful tireobjectgraph—instead,developerswillbespecifically foundation for many types of whole-application diag-

Description:
fers several advantages beyond its browser agnosticism. With respect to .. “panic” button that the developer has linked to the special. Rivet.pause()
See more

The list of books you might like

Most books are stored in the elastic cloud where traffic is expensive. For this reason, we have a limit on daily download.