UDN
Search public documentation:

DebuggerInterface
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > Editor & Tools Programming > UnrealScript Debugger Interface
UE3 Home > UnrealScript > UnrealScript Debugger Interface

UnrealScript Debugger Interface


Overview


Let's look at the typical debugging session and what goes in to it. Before you can debug UnrealScript you need to compile your scripts in debug mode (by adding "-debug" to the command line, e.g. UDK make -debug). This adds additional control and mapping information to the compiled packages. Once you have scripts that are compiled with debug information, you can activate the debugger in the engine via one of two methods. The first, is by using the "-autodebug" command line switch which causes UE3 to activate the script debugger the moment the first UnrealScript bytecode is processed. Alternatively, the "toggledebugger" console command can be used from the game console to activate the debugger and immediately break.

Let's look at what happens when you activate the debugger (using either method):

  1. First UE3 loads the interface DLL. By default, it will load the file "DebuggerInterface.dll" however this setting can be override in the Debugger.ini file. This DLL is the conduit for sending debug commands to the engine and getting results back.
  2. Once loaded, UE3 immediately calls the exported function SetCallback() which creates the communication conduit back to the engine. It then clears the 3 variable watch lists (locals, globals, and user watches).
  3. Finally, it calls the exported function ShowDllForm which then displays or activates the debugger.

The original external debugger interface was a small Borland Delphi forms DLL and the interface has remained the same for backwards compatibility. Let's look at the functions that need to be exported from the interface DLL.

Interface


There are 19 functions that the debugger interface needs to implement. They are:

void SetCallback(void* CallbackFunc)
 This is the most important function in to manage in the DLL. When first activated, the engine attempts to setup a callback through this function. You should store the function pointer passed in and use it to communicate with the engine later. We will discuss what commands can be sent later on.

void ShowDllForm()
 When called, ShowDLLForm() needs to be able to activate the window of the debugger and make it visible.

void BuildHierarchy()
void ClearHierarchy()
These two functions are called when the engine wants to signal the debugger that is it about to send over the currently loaded class hierarchy. Between them, UE3 will make several AddClassToHierarchy() calls, passing in a tree of all classes loaded.

void AddClassToHierarchy(const char* ClassName)
This function is used to add a class to the debugger's class hierarchy. The original debugger maintained a list of loaded class in a hierarchal tree view. This information is passed as strings using the following format: .. So for example you might have: "core..object" (because object has no parent) "engine.object.actor" "utgame.gameinfo.utgame" What the debugger does with this information is up to the external application.

void ClearWatch(int WatchType)
void ClearAWatch(int WatchType)
UE3 supports 3 different types of watches: locals, globals and user watches. This function is called when UE3 wants to clear one of those lists. It supports the following watch types:

  1. Local Watches
  2. Global Watches
  3. User Watches
The external debugger is responsible for managing and displaying data in the watch list. ClearWatch() is a holdover from the original Delphi debugger example. The engine is still calling it so it needs to be implemented. We recommend simply redirecting calls to ClearWatch() to your ClearAWatch() handler.

int AddAWatch(int WatchType, int ParentIndex, const char* VarName, const char* VarValue)
The engine uses this function to update the debuggers watch list. A single variable often results in multiple AddAWatch() calls. For example, adding a watch for a vector (a struct with 3 children) will result in 4 AddAWatch() calls: the first call for the variable and 3 calls for X, Y and Z. The parameters for this function are:

   • WatchType: The watch type is the same type of value used in ClearWatch().
   • ParentIndex: This is the unique index of its parent in the current object. As the engine recurses through an object looking for members variables to add, each one will get a unique index.
   • VarName: This is the name of the variable.
   • VarValue: This will be the value to display or additional display data about the variable.

The return result for AddAWatch() needs to be a unique ID that describes this particular variable in a watch list.

Let's look at the vector example more closely. Take the following script code:

function TestFunc()
{
    local vector TestVec;
    TestVec.X = 10;
    TestVec.Y=20;
    TestVec.Z=30;
    log(TestVec);
}

If you set a breakpoint on the log statement, when the debugger breaks, the first thing UE3 does is clear the local watches using ClearWatch(0). It then attempts to add the watch for TestVec. This results in the following AddAWatch() calls:

AddAWatch(0, 0, "!TestVec",""); // Should return 1
AddAWatch(0,1,"X","10"); // Should return 2
AddAWatch(0,1,"Y","20"); // Should return 3
AddAWatch(0,1,"Z","20"); // Should return 4

