Archive

Posts Tagged ‘Debug’

DWScript 2.2 preview 2: new debugger component

February 14th, 2011

An SVN snapshot archive is now available as DWScript 2.2 preview 2 (218 kB).

It includes the changes listed there, as well as the following:

  • new TdwsDebugger component to facilitate debugging scripts. This is a helper that wraps common debugging task in a ready-to-use fashion for a debug IDE.
    • supports managing breakpoints.
    • supports watches (hierarchically explorable, you can dig inside a class or record).
    • supports step (into, over, out, to line).
    • supports direct evaluation of expressions.
    • provides info about current expression, source location.
    • callstack is directly available in the execution object.
    • currently limited to non-threaded script debugging.
  • internal class hierarchies have been partly reorganized, providing small gains in execution performance and a reduction in memory usage.
  • unit test code coverage is now up to 83%.

There is however no debug/IDE demo just yet, mostly as I haven’t settled on a syntax editor component to use for such a demo. Internally here, I’m using SynEdit, but I’m not sure which exactly SynEdit I’m using ;-) , and I guess other users of SynEdit may have a similar issue. Another possibility would be to use Scintilla, though there doesn’t seem to be any “reference” Scintilla for Delphi.

(if worst comes to worst I’ll use TMemo or TListBox for a dependency-free demo, but would rather not…)

News , ,

Spotlight on DWS’s IDebugger

December 3rd, 2010

DWScript includes a debugging facility, in the form of the IDebugger interface. The TdwsSimpleDebugger component implements that interface and can be used to simply surface the events.

Debugger interface

You activate a debugger by merely attaching it to a compiled script (a TdswProgram), you’ll then get notified of debugging events. Note that the events will be invoked from the thread the scripts runs in, so if you’ve got some UI updates involved f.i., you’ll have to handle the cross-thread synchronization.

  • StartDebug: invoked when the program execution (under debug) starts.
  • DoDebug: invoked for each instruction by executed.
  • StopDebug: invoked when the program execution (under debug) ends.
  • EnterFunc: invoked when the script enters a function.
  • LeaveFunc: invoked when the script enters a function.

For aborting script execution, you have the standard TdwsProgram.Stop method.

Standard debugging tasks

Breakpoints

Breakpoints can be implemented by merely checking the position of the instruction you get in DoDebug against a reference of breakpoints (you can use a TBits for that). You can then suspend or check conditions (see below).

Suspend, step

To suspend execution, just don’t return from DoDebug and the script will effectively be suspended.

When stepping, DoDebug will fire on instructions, so if you step from DoDebug to DoDebug, and the user placed two instruction in the same script lines f.i., you’ll step twice on the same script line. If that isn’t desirable, you can filter and step only if the source line changed.
Step into and step over can be implemented with the help of the EnterFunc/LeaveFunc notifications.

Evaluating symbols

If you want to evaluate a symbol, you can use Expr.Prog.Table.FindSymbol(), with Expr being the expression you got passed in the DoDebug. That will find the symbol in the current context (the current method if you’re in a method f.i.). You can also find a symbol from the root by going through the Root property.
Note that FindSymbol will return any symbol, not just variables (TDataSymbol), so you can use this to evaluate functions (with side-effects) if you wish. Data symbols are stored in the stack, so the relevant part for you will be a data symbol’s stack address (Addr).

The stack is accessible via Expr.Prog.Stack, you’ll need it to evaluate data symbols. You can of course also use it to modify a variable (just write to the variable’s stack location).

Call stack

DWS 2.1: If you want the call stack, the most pragmatic approach is IME to do it via EnterFunc/LeaveFunc, the script call stack structure exist, but they aren’t really geared towards ease of use when debugging. So you can just maintain your own simplified call stack.

DWS 2.2+: StackTrace is available directly in the execution, both as a raw expression CallStack array, or via CallStackToString, as a textual version. You also have access to any script Exception call stack via the StackTrace method.

Note on calls to Delphi-functions

Keep in mind the debugger can only operate on the script code. If the script has invoked a Delphi function, and your execution is stuck there, the debugger won’t help. You’ll have to handle suspension in your Delphi code.

That’s a reason why here it’s considered a good practice to wrap calls to Delphi code with a safety net, as failure, crashes, incorrect data are the norm when scripting. Raw exposure of Delphi (or external) functions can often be problematic when the user is writing and debugging his scripts.

