Personal tools

Embedded Debugger

From OpenLaszlo

To include the embedded debugger, run the app with "debug=true" as a query arg or in the canvas:

 <canvas debug='true'>.

That causes the compiler to compile the app with "debugging" enabled, which means extra checks inline in the code for undefined methods, etc. It also links against a Debug version of the LFC, which includes the LzDebug.as file. This file defines the __LZDebug object, as well as the components/debugger.lzx file which defines the interactive debugger.

There's a little switcheroo going on. The __LZdebug object is a primitive debugger which just can collect up calls to debug.write() until the real interactive debugger window gets instantiated, which happens late after the rest of the user's app is built. So early on the symbol "Debug" is bound to the __LZDebug object, and later during app initialization that is changed to bind it to the "debugwindow" view. Any debug messages which were written to "debug.write()" during the early app startup are then dumped out to the debugger window for the user to see.

Contents

Backtraces

As of the Pavé release, there is a new configuration parameter for WEB-INF/lps/config/lps.properties: compiler.debug.backtrace, when true, the compiler will instrument functions to maintain a call stack. You must recompile both the LFC and your program if you change the setting of this parameter. It defaults to false because there is significant overhead introduced; in general it is only useful on small test cases.

To create your own backtrace at any time:

 Debug.backtrace()
 

There is an optional argument that is the number of frames to skip in creating the backtrace.

If you print a backtrace using Debug.write, it may be abbreviated, but clicking on it will reveal the full backtrace.

Debugger warnings will automatically include a backtrace. A backtrace snapshot can created by Debug.backtrace(). A backtrace is a subclass of Array, converting it to a string will create a trace from innermost to outermost call, inspecting it will reveal the actual funtion objects that make up the array.

To get the full backtrace out of a debugger warning, you need to use:

 __LzDebug.ObjectForID(NN)
 

Where NN is the object ID of the backtrace object, and then click on the resulting object to see the full trace. E.g.:

 lz/windowpanel.lzx:253: reference to undefined property 'textcolor'  @ «__LzBacktrace(6)#3| $base$2Fbasetabs$2Elzx_129_44_select <- $base$2Fbaselistitem$2Elzx_96_46...»
 
 __LzDebug.ObjectForID(3)
 «__LzBacktrace(6)#3| $base$2Fbasetabs$2Elzx_129_44_select <- $base$2Fbaselistitem$2Elzx_96_46...»
 «__LzBacktrace(6)#3| $base$2Fbasetabs$2Elzx_129_44_select <- $base$2Fbas...» {
   length: 6
   0: «function| LzNode.__LZcallInit»
   1: «function| Class.callInherited»
   2: «function| LzNode.setAttribute»
   3: «function| $base$2Fbaselistitem$2Elzx_9_88_selected_onset»
   4: «function| $base$2Fbaselistitem$2Elzx_96_46__setSelected»
   5: «function| $base$2Fbasetabs$2Elzx_129_44_select»
 }
 «__LzBacktrace(6)#3| $base$2Fbasetabs$2Elzx_129_44_select <- $base$2Fbaselistitem$2Elzx_96_46...»
 

Memory Leaks

As of the Pavé release, there are three new methods on __LzDebug:

  1. markObjects
  2. findNewObjects
  3. whyAlive

You use them in that order. Typically, you will start up your program, run it for a bit until you think it has reached a steady state. Then invoke __LzDebug.markObjects(). This runs in the background and will mark all the objects that are currently in use. When you see the output Trace Done you can move on to the next step.

You exercise your application in a way that you expect not to result in any retained storage (or some small amount of retained storage). Run __LzDebug.findNewObjects(). This also runs in the background and will find any new objects that have been allocated since the markObjects call that have not yet been garbage-collected. When you see the output Trace Done you can move on to the next step.

Call __LzDebug.whyAlive(). This returns a leak list, which you can call .toString() on to see <why> (<size>): <what> pairs. <why> is the shortest path from _root that is keeping the object from being collected; <size> is a count of the slots in that object and all its descendants, which is a rough indication of the cost of the object; <what> is the debugger representation of the object. You may want to set Debug.printLength to a larger value before converting to a string. You can also inspect the leak list to see the objects that have been retained.


Monitoring Properties

As of the Denver release, there are two new debugger functions, Debug.monitor and Debug.unmonitor.

Debug.monitor(who, what): Takes an object and property name and will emit a 'monitor message' each time that property is changed.

Debug.unmonitor(who, what): Turns that off.

A montior message consists of a timestamp, the function that cause the change, the object and property, and the old and new values. E.g.:

MONITOR: [69227.23] LzLoader.initializeRequestObj: <<LoadMovie#0| __debugger.lzx (loaded)>>.valid: true -> true 
MONITOR: [69265.36] LzLoader.initializeRequestObj: <<LoadMovie#0| __debugger.lzx (loaded)>>.loaded: true -> false 

Remote Debugging Protocol

The debugger contains a remote debug protocol module (LzRemote.as), which speaks an XML protocol for remotely inspecting and modifying an app.

See Remote_Debugging