void LockList(int WatchList)
void UnlockList(int WatchList)
These two functions are called when UE3 is about to make an update to a given watch list. The debugger should take whatever steps needed to make the update as smooth as possible until the UnlockList() function is called.

void AddBreakpoint(const char* ClassName, int LineNo)
void RemoveBreakpoint(const char* ClassName, int LineNo)
When UE3 needs to add to the display a breakpoint that it is tracking, it calls AddBreakPoint to register it with the debugger. ClassName is the name of the class where the breakpoint exists and LineNo is the line number. The external debugger is responsible for tracking these and displaying the breakpoint on the associated line. Likewise, RemoveBreakPoint() tells the debugger to remove an existing breakpoint.

void EditorLoadClass(const char* ClassName)
UE3 uses this function to instruct the debugger to load the script for the class specified by the ClassName variable. ClassName is be passed as .. The debugger will be responsible for splitting the string and loading the proper file.

void EditorGotoLine(int LineNo, int bHighlight)
UE3 uses this function to instruct the debugger to display a specific line in the current source file. NOTE that EditorGotoLine() will always be preceded by an EditorLoadClass() call.

void AddLineToLog(const char* Text)
UE3 calls this function when it needs to output something to the debugger's log file. Text is the string to add.

void CallStackClear()
When UE3 breaks and needs to update the call stack in the debugger, the first thing it will do is execute a CallStackClear(). This should signal the debugger that UE3 wants to rebuild the calls stack.

void CallStackAdd(const char* CallStackEntry)
After a break, UE3 makes several CallStackAdd() calls in order to build the call stack on the debugger. The debugger should take the data the comes in and display it somewhere. The CallStackEntry is a string that contains the class name as well as line number.

void SetCurrentObjectName(const char* ObjectName)
UE3 uses this function to set the name of the current object in the debugger. It's called during each internal update call. You can do with this information as you see fit.

void DebugWindowState(int StateCode)
This function is not currently used.

The Callback


We've examined how UE3 communicates with the debugger, now lets look at the other direction. The debugger needs to communicate with UE3 via the callback function. When the interface DLL is bound to the engine, the engine will register its callback. Through this callback, you can pass several commands and all are passed as strings.

The typedef for the callback is as follows:

typedef void (*CallbackPointer)(const char*);

Here is list of commands:

addbreakpoint

<classname>  <linenumber>

removebreakpoint
<classname>  <linenumber>

These commands are used to tell UE3 to add/remove breakpoints. is the name of the call where the breakpoint will exists and is the line number.

addwatch

<varname>

removewatch
<varname>

These commands tell UE3 to add/remove user defined watches. You should fully describe it (for example: pawn.playerreplicationinfo.playername).

clearwatch
This command takes no parameters and will clear all user watches.

changestack

<stackid>

This command causes the engine to jump to a given position in the callstack.

setdatawatch

<watchtext>

This command sets a breakpoint on data access of a given variable.

breakonnone

<0=false|1=true>

Use the breakonnone command to instruct the debugger to break whenever an access none error occurs.

break
Tells UE3 to break as soon as possible.

stopdebugging
This command shuts down the debugger and stop the debugging session.

go
stepinto
stepover
stepoutof
These commands control the execution of the debugger.

But wait.. so if I send the "addwatch" command to UE3 I get the AddAWatch() interface call? That's correct. Think of UE3 as where things happen, and your interface DLL as the thing that renders what's happening. So when you send the "addwatch" command, UE3 first verifies that the path to the object is valid, then it internally sets up a watch on that object and then when it's all done, it will tell the interface that it's watching a given object. It's that simple.. right :)

Other Useful Information


Before we end, let's look at what actually happens when you break the debugger. This should give you an idea of what to look for in your external application. When you send the "break" command to the callback, UE3 will start the process. It sets a flag that says break upon the execution of the next script byte code. When that happens, it performs several steps.

  1. UE3 tells the interface what class and line it's on through calls to EditorLoadClass() and EditorGotoLine().
  2. It then sets the ObjectName with SetCurrentObjectName.
  3. Next it refreshes all watches, first by calling ClearAWatch() and then calling AddAWatch() multiple times.
  4. It then refreshes the call stack by calling CallStackClear() followed by multiple calls to CallStackAdd().

While in its "broken" state, the game will be frozen (including all timings) but it will continue to render and accept windows messages.