Using the debugger for profiling

You can easily make use of IDebugger for profiling purposes via DoDebug and EnterFunc/LeaveFunc. Instrumenting is implicitly there, so it’s just a matter of performing the timings.

In our mini-IDE for scripting here (not open-source), a sampling profiler is always active when running code: it periodically suspends the script, notes the current expression, call stack and resumes execution ASAP. For scripting purposes, a sampling frequency of 100 Hz is more than enough IME, and it’s low-frequency enough to have no measurable impact on the script execution time.
Another cheap profiling tool is to add a function call counter (via EnterFunc), with the two combined, you can pretty much identify all bottlenecks at a glance.

Finally, another tool in your chest can be to implement an execution monitor with the debugger, like the one in SamplingProfiler, which can be useful not just at the IDE level, but also at runtime. A typical use is to make a watchdog screen, where you can have an overview of what all the running scripts are doing: useful to diagnose in-production slowdowns, see if they’re all stuck on the same database access, shared resources, or whatever.

Tips , , , , ,

All hail the “const” parameters!

July 28th, 2010

Passing parameters as “const” is a classic Delphi optimization trick, but the mechanisms behind that “trick” go beyond cargo-cult recipes, and may actually stumble into the “good practice” territory.

Why does it work?

The most well known case is “const String“, for which the compiler than takes advantage of the “const” to pass the String reference directly (as a pointer)… and without increasing the reference counter.

To illustrate what the reference counting implies, here are two screen-shots from the CPU disassembly view, taken in Delphi 6, but the recent compilers up to Delphi 2010 at least behave in a similar fashion (just with the Unicode version of the functions).
I’ve made a pseudo-function, with only one String parameter that calls a single function (here Length(), which isn’t inlined in Delphi 6, and corresponds to LStrLen), guess which CPU disassembly corresponds to “Test(const a : String)” and which to “Test(a : String)

What you’re seeing on the left is an implicit try…finally construct which is used to protect the reference counting (LStrAddRef/LStrClr) on the implicit local variable used to store the String parameter. If you have such calls nested a few levels deep (not uncommon in object-oriented code), you could accumulate quite some overhead.
Also not see here but hidden within the AddRef and Clr calls are bus locks, which may hit you disproportionately in multi-threading scenarios.

And what if you’re modifying the passed String? Can you forego the “const“? Well no, the extra overhead is still there, and using a “const” still beneficial.

String isn’t the only type affected, there are similar gains for all reference counted types (interfaces, dynamic arrays, records holding reference-counted types). And when passing a record as “const“, there is an additional gain in the lack of defensive copying of the record (the larger the record, the greater the gain).

For ordinal and numeric types, there is no compiler optimization (yet), but it can be good practice to mark them as “const“, not in the optimistic hope that someday the compiler will optimize it, but to ease up debugging and code writing. Copies of those parameters (when you want to modify them in the function body) are typically handled adequately by the compiler (and often enough comes for free), so don’t let performance considerations hold you.

There is one case in which using “const” could have adverse effects, but it involves global variables which you would modify or release during the call… so you don’t really need to know more about it, do you? ;)

How does it manifests itself in Profiling?

A missing “const” can manifests itself in different ways during profiling, for String, Interface and dynamic array types, you’ll usually see it via the relevant xxxAddRef or xxxClr functions (the obvious case) and via time spent in begin/end (the less obvious case). Depending on how many functions calls with the reference counting are involved in your inner loop, none of the above may come ahead, even if calls and reference counting are an issue: the time spent will be spread over the above functions and your various begin/end sections.
For record parameters, the lack of “const” could materialize itself in “Move“, or other data copying costs (large records will be moved, smaller ones can be copied via ad-hoc in-place code by the compiler).

In multi-threading, things can be a little less obvious, as rather than the reference counting and exception frame, it could be the bus locks hitting you, or sometimes worse, inter-CPU traffic to pass around the values of the reference counter.

Multi-threading’s worst case would be referring to the same string field from multiple threads, and passing that string around in functions without “const” parameters, thus repeatedly hitting the reference counter of that string from multiple threads at the same time, resulting in extra inter-CPUs traffic to share the value of that reference counter.

In such cases, “const” can help, but it will often not be enough, you’ll also have to look for functions/methods that return strings with an unnecessary reference counting, like when getting your strings from a TStringList.

