Re: Allocating memory in unix vs. windows

From: Daniel A. Koepke (dkoepke@circlemud.org)
Date: 03/21/01


On Wed, 21 Mar 2001, Jake Turner wrote:

> Ok, sorry about all that hassle guys.
>
> But you may of noticed in my last snippet of code, in some cases i was
> assigning data to map[-1][x] or map[x][-1] or even negative in both! which
> obviously ballsed it up :P

A general way to find this stuff out, BTW, is by taking advantage of the
fact that processes "dump core" when they crash (unless you have core
dumps disabled or have a size limit too small for the core file to fit: if
you're using bash, try 'ulimit -a' to see your account's limits and, if
you can, 'ulimit -c unlimited' to unrestrict your coredump size).  A
coredump is a dump of the process's memory when it crashes.  When you
compile debugging information into the executable (by using the -g option
passed to gcc during compilation, which CircleMUD uses by default), you
can use gdb to hunt crashes very effectively.

There is an introduction to using the GNU debugger (gdb) linked through
Ceramic Mouse, but you won't even need that if you're the tinkering type.
Once you have the core file (which will be in your lib/ directory under
the CircleMUD directory) from the crash, the following from the CircleMUD
directory will start the debugger (% being your shell prompt):

  % gdb bin/circle lib/core

The first argument is the binary executable that produced the coredump
(the executable is what has the debugging symbols in it that gdb needs to
provide you with a simple interface to looking at the coredump).  The
second argument is, of course, the coredump.  gdb's online help is
surprisingly good, so you could start with 'help'.  The most important
thing is to understand what the debugger is doing for you.  The general
concepts you need to know can be summarized as follows:

When a function is called, the computer jumps to the place of that
instruction and begins executing code there within a new local
environment.  This is called a 'frame of execution'.  The frame that you
were formerly in (that of the calling function) is pushed onto a stack,
later to be recovered (when the function we're in returns).  E.g.,

  int foobar(void)
  {
    int a, b, c;
    a = 1;
    b = 2;
    c = foobaz(a, b);
    return (c);
  }

  int foobaz(int a, int b)
  {
    int r = a + b;
    return (r);
  }

when foobar() is called from main(), main()'s frame is pushed onto the
stack and we create a new frame, foobar(), which has three local
variables.  Inside foobar() we run into a call to foobaz(), which causes
us to create a new frame and jump into foobaz() (passing the appropriate
arguments from foobar()'s frame into foobaz()), where we create a local
variable r in this new frame and return its value.  When we get to return,
we free up foobaz()'s frame, pop foobar()'s frame from the stack and
restore it as our current frame.  When we return from foobar(), we do the
same thing, returning back to main()'s frame.  Now if we crashed in
foobaz() for some (very) bizarre reason, our stack frame would look like:

  #0 foobaz(a=<blah>, b=<blah>): int r = a + b;
  #1 foobar(void): c = foobaz(a, b);
  #2 main(void): printf("%d\n", foobar());

where the line of code following the function name is the line that we
were on when the frame was exited (whether by a crash or by pushing a new
frame on the stack).  Note that we crashed in frame #0 and that what we're
looking at can be thought of as a look back into the execution of our
program.

If you understand this, you will be very pleased to see that things are
just as simple and straightforward in gdb.  In gdb, if I type 'bt' (which
is short for 'backtrace'), I would get something like the following:

  #0 adjust_kp (killer=0x4016fbfc, victim=0x401a03c4) at fight.c:206
  #1 0x8061fc7 in explosion_event (room=0x40140680) at exp.c:1901
  .
  .
  .
  (gdb)

I start, of course, in the frame we were in when the game crashed.  While
in this frame I have access to any variable it could access (local
variables, function arguments, global variables, etc.), with the caveat
that an optimization compiler can make variables disappear and there's
nothing the debugger can do about that.  So let's say I want to see what
line 206 in fight.c is.  Being already in frame #0 (by default), I can
simply type 'list' and it will give line 206 of fight.c with some pre- and
post-context.  Good start.  Now let's say I see line 206 is:

  if (!IS_NPC(tanker))

So we have a new variable there.  I want to see what it is:

  (gdb) print tanker
  (PropertyHashType *) 0x0

Ah hah.  So tanker is a pointer to the address 0x0 (which is 0, which is
NULL).  So I'm trying, in essence, to do:

  if (!IS_NPC(NULL))

which we'll assume is bad (it is).  Now I know why we're crashing on line
206 of fight.c, and with some additional investigative work into where the
value of tanker is coming from, I can quickly and easily resolve the bug.

Naturally, debugging is not a replacement for being a careful coder.
Desk-checking (reading your code) is always recommended, but it's
certainly more fun to Indiana Jones it and make the adventure really be
getting out of the jam that you got yourself into.  So there's a crash
course on the loveliness of gdb.  Use it.  It gets you answers faster than
the list and keeps list traffic down.  :)

-dak

--
   +---------------------------------------------------------------+
   | FAQ: http://qsilver.queensu.ca/~fletchra/Circle/list-faq.html |
   | Archives: http://post.queensu.ca/listserv/wwwarch/circle.html |
   +---------------------------------------------------------------+



This archive was generated by hypermail 2b30 : 12/04/01 PST