log
" or ".log
", or somewhere in the
directory "/log
" as a file with your name.. In addition, muds
tend to keep a log of run time errors which occur while the mud is up. Again,
this is generally found in "/log
". On MudOS muds it is
called "debug.log
". On other muds it may be called something
different like "lpmud.log
". Ask your administrators where
compile time and run time errors are each logged if you do not already
know.
Compile time errors are errors which occur when the driver tries to load
an object into memory. If, when the driver is trying to load an object
into memory, it encounters things which it simply does not understand
with respect to what you wrote, it will fail to load it into memory and log
why it could not load the object into your personal error log. The most
common compile time errors are typos, missing or extra ()
,
{}
. []
, or ""
, and failure to declare
properly functions and variables used by the object.
Run time errors occur when something wrong happens to an object in
memory while it is executing a statement. For example, the driver
cannot tell whether the statement "x/y
" will be valid in all
circumstances. In fact, it is a valid LPC expression. Yet, if the value of
y
is 0
, then a run time error will occur since you
cannot divide by 0. When the driver runs across an error during the execution
of a function, it aborts execution of the function and logs an error to the
game's run time error log. It will also show the error to
this_player()
, if defined, if the player is a creator, or it will
show "What?" to players. Most common causes for run time errors are
bad values and trying to perform operations with data types for which those
operations are not defined.
The most insideous type of error, however, is plain malfunctioning code. These errors do not log, since the driver never really realizes that anything is wrong. In short, this error happens when you think the code says one thing, but in fact it says another thing. People too often encounter this bug and automatically insist that it must be a mudlib or driver bug. Everyone makes all types of errors though, and more often than not when code is not functioning the way you should, it will be because you misread it.
In your error log, the driver will tell you the type of error and on which line it finally noticed there was an error. Note that this is not on which line the actual error necessarily exists. The most common compile time error, besides the typo, is the missing or superfluous parentheses, brackets, braces, or quotes. Yet this error is the one that most baffles new coders, since the driver will not notice the missing or extra piece until well after the original. Take for example the following code:
1 int test(string str) { 2 int x; 3 for(x =0; x<10; x++) 4 write(x+"\n"); 5 } 6 write("Done.\n"); 7 }Depending on what you intended, the actual error here is either at line 3 (meaning you are missing a
{
) or at line 5 (meaing you have an
extra }
). Nevertheless, the driver will report that it found an
error when it gets to line 6. The actual driver message may vary from driver
to driver, but no matter which driver, you will see an error on line 6, since
the }
in line 5 is interpreted as ending the function
test()
. At line 6, the driver sees that you have a
write()
sitting outside any function definition, and thus reports
an error. Generally, the driver will also go on to report that it found an
error at line 7 in the form of an extra }
.
The secret to debugging these is coding style. Having closing }
match up vertically with the clauses they close out helps you see where you
are missing them when you are debugging code. Similarly, when using multiple
sets of parentheses, space out different groups like this:
if( (x=sizeof(who=users()) > ( (y+z)/(a-b) + (-(random(7))) ) )As you can see, the parentheses for the
for()
statement, are
spaced out from the rest of the statement. In addition, individual sub-groups
are spaced so they can easily be sorted out in the event of an error.Once you have a coding style which aids in picking these out, you learn which error messages tend to indicate this sort of error. When debugging this sort of error, you then view a section of code before and after the line in question. In most all cases, you will catch the bug right off.
Another common compile time error is where the driver reports an unknown identifier. Generally, typos and failure to declare variables causes this sort of error. Fortunately, the error log will almost always tell you exactly where the error is. So when debugging it, enter the editor and find the line in question. If the problem is with a variable and is not a typo, make sure you declared it properly. On the other hand, if it is a typo, simply fix it!
One thing to beware of, however, is that this error will sometimes be
reported in conjunction with a missing parentheses, brackets, or braces
type error. In these situations, your problem with an unknown identifier
is often bogus. The driver misreads the way the {}
or whatever
are setup, and thus gets variable declarations confused. Therefore make sure
all other compile time errors are corrected before bothering with these types
of errors.
In the same class with the above error, is the general syntax error. The
driver generates this error when it simply fails to understand what you
said. Again, this is often caused by typos, but can also be caused by not
properly understanding the syntax of a certain feature like writing a
for()
statement: for(x=0, x<10, x++)
. If you get
an error like this which is not a syntax error, try reviewing the syntax of
the statement in which the error is occurring.
Run time errors almost always result from misusing LPC data types. Most commonly, trying to do call others using object variables which are NULL, indexing on mapping, array, or string variables which are NULL, or passing bad arguments to functions. We will look at a real run time error log from Nightmare:
Bad argument 1 to explode() program: bin/system/_grep.c, object: bin/system/_grep line 32 ' cmd_hook' in ' std/living.c' (' std/user#4002')line 83 ' cmd_grep' in ' bin/system/_grep.c' (' bin/system/_grep')line 32 Bad argument 2 to message() program: adm/obj/simul_efun.c, object: adm/obj/simul_efun line 34 ' cmd_hook' in ' std/living.c' (' std/user#4957')line 83 ' cmd_look' in ' bin/mortal/_look.c' (' bin/mortal/_look')line 23 ' examine_object' in ' bin/mortal/_look.c' (' bin/mortal/_look')line 78 ' write' in 'adm/obj/simul_efun.c' (' adm/obj/simul_efun')line 34 Bad argument 1 to call_other() program: bin/system/_clone.c, object: bin/system/_clone line 25 ' cmd_hook' in ' std/living.c' (' std/user#3734')line 83 ' cmd_clone' in ' bin/system/_clone.c' (' bin/system/_clone')line 25 Illegal index program: std/monster.c, object: wizards/zaknaifen/spy#7205 line 76 ' heart_beat' in ' std/monster.c' ('wizards/zaknaifen/spy#7205')line 76All of the errors, except the last one, involve passing a bad argument to a function. The first bug, involves passing a bad first arument to the efun
explode()
. This efun expects a string as its first argment. In
debugging these kinds of errors, we would therefore go to line 32 in
/bin/system/_grep.c
and check to see what the data type of the
first argument being passed in fact is. In this particular case, the value
being passed should be a string.
If for some reason I has actually passed something else, I would be done
debugging at that point and fix it simply by making sure that I was
passing a string. This situation is more complex. I now need to trace
the actual values contained by the variable being passed to explode, so
that I can see what it is the explode()
efun sees that it is
being passed.
The line is question is this:
borg[files[i]] = regexp(explode(read_file(files[i]), "\n"), exp);where files is an array for strings,
i
is an integer, and
borg
is a mapping. So clearly we need to find out what the value
of read_file(files[i])
is. Well, this efun returns a string
unless the file in question does not exist, the object in question does not
have read access to the file in question, or the file in question is an empty
file, in which cases the function will return NULL. Clearly, our
problem is that one of these events must have happened. In order to see
which, we need to look at files[i]
.
Examining the code, the files array gets its value through the
get_dir()
efun. This returns all the files in a directory if the
object has read access to the directory. Therefore the problem is neither
lack of access or non-existent files. The file which caused this error then
must have been an empty file. And, in fact, that is exactly what caused this
error. To debug that, we would pass files through the
filter_array()
efun and make sure that only files with a file
size greater than 0 were allowed into the array.
The key to debugging a run time error is therefore knowing exactly what
the values of all variables in question are at the exact moment where the
bug created. When reading your run time log, be careful to separate the
object from the file in which the bug occurred. For example, the
indexing error above came about in the object
/wizards/zaknaifen/spy
, but the error occured while running a
function in /std/monster.c
, which the object inherited.
if()
statements and not accounting for all possibilities. For
example:
int cmd(string tmp) { if(stringp(tmp)) return do_a() else if(intp(tmp)) return do_b() return 1; }In this code, we find that it compiles and runs fine. Problem is nothing happens when it is executed. We know for sure that the
cmd()
function is getting executed, so we can start there. We also know that a
value of 1 is in fact being returned, since we do not see "What?"
when we enter the command. Immediately, we can see that for some reason the
variable tmp
has a value other than string or int. As it turns
out, we issued the command without parameters, so tmp
was
NULL and failed all tests.
The above example is rather simplistic, bordering on silly.
Nevertheless, it gives you an idea of how to examine the flow of the
code when debugging malfunctioning code. Other tools are available as
well to help in debugging code. The most important tool is the use of
the precompiler to debug code. With the code above, we have a clause
checking for integers being passed to cmd()
. When we type
"cmd 10", we are expecting do_b()
to execute. We need
to see what the value of tmp
is before we get into the loop:
#define DEBUG int cmd(string tmp) { #ifdef DEBUG write(tmp); #endif if(stringp(tmp)) return do_a(); else if(intp(tmp)) return do_b(); else return 1; }We find out immediately upon issuing the command, that
tmp
has a
value of "10"
. Looking back at the code, we slap ourselves
silly, forgetting that we have to change command arguments to integers using
sscanf()
before evaluating them as integers.
this_player()
when there is no
this_player()
this_object()
just after
this_object()
was destructed
Finally, make use of the precompiler to temporarly throw out code, or
introduce code which will show you the values of variables. The
precompiler makes it easy to get rid of debugging code quickly once you
are done. You can simply remove the DEBUG
define when you are
done.