Good Practice

Usually I tend to “const” just about every parameter that isn’t “var” or an object, this isn’t just  to pre-emptively alleviate possible performance issues, but also to gain something valuable when debugging and writing a function’s body: untouched input parameters, wherever you are in the function’s body. This means not just in the function’s body, but also when in the functions called by the function.

And that’s the key point to remember about “const” parameter: it’s here to state that a parameter will be left unchanged inside the body, something which allows the compiler to optimize it’s output, and the developer to optimize his thought process too. Qualifying all your parameters as either “const” or “var” makes your intentions obvious.

So IME, it’s not worth it to hoard your keystrokes, best spend them on “const” and explicit local variables when you really need to alter those input parameters.
Whoever has to maintain your code will be thankful.

Object parameters

There is one case I’ve not mentioned in all the above, the case of object parameters (and class references). It’s a special case because when you pass an object as “const“, you’re not making any promises of not keeping the object constant, but just keeping the underlying variable (pointer) constant. This is unlike other languages. There is also a risk of confusion when your code will be read by developers not really familiar with the underlying pointer-based mechanics of objects (and they are more common than you may think, even, and maybe more so in highly OO languages).

So for objects, unless you want to spend a lot of time educating, my rule of thumb is to avoid “const” or “var” for object parameters.

Beyond “const” parameters

Const’ing your parameters will eliminate some defensive copying, implicit try…finally, and reference counting overhead, but it leaves open another field of similar inefficiencies: that of return values.

Alas there is no “const Result” syntax, which would allow to state that your result can’t be altered in any way (a sort of strict immutability if you will). Thus whenever you have a function returning a String f.i., you’ll end up triggering the implicit reference counting and exception machinery if you’re not careful (like in this case)…

There are strategies to tackle this issue, but none of them are as simple as adding a “const“, and most of them come with sacrifices… so, let them be food for other articles.

Tips , , , , ,

Don’t abuse FreeAndNil anymore

February 6th, 2010

A recurring subject when it comes to freeing objects and preventing is whether you should just .Free them, thus leaving a invalid reference that should however never be used anymore when the code design is correct, or if you should defensively FreeAndNil() them, thus leaving a nil value that will hopefully trigger AVs more often on improper usage after release.

Allen Bauer recently brought this subject in his blog “A case against FreeAndNil“, arguing that there are better tools than FreeAndNil to diagnose improper usage after release, and that it can hide other issues and lead to other magic bullet solutions, which only further the problem. This is true, and FastMM debug mode can do wonders here, however, quite often, you don’t want to rely on a debug and diagnostic machinery that needs to be switched ON for problems to be detected early on.

Well, if you’re using FreeAndNil() for defensive purposes, don’t abuse it anymore, invest in a few lines of code for a shiny new FreeAndInvalidate():

procedure FreeAndInvalidate(var obj);
var
   temp : TObject;
begin
   temp := TObject(obj);
   Pointer(obj) := Pointer(1);
   temp.Free;
end;

This function frees the object and sets the reference to an invalid magic value, which will trigger and AV on improper field or virtual method access after release  (just like FreeAndNil), but unlike FreeAndNil, it will also AV on multiple .Free attempt, and will not be stopped by “if Assigned()” checks. If you wish even more defense, you can also “sabotage” the VMT pointer of the freed object instance.

With a FreeAndInvalidate() added to your bag of tricks, you can now reserve FreeAndNil usage to situations where having a nil reference is truly part of the design, and no longer abuse it for defensive programming. Of course this is still no magic-bullet, but it’s cheap enough that you can use it in release builds (unlike debug and diagnostic tools), and as a bonus, it makes it obvious when reading the code that the object reference is supposed to be invalid after the call.

Tips ,

ZJDBGPack re-release

May 4th, 2009
Comments Off

ZJDBGPack is again available, but as an independent download (it used to be bundled with SamplingProfiler).

This is a command-line utility intended for use in a build process or from the Delphi tools menu, whose purpose is to integrate debug information into an executable. The debug information format  is a compressed version of JCL‘s JDBG.

As of know, SamplingProfiler is the only published utility that understands this format, so you can use it either to reduce the size of the executables you deploy for profiling purposes, or if you do not want to deploy directly-readable debug information files.

News , , , , , , ,