Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Programming ➜ General ➜ Debugging with gdb

Debugging with gdb

This subject is now closed.     Refresh page


Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Fri 16 Jan 2004 12:20 AM (UTC)

Amended on Mon 15 Feb 2010 07:37 PM (UTC) by Nick Gammon

Message
This page can be quickly reached from the link: http://www.gammon.com.au/gdb


This post describes general tips for using the GNU debugger (gdb) with gcc (GNU compiler). These should apply under Linux, OpenBSD, Cygwin, and other GNU platforms.




Enable debugging when compiling

The first thing to do is make sure that debug information is written to the executable file. That way the debug information in gdb is much more informative. Look for the line(s) that do the actual compiling and make sure that '-g3' (output gdb debug information, level 3) is present, and I strongly recommend you turn off optimisation (remove any -O option, such as -O, -O1, -O2, -O3 etc.). If you leave optimization on you may find that variables you have in your source "don't exist" as far as the debugger is concerned, or the order of instructions is different.

An example from the SMAUG Makefile:

.c.o: mud.h
        $(CC) -c $(C_FLAGS) $(USE_IMC) $<


The above lines do the compiling, using the variable CC as the compiler, and flags C_FLAGS as the flags. Looking further up the make file reveals that CC is the compiler gcc, and the C_FLAGS are as follows ...


CC = gcc
C_FLAGS = $(OPT_FLAG) -g3 -Wall -Wuninitialized $(PROF) $(NOCRYPT) $(DBUGFLG) -DSMAUG $(SOLARIS_FLAG) $(TIME) $(REG)


In the example above I removed -O from the original Makefile, and added -g3.

If you changed the Makefile, do a "clean compile" to make sure that the executable contains the debugging information.

eg.


rm *.o
make






Enable large core dumps

Some versions of Linux may limit the size of your core file (the file that is automatically created when a program crashes).

Type "ulimit -a" to see what is reported...


$ ulimit -a
core file size (blocks, -c) 0

data seg size (kbytes, -d) unlimited
...


Note the limit on the core file size. Remove this by adding to your file .bash_profile (in your home directory) this line:


# max core dump size in blocks
ulimit -c 16384


The log out and log back in again, and check the limit has increased (by typing "ulimit -a" again).



Remove SIGSEGV handler from your code

In the SMAUG release (see below) there is a SIGSEGV handler - commented out. If you put that in, then it does a nice display on a segment violation (crash), however it makes debugging with gdb pretty hard, because it handles the crash (if any) internally.

If you see something like this in your log, then the SIGSEV handler is active ...


Sat Jan 17 03:34:44 2004 :: SEGMENTATION VIOLATION
Sat Jan 17 03:34:44 2004 :: nanny used , frowns and takes something from a dwarven toddler's mouth.
Sat Jan 17 03:34:44 2004 :: NPC: Dark Trail of Vines room: 3
... and so on ...


This is the code that does that ...


    signal( SIGSEGV, SegVio );  // <-- remove this line

... and later on ...

static void SegVio()
{
  CHAR_DATA *ch;
  char buf[MAX_STRING_LENGTH];

  log_string( "SEGMENTATION VIOLATION" );
  log_string( lastplayercmd );
  for ( ch = first_char; ch; ch = ch->next )
  {
    sprintf( buf, "%cPC: %-20s room: %d", IS_NPC(ch) ? 'N' : ' ',
                ch->name, ch->in_room->vnum );
    log_string( buf );
  }
  exit(0);
}



In order for debugging to work as described below (ie. in order to get a "core" file) you must at least comment out the first line, the one in bold that sets up the SIGSEGV handler.

See further down in this thread for how you can make a gdb command to display the same thing that the SegVio handler did.



Getting started - basics of gdb

Once gdb is running you can get help by typing various things. Also you can press <tab> for command-completion. That is, enter a partial command and press <tab>. Commands can be abbreviated to the shortest unambiguous form (eg. "n" for "next"). The various things you can look at are:


  • help (or h) for internal help
  • help <topic> for help on a command (eg. help continue), or class of command (eg. help breakpoints). The command classes are shown when you type "help" on its own.
  • apropos <word> - list help topics which contain that word (eg. apropos break for help on breakpoints and related commands)
  • info <something> - get information about various things (eg. info registers). Type "info" on its own to get a list.
  • info source - shows the current source file you are looking at
  • info frame - shows the curreent stack frame
  • show <something> - show a setting (eg. show version). Type "show" on its own to show all internal settings.





Starting and stopping your program


  • run - if you have loaded gdb before running your program you can set various breakpoints if desired, and then type "run" to run it.

    • If you have program arguments, you can append them to the "run" command, eg. "run config_file".


  • continue - after stopping at a breakpoint type "cont" to continue.

  • quit - to exit from gdb and stop running your program, type "quit".





Let's add a bug and detect it in gdb

I'm using the MUD server source smaug1.4a_mxp.tgz from this site's download area. I'll edit the function send_to_char_color in comm.c and deliberately remove an important line by commenting out an assignment at line 2744, which assigns an important pointer to "d".


void send_to_char_color( const char *txt, CHAR_DATA *ch )
{
  DESCRIPTOR_DATA *d;
  char *colstr;
  const char *prevstr = txt;
  char colbuf[20];
  int ln;

  if ( !ch )
  {
    bug( "Send_to_char_color: NULL *ch" );
    return;
  }
  if ( !txt || !ch->desc )
    return;
//  d = ch->desc;   // BUG - commented out this line
  /* Clear out old color stuff */
/*  make_color_sequence(NULL, NULL, NULL);*/
  while ( (colstr = strpbrk(prevstr, "&^")) != NULL )
  {
    if (colstr > prevstr)
      write_to_buffer(d, prevstr, (colstr-prevstr));
    ln = make_color_sequence(colstr, colbuf, d);
    if ( ln < 0 )
    {
      prevstr = colstr+1;
      break;
    }
    else if ( ln > 0 )
      write_to_buffer(d, colbuf, ln);
    prevstr = colstr+2;
  }
  if ( *prevstr )
    write_to_buffer(d, prevstr, 0);
  return;
}



Now, let's fire up the server and try to connect to it.


cd ../area
../src/smaug



I connect as Lordrom, and after the initial welcome screens it crashes ...


Fri Jan 16 10:04:22 2004 :: Loading player data for: Lordrom (1K)
Fri Jan 16 10:04:22 2004 :: Lordrom@10.0.0.11((pipe breaking)) has connected.
Segmentation fault (core dumped)


Time for gdb ...

First, let's locate the core file:


$ ls -lt core*
-rw------- 1 nick nick 3682304 Jan 16 10:04 core.22179


In this version of Linux, the core file has the process ID appended (to help debugging where multiple processes might be in the same directory). A quick check indicates the date/time agrees with when I did the test, so now we know the core file name.

We start gdb in this case by typing:

gdb (object file) (core file)

Like this:


$ gdb ../src/smaug core.22179
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Core was generated by `../src/smaug'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/libnss_files.so.2...done.
Loaded symbols for /lib/libnss_files.so.2
#0 0x080bbe35 in write_to_buffer (d=0x4a,
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", length=0)
at comm.c:1615
1615 if ( !d->outbuf )

(gdb)


The relevant parts are in italics. First the nature of the error, segmentation fault, and secondly the line at which it occurred, line 1615.




list - lists source

A helpful start is to list the source around the line in error, to put it into context. In this case we only see some comments.


(gdb) list
1610        }
1611
1612        /*
1613         * Normally a bug... but can happen if loadup is used.
1614         */
1615        if ( !d->outbuf )
1616            return;
1617
1618        /*
1619         * Find length in case caller didn't.


Notice how the debug information in the object file lets us see our original source code.

If you want to list in another file, prepend the file name, eg:


(gdb) list comm.c:1610




print - print the value of a variable

The line of the crash refers to dereferencing the descriptor "d" so let's print it and see if it looks OK:


(gdb) print d
$2 = (DESCRIPTOR_DATA *) 0x4a
(gdb)


This looks bad, a pointer is usually something long, not 0x4a.



info - information about things

The "info" command can show heaps of things, type "info" on its own to see an extensive list.


(gdb) info
"info" must be followed by the name of an info command.
List of info subcommands:

info address -- Describe where symbol SYM is stored
info all-registers -- List of all registers and their contents
info args -- Argument variables of current stack frame
info breakpoints -- Status of user-settable breakpoints
info catch -- Exceptions that can be caught in the current stack frame
info common -- Print out the values contained in a Fortran COMMON block
info copying -- Conditions for redistributing copies of GDB
info dcache -- Print information on the dcache performance
info display -- Expressions to display when program stops
info extensions -- All filename extensions associated with a source language
info files -- Names of targets and files being debugged
info float -- Print the status of the floating point unit
info frame -- All about selected stack frame
info functions -- All function names
info handle -- What debugger does when program gets various signals
info line -- Core addresses of the code for a source line
info locals -- Local variables of current stack frame
info macro -- Show the definition of MACRO
info mem -- Memory region attributes
info proc -- Show /proc process information about any running process
info program -- Execution status of the program
info registers -- List of integer registers and their contents
info remote-process -- Query the remote system for process info
info scope -- List the variables local to a scope
info set -- Show all GDB settings
info sharedlibrary -- Status of loaded shared object libraries
info signals -- What debugger does when program gets various signals
info source -- Information about the current source file
info sources -- Source files in the program
info stack -- Backtrace of the stack
info symbol -- Describe what symbol is at location ADDR
info target -- Names of targets and files being debugged
info terminal -- Print inferior's saved terminal status
info threads -- IDs of currently known threads
info tracepoints -- Status of tracepoints
info types -- All type names
info udot -- Print contents of kernel ``struct user'' for current child
info variables -- All global and static variable names
info vector -- Print the status of the vector unit
info warranty -- Various kinds of warranty you do not have
info watchpoints -- Synonym for ``info breakpoints''

Type "help info" followed by info subcommand name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)


A couple of useful things here are the arguments to the function:


(gdb) info args
d = (DESCRIPTOR_DATA *) 0x4a
txt = 0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r"
length = 0
(gdb)


This shows the current values that were passed down to this function. We can put this in context by listing the function:


(gdb) list write_to_buffer
1598
1599    /*
1600     * Append onto an output buffer.
1601     */
1602    void write_to_buffer( DESCRIPTOR_DATA *d, const char *txt, int length )
1603    {
1604    int origlength;


By using "list (function-name)" we can see the start of a function. This shows that this function has three arguments, d, txt, and length).

As you can see, "info args" showed the values for each of those three.

Also we can use "info local" to see the values of local variables.


(gdb) info local
origlength = 0
(gdb)


That shows the current value of origlength.



bt - backtrace

A very useful command is "bt" (backtrace). This shows the call stack.


(gdb) bt
#0 0x080bbe35 in write_to_buffer (d=0x4a,
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", length=0)
at comm.c:1615
#1 0x080bef81 in send_to_char_color (
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", ch=0x855e9c0)
at comm.c:2762
#2 0x080bf3cd in send_to_pager_color (
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", ch=0x855e9c0)
at comm.c:2862
#3 0x08059794 in do_help (ch=0x855e9c0, argument=0x81b3d0f "imotd")
at act_info.c:1972
#4 0x080bd90f in nanny (d=0x855c8c8, argument=0xbfffdea1 "") at comm.c:2284
#5 0x080ba142 in game_loop () at comm.c:731
#6 0x080b960a in main (argc=1, argv=0xbfffe3a4) at comm.c:316
#7 0x420156a4 in __libc_start_main () from /lib/tls/libc.so.6
(gdb)


This tells us what functions called what. Namely in this case:

__libc_start_main -> main -> game_loop -> nanny -> do_help ->
send_to_pager_color -> send_to_char_color -> write_to_buffer

If you are familiar with SMAUG, this is pretty-much what we expect. The main function calls the "game_loop" and when it gets a new character that calls "nanny" to handle the new connection. Part of nanny uses "do_help" to send help information to the player.




frame/up/down - go to a stack frame

So far we have been looking at stack frame 0 - the bottom of the stack. Let's work our way back. Typing "frame 1" goes to frame 1. (We could also type "up" to go up one level, or "down" to go down one level).


(gdb) frame 1
#1 0x080bef81 in send_to_char_color (
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", ch=0x855e9c0)
at comm.c:2762
2762 write_to_buffer(d, prevstr, 0);


Now that we have changed frames, typing "list" again shows the line in the context of the new frame ...


(gdb) list
2757        else if ( ln > 0 )
2758          write_to_buffer(d, colbuf, ln);
2759        prevstr = colstr+2;
2760      }
2761      if ( *prevstr )
2762        write_to_buffer(d, prevstr, 0);
2763      return;
2764    }
2765
2766    void write_to_pager( DESCRIPTOR_DATA *d, const char *txt, int length )
(gdb)


In this case the frame is on the line which calls write_to_buffer, which is what we expect, as we just came from the frame which was the contents of that function.

Once again we can find the arguments to this function, and any local variables ...


(gdb) i args
txt = 0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r"
ch = (CHAR_DATA *) 0x855e9c0
(gdb) i locals
d = (DESCRIPTOR_DATA *) 0x4a
colstr = 0x0
prevstr = 0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r"
colbuf = "\001\000\000\000(ÕU\b ­ÿ¿\000\000\000\000À<\023B"
ln = 134987743
(gdb)





print - looking at structures

OK, we have a couple of structures here, CHAR_DATA and DESCRIPTOR_DATA.

Let's try looking at them. By dereferencing them with an asterisk (same as in C) we see what the pointer is pointing to. Let's try ch (the player character) ...


(gdb) print *ch
$15 = {next = 0x0, prev = 0x0, next_in_room = 0x0, prev_in_room = 0x0,
master = 0x0, leader = 0x0, fighting = 0x0, reply = 0x0, retell = 0x0,
switched = 0x0, mount = 0x0, hunting = 0x0, fearing = 0x0, hating = 0x0,
spec_fun = 0, mpact = 0x0, mpactnum = 0, mpscriptpos = 0, pIndexData = 0x0,
desc = 0x855c8c8, first_affect = 0x0, last_affect = 0x0, pnote = 0x0,
comments = 0x0, first_carrying = 0x855f300, last_carrying = 0x855f940,
in_room = 0x834d948, was_in_room = 0x0, pcdata = 0x855eb90, last_cmd = 0,
prev_cmd = 0, dest_buf = 0x0, alloc_ptr = 0x0, spare_ptr = 0x0, tempnum = 0,
editor = 0x0, first_timer = 0x0, last_timer = 0x0, morph = 0x0,
name = 0x855e1c8 "Lordrom", short_descr = 0x8225150 "",
long_descr = 0x8225150 "", description = 0x8225150 "", num_fighting = 0,
substate = 0, sex = 1, class = 0, race = 0, level = 65, trust = 0,
played = 11542914, logon = 1074207862, save_time = 0, timer = 0, wait = 0,
hit = 3000, max_hit = 3000, mana = 5000, max_mana = 5000, move = 3100,
max_move = 3100, practice = 2, numattacks = 0, gold = 267, exp = 2000,
act = {bits = {570431064, 0, 0, 0}}, affected_by = {bits = {0, 0, 0, 0}},
no_affected_by = {bits = {0, 0, 0, 0}}, carry_weight = 1023,
carry_number = 8, xflags = 0, no_immune = 0, no_resistant = 0,
no_susceptible = 0, immune = 0, resistant = 0, susceptible = 0, attacks = {
bits = {0, 0, 0, 0}}, defenses = {bits = {0, 0, 0, 0}}, speaks = -1,
speaking = 1, saving_poison_death = 0, saving_wand = 0,
saving_para_petri = 0, saving_breath = 0, saving_spell_staff = 0,
alignment = 0, barenumdie = 1, baresizedie = 4, mobthac0 = 0, hitroll = 2,
damroll = 1, hitplus = 0, damplus = 0, position = 12, defposition = 0,
style = 2, height = 69, weight = 157, armor = 76, wimpy = 0, deaf = 0,
perm_str = 18, perm_int = 17, perm_wis = 13, perm_dex = 13, perm_con = 15,
perm_cha = 14, perm_lck = 15, mod_str = 0, mod_int = 1, mod_wis = 1,
mod_dex = 1, mod_con = 1, mod_cha = 0, mod_lck = 1, mental_state = -12,
emotional_state = 0, pagelen = 24, inter_page = 0, inter_type = 0,
inter_editing = 0x0, inter_editing_vnum = -1, inter_substate = 0,
retran = 0, regoto = 0, mobinvis = 0}
(gdb)


That looks nice, but how about "pretty printing" it?


(gdb) set print pretty
(gdb) print *ch
$17 = {
  next = 0x0,
  prev = 0x0,
  next_in_room = 0x0,
  prev_in_room = 0x0,
  master = 0x0,
  leader = 0x0,
  fighting = 0x0,
  reply = 0x0,
  retell = 0x0,
  switched = 0x0,
  mount = 0x0,
  hunting = 0x0,
  fearing = 0x0,
  hating = 0x0,
  spec_fun = 0,
  mpact = 0x0,
  mpactnum = 0,
  mpscriptpos = 0,
  pIndexData = 0x0,
  desc = 0x855c8c8,
  first_affect = 0x0,
  last_affect = 0x0,
  pnote = 0x0,
  comments = 0x0,
  first_carrying = 0x855f300,
  last_carrying = 0x855f940,
  in_room = 0x834d948,
  was_in_room = 0x0,
  pcdata = 0x855eb90,
  last_cmd = 0,
  prev_cmd = 0,
  dest_buf = 0x0,
  alloc_ptr = 0x0,
  spare_ptr = 0x0,
  tempnum = 0,
  editor = 0x0,
  first_timer = 0x0,
  last_timer = 0x0,
  morph = 0x0,
  name = 0x855e1c8 "Lordrom",
  short_descr = 0x8225150 "",
  long_descr = 0x8225150 "",
  description = 0x8225150 "",
  num_fighting = 0,
  substate = 0,
  sex = 1,
  class = 0,
  race = 0,
  level = 65,
  trust = 0,
  played = 11542914,
  logon = 1074207862,
  save_time = 0,
  timer = 0,
  wait = 0,
  hit = 3000,
  max_hit = 3000,
  mana = 5000,
  max_mana = 5000,
  move = 3100,
  max_move = 3100,
  practice = 2,
  numattacks = 0,
  gold = 267,
  exp = 2000,
  act = {
    bits = {570431064, 0, 0, 0}
  },
  affected_by = {
    bits = {0, 0, 0, 0}
  },
  no_affected_by = {
    bits = {0, 0, 0, 0}
  },
  carry_weight = 1023,
  carry_number = 8,
  xflags = 0,
  no_immune = 0,
  no_resistant = 0,
  no_susceptible = 0,
  immune = 0,
  resistant = 0,
  susceptible = 0,
  attacks = {
    bits = {0, 0, 0, 0}
  },
  defenses = {
    bits = {0, 0, 0, 0}
  },
  speaks = -1,
  speaking = 1,
  saving_poison_death = 0,
  saving_wand = 0,
  saving_para_petri = 0,
  saving_breath = 0,
  saving_spell_staff = 0,
  alignment = 0,
  barenumdie = 1,
  baresizedie = 4,
  mobthac0 = 0,
  hitroll = 2,
  damroll = 1,
  hitplus = 0,
  damplus = 0,
  position = 12,
  defposition = 0,
  style = 2,
  height = 69,
  weight = 157,
  armor = 76,
  wimpy = 0,
  deaf = 0,
  perm_str = 18,
  perm_int = 17,
  perm_wis = 13,
  perm_dex = 13,
  perm_con = 15,
  perm_cha = 14,
  perm_lck = 15,
  mod_str = 0,
  mod_int = 1,
  mod_wis = 1,
  mod_dex = 1,
  mod_con = 1,
  mod_cha = 0,
  mod_lck = 1,
  mental_state = -12,
  emotional_state = 0,
  pagelen = 24,
  inter_page = 0,
  inter_type = 0,
  inter_editing = 0x0,
  inter_editing_vnum = -1,
  inter_substate = 0,
  retran = 0,
  regoto = 0,
  mobinvis = 0
}
(gdb)


That took quite a bit more room, but laid out things more-or-less like the original structure, so we can easily see each value.

Now we'll try the other structure, DESCRIPTOR_DATA "d":


(gdb) print *d
Cannot access memory at address 0x4a
(gdb)


Clearly there is a problem with d, so we just have to work out why. In this case, we know, because we took out the assignment statement that made it work.


d = ch->desc;


However if it wasn't obvious, we would either look around in the local function, or keep typing "up" to go back to the caller, if the bad value was passed down from an earlier function.

In our case we can see that "d" is a local variable, and that the problem must be in the current function. How we do that is like this:

First type "f" (frame) to get information about the current frame ...


(gdb) f
#1 0x080bef81 in send_to_char_color (
txt=0x82c9a40 "This is where you may post messages to imms only\n\r \n\r \n\rTo edit it.. type hedit imotd\n\r \n\rthen hset save\n\r", ch=0x855e9c0)
at comm.c:2762
2762 write_to_buffer(d, prevstr, 0);


Now list the current function (whose name we saw in the stack frame above), and keep typing "list" until we get down to our current line...


(gdb) list send_to_char_color
2725
2726    /*
2727     * Same as above, but converts &color codes to ANSI sequences..
2728     */
2729    void send_to_char_color( const char *txt, CHAR_DATA *ch )
2730    {
2731      DESCRIPTOR_DATA *d;
2732      char *colstr;
2733      const char *prevstr = txt;
2734      char colbuf[20];
(gdb) list
2735      int ln;
2736
2737      if ( !ch )
2738      {
2739        bug( "Send_to_char_color: NULL *ch" );
2740        return;
2741      }
2742      if ( !txt || !ch->desc )
2743        return;
2744    //  d = ch->desc;
(gdb)
(gdb) list
2745      /* Clear out old color stuff */
2746    /*  make_color_sequence(NULL, NULL, NULL);*/
2747      while ( (colstr = strpbrk(prevstr, "&^")) != NULL )
2748      {
2749        if (colstr > prevstr)
2750          write_to_buffer(d, prevstr, (colstr-prevstr));
2751        ln = make_color_sequence(colstr, colbuf, d);
2752        if ( ln < 0 )
2753        {
2754          prevstr = colstr+1;
(gdb) list
2755          break;
2756        }
2757        else if ( ln > 0 )
2758          write_to_buffer(d, colbuf, ln);
2759        prevstr = colstr+2;
2760      }
2761      if ( *prevstr )
2762        write_to_buffer(d, prevstr, 0);
2763      return;
2764    }


You can save a bit of time here by just hitting <enter> instead of retyping "list" - hitting <enter> tells gdb to repeat the last command.

Another way of achieving the same effect, with less typing, would be to list the current frame, down to the line number we know we are up to:


(gdb) list send_to_char_color, 2762
2730    {
2731      DESCRIPTOR_DATA *d;
2732      char *colstr;
2733      const char *prevstr = txt;
2734      char colbuf[20];
2735      int ln;
2736
2737      if ( !ch )
2738      {
2739        bug( "Send_to_char_color: NULL *ch" );
2740        return;
2741      }
2742      if ( !txt || !ch->desc )
2743        return;
2744    //  d = ch->desc;
2745      /* Clear out old color stuff */
2746    /*  make_color_sequence(NULL, NULL, NULL);*/
2747      while ( (colstr = strpbrk(prevstr, "&^")) != NULL )
2748      {
2749        if (colstr > prevstr)
2750          write_to_buffer(d, prevstr, (colstr-prevstr));
2751        ln = make_color_sequence(colstr, colbuf, d);
2752        if ( ln < 0 )
2753        {
2754          prevstr = colstr+1;
2755          break;
2756        }
2757        else if ( ln > 0 )
2758          write_to_buffer(d, colbuf, ln);
2759        prevstr = colstr+2;
2760      }
2761      if ( *prevstr )
2762        write_to_buffer(d, prevstr, 0);
(gdb)


We found out both of those pieces of information when we typed "frame".




ptype - finding out information about a structure

Say we want to find out more about DESCRIPTOR_DATA. We can type:

ptype DESCRIPTOR_DATA (the structure itself)

or

ptype d (an instance of the structure)

Both cases will give an expansion of the source for the structure:


(gdb) ptype d
type = struct descriptor_data {
    DESCRIPTOR_DATA *next;
    DESCRIPTOR_DATA *prev;
    DESCRIPTOR_DATA *snoop_by;
    CHAR_DATA *character;
    CHAR_DATA *original;
    char *host;
    int port;
    int descriptor;
    sh_int connected;
    sh_int idle;
    sh_int lines;
    sh_int scrlen;
    sh_int mxp;
    bool fcommand;
    char inbuf[1024];
    char incomm[1024];
    char inlast[1024];
    int repeat;
    char *outbuf;
    long unsigned int outsize;
    int outtop;
    char *pagebuf;
    long unsigned int pagesize;
    int pagetop;
    char *pagepoint;
    char pagecmd;
    char pagecolor;
    char *user;
    int newstate;
    unsigned char prevcolor;
} *
(gdb)


This is very handy for seeing what is going on in structures without having to go back to the source.

Remember, if we want to see what the actual structure elements contain at a particular moment, we can print them, eg. "print *d".

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #1 on Fri 16 Jan 2004 12:52 AM (UTC)
Message
Excellent post... that should help a lot of people in their debugging quests. :)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #2 on Fri 16 Jan 2004 02:05 AM (UTC)

Amended on Fri 16 Jan 2004 11:21 PM (UTC) by Nick Gammon

Message
Thanks.

There is one more thing you can do, debug a process that is already running. To do that you find the process ID (with ps) and then start gdb like this:

gdb (executable file) (process ID)

eg.

gdb ../src/smaug 5412


Now, for the next bit ...

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #3 on Fri 16 Jan 2004 02:05 AM (UTC)

Amended on Mon 15 Feb 2010 07:38 PM (UTC) by Nick Gammon

Message
For our next trick, let's try adding some breakpoints.

Sometimes it is nice to see things actually happen before they crash - maybe the problem is not a crash, but simply unexpected behaviour.

In this case we will start up gdb with just the executable file (not a core file) and run from scratch ...


$ gdb ../src/smaug
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) info break
No breakpoints or watchpoints.


I also typed "info break" to see if I had any breakpoints. Naturally enough, there were none at this point.

Let's say we want to debug a problem with the player entering their name, in the "nanny" routine. We'll type "list nanny" to see the source code:


(gdb) list nanny
1723
1724    /*
1725     * Deal with sockets that haven't logged in yet.
1726     */
1727    void nanny( DESCRIPTOR_DATA *d, char *argument )
1728    {
1729    /*      extern int lang_array[];
1730            extern char *lang_names[];*/
1731        char buf[MAX_STRING_LENGTH];
1732        char arg[MAX_STRING_LENGTH];
(gdb)
1733        CHAR_DATA *ch;
1734        char *pwdnew;
1735        char *p;
1736        int iClass;
1737        int iRace;
1738        bool fOld, chk;
1739
1740        while ( isspace(*argument) )
1741            argument++;
1742
(gdb)
1743        ch = d->character;
1744
1745        switch ( d->connected )
1746        {
1747
1748        default:
1749            bug( "Nanny: bad d->connected %d.", d->connected );
1750            close_socket( d, TRUE );
1751            return;
1752
(gdb)
1753        case CON_GET_NAME:
1754            if ( argument[0] == '\0' )
1755            {
1756                close_socket( d, FALSE );
1757                return;
1758            }
1759
1760            argument[0] = UPPER(argument[0]);
1761
1762            /* Old players can keep their characters. -- Alty */


Hitting <enter> a few times (to repeat the list command) takes us down to CON_GET_NAME, where we are processing the player's name.



break - insert breakpoint

Let's insert a breakpoint at the start of nanny itself, and then start examining things from line 1760 onwards, so we insert two breakpoints, and check they are there ...


(gdb) break nanny
Breakpoint 1 at 0x80bc154: file comm.c, line 1738.
(gdb) break 1760
Breakpoint 2 at 0x80bc1f9: file comm.c, line 1760.
(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x080bc154 in nanny at comm.c:1738
2   breakpoint     keep y   0x080bc1f9 in nanny at comm.c:1760
(gdb)


The interesting thing here is we can breakpoint on a function (eg. break nanny) or a line (break 1760) if we know the line number.

You can also specify a file name if required, eg.


(gdb) break comm.c:1760




run - run the program

Now we can type "run" to tell it to start running the program ...


(gdb) run
Starting program: /home/nick/smaug/dist/src/smaug
Fri Jan 16 12:30:32 2004 :: Booting Database
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Fri Jan 16 12:30:32 2004 :: [*****] BOOT: ---------------------[ Boot Log ]--------------------
Fri Jan 16 12:30:32 2004 :: Loading commands
Fri Jan 16 12:30:32 2004 :: Loading sysdata configuration...
... and so on ...


As soon as I type "lordrom" into my client, gdb stops and prints this:


Breakpoint 1, nanny (d=0x855c8d0, argument=0xbfffe0a0 "lordrom") at comm.c:1738
1738        bool fOld, chk;
(gdb)


This is what we expect - we are at the start of the nanny routine, and the word "lordrom" has been passed down as an argument, as well as the descriptor "d".

Let's look at the contents of "d" for the fun of it ...


(gdb) set print pretty
(gdb) p *d
$2 = {
next = 0x0,
prev = 0x0,
snoop_by = 0x0,
character = 0x0,
original = 0x0,
host = 0x855c278 "10.0.0.11",
port = 2025,
descriptor = 12,
connected = 1,
idle = 0,
lines = 0,
scrlen = 24,
mxp = 0,
fcommand = 1 '\001',
inbuf = "\000ordrom\r\n", '\0' <repeats 1014 times>,
incomm = "\000ordrom", '\0' <repeats 1016 times>,
inlast = "lordrom", '\0' <repeats 1016 times>,
repeat = 0,
outbuf = 0x855d530 "\n\r\n\r / _ _", ' ' <repeats 12 times>, "_ _ _____\n\r / |\\ /| /\\ | | / \\ ", '*' <repeats 15 times>, "\n\r / | \\ / | / \\ | | |", ' ' <repeats 12 times>, "* \\\\._."...,
outsize = 2000,
outtop = 0,
pagebuf = 0x0,
pagesize = 0,
pagetop = 0,
pagepoint = 0x0,
pagecmd = 0 '\0',
pagecolor = 0 '\0',
user = 0x827b938 "(pipe breaking)",
newstate = 0,
prevcolor = 7 '\a'
}
(gdb)


We'll also type "list" to see what happens next ...


(gdb) list
1733        CHAR_DATA *ch;
1734        char *pwdnew;
1735        char *p;
1736        int iClass;
1737        int iRace;
1738        bool fOld, chk;
1739
1740        while ( isspace(*argument) )
1741            argument++;
1742


Now we'll type "n" (next) to move onto the next executable statement. The data declarations (like char *p) are not executed so we expect to move down to line 1740 ...


(gdb) n
1740        while ( isspace(*argument) )
(gdb)




continue - continue executing

So far, so good. Let's move on to our next breakpoint. Typing "continue" should keep us going ...


(gdb) cont
Continuing.

Breakpoint 2, nanny (d=0x855c8d0, argument=0xbfffe0a0 "lordrom") at comm.c:1760
1760            argument[0] = UPPER(argument[0]);
(gdb)


Very good, we have stopped at the place we wanted.

It seems we are about to capitalise the argument. Let's see that in operation. The breakpoint is reached before we execute the statement, so we'll check the value of the argument:


(gdb) p argument
$3 = 0xbfffe0a0 "lordrom"
(gdb)




next - go to the next line

Now let's step over that line with "next" and see what it did ...


(gdb) next
1763            if ( !check_parse_name( argument, (d->newstate != 0) ) )
(gdb) p argument
$4 = 0xbfffe0a0 "Lordrom"
(gdb)


OK, it did what we expected, capitalised the name.



step - step into a function

Now we are about to execute the line at 1763, but this time let's "step" rather than "next". The difference is that step takes us into the function (if any) on that line, while next simply processes that line, and any functions. For instance in the earlier example we didn't see exactly how UPPER worked.


(gdb) step
check_parse_name (name=0xbfffe0a0 "Lordrom", newchar=0 '\0') at comm.c:2497
2497         if ( is_reserved_name(name) && newchar )
(gdb) list
2492      * disallowing current area mobnames.  I personally think that if we can
2493      * have more than one mob with the same keyword, then may as well have
2494      * players too though, so I don't mind that removal.  -- Alty
2495      */
2496
2497         if ( is_reserved_name(name) && newchar )
2498            return FALSE;
2499
2500         /*
2501          * Outdated stuff -- Alty


We can see from the line number that we have jumped from line 1763 to line 2497, so we have gone into the function call. It is a bit hard to see because of the comments, so let's type "f" to get the current frame information, and the list the start of that function with "list (function-name)" ...


(gdb) f
#0  check_parse_name (name=0xbfffe0a0 "Lordrom", newchar=0 '\0') at comm.c:2497
2497         if ( is_reserved_name(name) && newchar )
(gdb) list check_parse_name
2482
2483    /*
2484     * Parse a name for acceptability.
2485     */
2486    bool check_parse_name( char *name, bool newchar )
2487    {
2488     /*
2489      * Names checking should really only be done on new characters, otherwise
2490      * we could end up with people who can't access their characters.  Would
2491      * have also provided for that new area havoc mentioned below, while still
(gdb)


We can type "n" a couple of times to see us moving through the function, in this case passing various checks ...


(gdb) n
2509        if ( strlen(name) <  3 )
(gdb) 
2512        if ( strlen(name) > 12 )
(gdb) 
2523            fIll = TRUE;
(gdb)


Because the name is in range, the line which would be executed if it wasn't is not shown (as it isn't executed). I can press <enter> to repeat the last command (next) so that I can just keep hitting <enter> to progress through the function.

Just out of curiosity, let's find the name, and its length:


(gdb) p name
$7 = 0xbfffe0a0 "Lordrom"
(gdb) p strlen (name)
$8 = 7
(gdb)


That was nice, we could actually execute the strlen function, to let gdb work out the name length.



until - step forwards a bit

If we know nothing interesting will happen for a few lines, we can use "until" to reach a line of interest.

For example:


(gdb) until 2524


This executes up to line 2524. We can also supply the name of a function.



finish - leave a function being stepped through

I'm getting bored with the check_parse_name function, so let's stop stepping through it, and go back to the calling frame. We can type "finish" to do this ...


(gdb) finish
Run till exit from #0  check_parse_name (name=0xbfffe0a0 "Lordrom",
    newchar=0 '\0') at comm.c:2526
0x080bc257 in nanny (d=0x855c8d0, argument=0xbfffe0a0 "Lordrom") at comm.c:1763
1763            if ( !check_parse_name( argument, (d->newstate != 0) ) )
Value returned is $9 = 1 '\001'
(gdb)


Notice how we have returned to line 1763 in the nanny function, however we know that check_parse_name returned 1 (true) as its return value.

OK, let's press on with "n" (next):


(gdb) n
1769            if ( !str_cmp( argument, "New" ) )


It seems we are checking if someone is trying to call themselves "New" - we can see for ourselves what the result of this test will be:


(gdb) p str_cmp( argument, "New" )
$10 = 1 '\001'


OK, that returned 1, remembering that strcmp (and presumably str_cmp) return 0 for a match then a non-zero result means the name was not "New".

Onto the next line:


(gdb) n
1802            if ( check_playing( d, argument, FALSE ) == BERR )
(gdb)


Let's see what that function returns, by calling it ourself:


(gdb) p check_playing( d, argument, FALSE )
$2 = 0 '\0'


Wow! It looks like we can call any function in the program ourselves from the debugger. The test a few lines up checks to see if the return result is BERR, so let's see what BERR is ...


(gdb) p BERR
$1 = 255




Calling any function


(gdb) list
1797                  write_to_buffer(d, "Illegal name, try another.\n\rName: ", 0);
1798                  return;
1799                }
1800            }
1801
1802            if ( check_playing( d, argument, FALSE ) == BERR )
1803            {
1804                write_to_buffer( d, "Name: ", 0 );
1805                return;
1806            }
(gdb)


Let's play around and try calling a function ourselves that does something interesting. The function write_to_buffer looks like a good candidate. Let's throw in a call to that:


(gdb) p write_to_buffer (d, "But does it get goat's blood out?\n", 0)
$3 = void
(gdb)


That seemed to do something. Nothing has appeared on my screen yet, so I might need to let the program run a bit ...


(gdb) cont
Continuing.
Fri Jan 16 13:32:02 2004 :: Preloading player data for: Lordrom (1K)



Now in my client window I see ...


Enter your character's name, or type new: 
lordrom
But does it get goat's blood out?
Password: 


So, that extra function call I did was actually executed. This means that you can do arbitrary calls inside the debugger to see what would happen if you called this or that function.

In the example above I did the call by doing "p" (print) which did the function call and displayed the returned value. Another approach would be to simply call the function, like this:


(gdb) call write_to_buffer (d, "But does it get goat's blood out?\n", 0)
(gdb)



Now I type in a password "fred", and the nanny breakpoint is entered again ...


Breakpoint 1, nanny (d=0x855c8d0, argument=0xbfffdd20 "fred") at comm.c:1738
1738        bool fOld, chk;


Time to let the program get on with it.



delete - delete breakpoints

To delete one breakpoint you need to type its number, which you can find from "info break" ...


(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x080bc154 in nanny at comm.c:1738
2   breakpoint     keep y   0x080bc1f9 in nanny at comm.c:1760
(gdb) delete 1


Or you can type "delete" on its own to delete all breakpoints.

Another way is to "clear" the breakpoint, by giving the address given to set it. I'll put the breakpoint back, and then clear it ...


(gdb) break nanny
Breakpoint 10 at 0x80bc154: file comm.c, line 1738.
(gdb) clear nanny
Deleted breakpoint 10
(gdb)




Changing data

Finally let's play with changing variables. As we have seen we can "print" a function, which effectively executes it, like strlen. Let's try changing some string data.

I'll re-add the breakpoint at nanny ("break nanny") and then type "cont" to continue executing the MUD server.

I'll type the name "nick" into my client, and wait for the breakpoint to be hit ...


Breakpoint 11, nanny (d=0x855c8d0, argument=0xbfffdd20 "nick") at comm.c:1738
1738        bool fOld, chk;


OK, we have the word "nick" as the argument. Let's change it to "Lordrom". I'll go up a level to see what the argument is ...


(gdb) up
#1  0x080ba142 in game_loop () at comm.c:731
731                                     nanny( d, cmdline );
(gdb) list
726                               set_pager_input(d, cmdline);
727                             else
728                               switch( d->connected )
729                               {
730                                default:
731                                     nanny( d, cmdline );
732                                     break;
733                                case CON_PLAYING:
734                                     interpret( d->character, cmdline );
735                                     break;
(gdb) 


OK, I see that it is really "cmdline". Lets "print" a strcpy to copy the word "Lordrom" into it ...


(gdb) p strcpy (cmdline, "Lordrom")
$4 = -1073750752
(gdb) down
#0  nanny (d=0x855c8d0, argument=0xbfffdd20 "Lordrom") at comm.c:1738
1738        bool fOld, chk;


Going back down (into nanny), I now see that the argument is now Lordrom, instead of "nick".

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #4 on Fri 16 Jan 2004 03:11 AM (UTC)
Message
Conditional breakpoints

The next thing I want to describe is conditional breakpoints.

Say you have a bug where things crash sometimes, and you want to see when.

For instance, sometimes a NULL gets passed to a certain routine, or a certain variation of a command crashes.

What you can do is insert a conditonal breakpoint.

Say, for example, that when the player types "eat fish" you get a crash (or funny results). You can insert a conditional breakpoint.

First, let's look at the do_eat function.


(gdb) list do_eat
664           WAIT_STATE( ch, PULSE_PER_SECOND );
665         return;
666     }
667
668     void do_eat( CHAR_DATA *ch, char *argument )
669     {
670         char buf[MAX_STRING_LENGTH];
671         OBJ_DATA *obj;
672         ch_ret retcode;
673         int foodcond;
(gdb)
674         bool hgflag = TRUE;
675
676         if ( argument[0] == '\0' )
677         {
678             send_to_char( "Eat what?\n\r", ch );
679             return;
680         }
681
682         if ( IS_NPC(ch) || ch->pcdata->condition[COND_FULL] > 5 )
683             if ( ms_find_obj(ch) )
(gdb)


We see it has two arguments. Let's set a breakpoint on the argument, to stop if it is "fish".


(gdb) break do_eat if strcmp (argument, "fish") == 0
Breakpoint 1 at 0x8119e94: file misc.c, line 674.


This puts a break at the start of do_eat if the thing being eaten is "fish".

Alternatively, we could break on something in the first argument, like the player name:


(gdb) break do_eat if strcmp (ch->name, "Lordrom") == 0
Note: breakpoint 1 also set at pc 0x8119e94.
Breakpoint 2 at 0x8119e94: file misc.c, line 674.


Then when we start the program running, and then type "eat fish", the breakpoint fires:


Breakpoint 1, do_eat (ch=0x855e9a8, argument=0xbfffda24 "fish") at misc.c:674


However, eating other things does not fire the breakpoint.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #5 on Fri 16 Jan 2004 06:21 AM (UTC)

Amended on Fri 16 Jan 2004 06:22 AM (UTC) by Nick Gammon

Message
Getting fancy - making a gdb command to show my inventory

We can use user-defined commands in gdb to do things like "walk" an internal list.

Here is an example. My character, Lordrom, has a few things in his inventory, and is wearing a few things. Like this, from my client screen:


You are carrying:
     a cherry pie
     a fountain (2)
     a chocolate cake (3)
     dried rabbit meat (2)

You are using:
<worn around neck>  a strong metal collar
<worn on legs>      mail leggings
<worn on feet>      a pair of black combat boots
<worn on hands>     kid gloves
<worn around wrist> a charm bracelet
<wielded>           a menacing granitic blade
<held>              The Adventurer's Guide


Let's try to make gdb show all that to us with a minimum of fuss.


(gdb) define inventory
Type commands for definition of "inventory".
End with a line saying just "end".
>set $i = ch->first_carrying
>while $i
 >print $i->short_descr
 >set $i = $i->next
 >end
>end


OK, now we'll test it. In my earlier example (a breakpoint in do_eat function) we'll now type "inventory", our new command ...


(gdb) inventory
$67 = 0x833fdf0 "The Adventurer's Guide"
$68 = 0x8343220 "a menacing granitic blade"
$69 = 0x833da78 "a strong metal collar"
$70 = 0x833f018 "kid gloves"
$71 = 0x833ce20 "a charm bracelet"
$72 = 0x833d040 "a pair of black combat boots"
$73 = 0x8372ba8 "mail leggings"
$74 = 0x8371528 "a cherry pie"
$75 = 0x83430c0 "a fountain"
$76 = 0x8371300 "a chocolate cake"
$77 = 0x833ed98 "dried rabbit meat"
(gdb)


Perfecto! We have walked our inventory list, in one command. Of course, we could add more "print" items to the user-defined command to show other things, like whether the item was carried or worn.

Once you have defined user-commands you can show them with "show user":


(gdb) show user
User command inventory:
  set $i = ch->first_carrying
  while $i
    print $i->short_descr
    set $i = $i->next
  end


You can also use "if" inside user-defined commands (like while) to test conditions. For example, we might only show things with a wear_loc of -1 ...


(gdb) show user
User command inventory:
  set $i = ch->first_carrying
  while $i
    if $i->wear_loc == -1
      print $i->short_descr
    end
    set $i = $i->next
  end


Now let's test that ...


(gdb) invent
$100 = 0x8371528 "a cherry pie"
$101 = 0x83430c0 "a fountain"
$102 = 0x8371300 "a chocolate cake"
$103 = 0x833ed98 "dried rabbit meat"
(gdb) 


Evidently the wear_loc of -1 means we are not wearing these things, we are carrying them. That agrees with our inventory list.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #6 on Fri 16 Jan 2004 03:22 PM (UTC)

Amended on Fri 16 Jan 2004 09:26 PM (UTC) by Nick Gammon

Message
display - see the value of an expression

You can see what a variable is doing as you step through a function by setting it as a "display" item. This is like doing an "auto-print" - every time you "step" or "next" the item is displayed.

For instance, in do_eat I might be interested in the values of foodcond and obj, so I set them up to display automatically ...


(gdb) display obj
4: obj = (OBJ_DATA *) 0x855fae0

(gdb) display foodcond
5: foodcond = 10
(gdb)


Now as I step through the function the values are displayed for each line ...


(gdb) next
676         if ( argument[0] == '\0' )
5: foodcond = 10
4: obj = (OBJ_DATA *) 0x855fae0

(gdb)
682         if ( IS_NPC(ch) || ch->pcdata->condition[COND_FULL] > 5 )
5: foodcond = 10
4: obj = (OBJ_DATA *) 0x855fae0

(gdb)
683             if ( ms_find_obj(ch) )
5: foodcond = 10
4: obj = (OBJ_DATA *) 0x855fae0

(gdb)
686         if ( (obj = find_obj(ch, argument, TRUE)) == NULL )
5: foodcond = 10
4: obj = (OBJ_DATA *) 0x855fae0

(gdb)
687             return;
5: foodcond = 10
4: obj = (OBJ_DATA *) 0x0

(gdb)


Notice how foodcond and obj are displayed for each line (I kept doing an implied "next" by hitting <enter>).



watch - watch for a value to change

Using "display" is a bit tedious because you need to keep your eye on each displayed value, to see if it changes. That is fine if they change every line or so, but perhaps we just want to see when (say) obj changes. In that case we need a "watch".

First, we'll take out the display items with "undisplay" ...


(gdb) undisplay
Delete all auto-display expressions? (y or n) y
(gdb)


Next, we'll watch obj ...


(gdb) watch obj
Hardware watchpoint 7: obj
(gdb)


This is nice - the watch is implemented in hardware, so it won't slow the program down. Now we can keep running until obj changes ...


(gdb) cont
Continuing.
Hardware watchpoint 7: obj

Old value = (OBJ_DATA *) 0x0
New value = (OBJ_DATA *) 0x855f758
0x08119f0a in do_eat (ch=0x855e9a8, argument=0xbfffda24 "pie") at misc.c:686
686         if ( (obj = find_obj(ch, argument, TRUE)) == NULL )
(gdb)


The value of obj has changed from 0x0 (NULL) to a non-zero value. We may as well see what the value means (I typed "eat pie" into the client program) ...


(gdb) p *obj
$104 = {
  next = 0x855f7f8,
  prev = 0x855f6b8,
  next_content = 0x855f7f8,
  prev_content = 0x855f6b8,
  first_content = 0x0,
  last_content = 0x0,
  in_obj = 0x0,
  carried_by = 0x855e9a8,
  first_extradesc = 0x0,
  last_extradesc = 0x0,
  first_affect = 0x0,
  last_affect = 0x0,
  pIndexData = 0x8371478,
  in_room = 0x0,
  name = 0x8371510 "cherry pie",
  short_descr = 0x8371528 "a cherry pie",
  description = 0x8371548 "A cherry pie makes your mouth water.",
  action_desc = 0x8225160 "",
  item_type = 19,
  mpscriptpos = 0,
  extra_flags = {
    bits = {0, 0, 0, 0}
  },
  magic_flags = 0,
  wear_flags = 16385,
  mpact = 0x0,
  mpactnum = 0,
  wear_loc = -1,
  weight = 1,
  cost = 100,
  level = 0,
  timer = 0,
  value = {4, 0, 0, 0, 0, 0},
  count = 1,
  serial = 1292
}
(gdb)




hook functions - add to existing command functionality

A nice variation on "display" would be to automatically display all local variables whilst debugging (local variables being those declared inside a function).

This can be done easily with a little "hook" function.

Hook functions can be defined to execute either before or after existing commands.

In this case, to examine local variables after doing a "next" I need to define a function "hookpost-next" (hook into post (after) "next"), like this:


(gdb) define hookpost-next
Type commands for definition of "hookpost-next".
End with a line saying just "end".
>info local
>end


For this demonstration I put a breakpoint into update_handler, which has a nice list of local variables. Then, by typing "n" (next) I see, for each "next" all my local variables displayed ...


(gdb) n
2023        if ( --pulse_area     <= 0 )
pulse_area = 27
pulse_mobile = 16
pulse_violence = 8
pulse_point = 178
pulse_second = 4
stime = {
  tv_sec = 139839696,
  tv_usec = 136365920
}
etime = {
  tv_sec = -1073750472,
  tv_usec = 134979683
}

(gdb) n
2029        if ( --pulse_mobile   <= 0 )
pulse_area = 26
pulse_mobile = 16
pulse_violence = 8
pulse_point = 178
pulse_second = 4
stime = {
  tv_sec = 139839696,
  tv_usec = 136365920
}
etime = {
  tv_sec = -1073750472,
  tv_usec = 134979683
}

(gdb)


Similarly for a hook to do something before each "next" command I can make a hook-next command:


(gdb) define hook-next
Type commands for definition of "hook-next".
End with a line saying just "end".
>echo --- Doing Next ---\n
>end

(gdb) n
--- Doing Next ---



This illustrates executing my user-defined command before the "next" command, and inside that I am just using "echo" to display something on the screen.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #7 on Fri 16 Jan 2004 04:05 PM (UTC)

Amended on Fri 16 Jan 2004 04:10 PM (UTC) by Nick Gammon

Message
Making pre-canned functions

If these user-defined commands sound powerful, you can make a batch of them and put them into a separate file. There are various ways of processing such a file.

One is to make a file .gdbinit (Unix) or gdb.ini (Windows) and put that in your home directory, or the current directory.

Another is to simply say (in gdb): source <filename>

You could also use the ini files to set preferred display options (like pretty print for structures).

Here is an example ...




Define a user-command to show the same things that the SegVio handler showed
(see earlier post in this thread)

First, use your text editor to make a file (I'll call it gdb_cmd), containing this:




define show_smaug_crash_info
 printf "%s\n", lastplayercmd
 set $ch = first_char
 while $ch
    printf "%cPC: %-20s room: %d\n", IS_NPC($ch) ? 'N' : ' ', $ch->name, $ch->in_room->vnum
    set $ch = $ch->next
  end
end




This does the same things as the SegVio handler, except it uses a local gdb variable $ch instead of the C variable ch. It also uses gdb's printf, rather than sprintf.

Now, if smaug crashes, we need to "source" this command:


(gdb) source gdb_cmd


That loads our user-defined command into gdb's memory (only need to do this once per gdb execution).

Now we can execute the command ...


(gdb) show_smaug_crash_info
Lordrom used eat food
NPC: supermob             room: 3
NPC: Puff                 room: 2
NPC: demon imp            room: 6
NPC: Tsythia Mistress Headmistress room: 10300
NPC: Domick Dom teacher   room: 10303
NPC: healer spectre cage  room: 10305
NPC: Abbigayle language teacher room: 10306
NPC: chadoyn cage         room: 10308
NPC: statue warrior cage  room: 10318
NPC: Toric lord healer    room: 10319
NPC: statue druid cage    room: 10320
NPC: statue vampire cage  room: 10321
NPC: statue thief cage    room: 10322
NPC: statue mage cage     room: 10323
NPC: statue cleric cage   room: 10324
... and so on ...


Of course, you could expand the command to show all sorts of things as well, maybe check the integrity of internal variables, and so on.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #8 on Fri 16 Jan 2004 08:01 PM (UTC)
Message
Changing execution behaviour

Now we'll look at changing the way a program executes at run-time, using gdb.

Let's try to allow an "illegal name" into our SMAUG game. For instance, the name a1234. A quick browse of the nanny routine shows that the test is done in check_parse_name, so let's put a breakpoint on line 1761:


(gdb) list 1761, +10
1761            if ( !check_parse_name( argument, (d->newstate != 0) ) )
1762            {
1763                write_to_buffer( d, "Illegal name, try another.\n\rName: ", 0 );
1764                return;
1765            }
1766
1767            if ( !str_cmp( argument, "New" ) )
1768            {
1769                if (d->newstate == 0)
1770                {
1771                  /* New player */
(gdb)


(gdb) break 1761
Breakpoint 5 at 0x80bc2fb: file comm.c, line 1761.



I now type "new" and then "a1234" into my client and reach the breakpoint:


Breakpoint 5, nanny (d=0x855c8d0, argument=0xbffff1a0 "A1234") at comm.c:1761
/home/nick/smaug/dist/src/comm.c:1761:42750:beg:0x80bc2fb


There are a number of methods of achieving this, so I'll do each one in turn ...




jump - alter execution address


(gdb) jump 1767
Continuing at 0x80bc338.


That worked OK. The client shows the line:


Did I get that right, A1234 (Y/N)?


So, you can use "jump (line)" to jump past (or back) to somewhere else in the current function. You can also jump to a completely different function, however that is not recommended unless you want to debug some bizarre problems with the stack.



return - return from a function with a supplied value

Alternatively, we can step into the function that does the name check, and then "return TRUE" to force it to return a "good" return value. "return" is different from "finish", described earlier, because "return" actually terminates the current function prematurely.

Let's try that ...


(gdb) step
check_parse_name (name=0xbffff1a0 "A1234", newchar=1 '\001') at comm.c:2495
/home/nick/smaug/dist/src/comm.c:2495:64628:beg:0x80be7fa

(gdb) return TRUE
Make check_parse_name return now? (y or n) y
#0  0x080bc317 in nanny (
    d=0x855c8d0, argument=0xbffff1a0 "A1234") at comm.c:1761
/home/nick/smaug/dist/src/comm.c:1761:42750:beg:0x80bc317

(gdb) cont
Continuing.


That also worked. It was a bit more involved, but had the advantage that we could control the value returned.



call/set - change values inside the program

Another approach is to give a good name (eg. "nick") and then once it passes the test, change it to something else. For example, we'll put a breakpoint after the test for bad names ...


(gdb) break 1767
Breakpoint 6 at 0x80bc338: file comm.c, line 1767.


Now let the program continue and wait for the breakpoint to be hit ...


Breakpoint 6, nanny (d=0x8561690, argument=0xbffff1a0 "Nick") at comm.c:1767
/home/nick/smaug/dist/src/comm.c:1767:42946:beg:0x80bc338


Now use "call" to copy a new name into the argument ...


(gdb) call strcpy (argument, "A1234")
$7 = -1073745504


In an earlier post I did the same thing by using "print" - they are very similar, except that "print" shows the value returned by strcpy.

However if the variable was a simple variable (like an int) we could simply use "set var", like this:


(gdb) set var hp=100


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #9 on Sat 17 Jan 2004 12:43 AM (UTC)

Amended on Wed 21 Feb 2024 08:45 PM (UTC) by Nick Gammon

Message
Acknowledgements

Thanks for information and suggestions from:


  • Ksilyan

  • Samson

  • Kalahn

  • The book "Debugging with GDB" by Richard Stallman, Roland Pesch, Stan Shebs et. al. This book can be ordered from the Free Software Foundation (see: www.gnu.org). ISBN 1-882114-88-4.

    You should be able to find the online version at:



    http://sourceware.org/gdb/current/onlinedocs/gdb_toc.html/





There is also a reference card (2 pages of closely condensed information) about gdb. Various copies are available, one can be obtained from:



http://www.gammon.com.au/files/utils/gdb-refcard.pdf







The information on this page may be freely distributed provided credit is given to the original author(s).

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #10 on Sat 17 Jan 2004 01:39 AM (UTC)
Message
commands - automating breakpoint behaviour

You can automate what happens when a breakpoint is reached. Rather than manually doing something, you may wish to do it automatically.

Here is an example. Say you just want to note when someone eats something, but otherwise continue running. Here is how you might do it ...

First, set a breakpoint at the do_eat function...


(gdb) break do_eat
Breakpoint 1 at 0x8119f54: file misc.c, line 674.


Now add some commands to the breakpoint. The first one is "silent" to suppress the breakpoint message, then we echo a message, and then continue execution ...


(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent
>echo Now in do_eat function ***\n
>cont
>end


Now we'll resume execution and do some eating from our client program ...


Now in do_eat function ***
Now in do_eat function ***
Now in do_eat function ***


We could add more information of course, like who was eating what.

Let's type "info break" to check what the breakpoint did ...


(gdb) info break
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08119f54 in do_eat at misc.c:674
        breakpoint already hit 3 times
        silent
        echo Now in do_eat function ***\n
        cont
(gdb)


Note the summary, that it was hit 3 times, that could be useful to know later on.

You could use this feature to maybe change a variable for debugging purposes automatically, or maybe use "if" and decide whether to continue or not based on some condition.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #11 on Sat 17 Jan 2004 04:23 AM (UTC)
Message
A few more tips ...

Reading up on gdb a bit more has revealed a few more helpful things ...



condition - adding/removing conditions from breakpoints

Say you have added a breakpoint, but find it firing too often. Without deleting and re-adding it, you can add a condition, like this:


(gdb) info break
Num Type           Disp Enb Address    What
2   breakpoint     keep y   0x0049f7c3 in nanny at comm.c:1745
        breakpoint already hit 4 times
(gdb)

(gdb) cond 2 d->connected == 1


The "cond" line adds the condition to breakpoint 2, that it only stops if d->connected == 1.

However if we want to remove the condition we just do this:


(gdb) cond 2
Breakpoint 2 now unconditional.




set listsize n

If you prefer to see more source lines when you type "list" you can amend the default from 10 lines to (say) 20, like this:


set listsize 20




search - search source file for any string

You can search from the current line with "search" to find any regular expression. eg.


(gdb) search player
1762            /* Old players can keep their characters. -- Alty */
(gdb)

1773                  /* New player */
(gdb)

1774                  /* Don't allow new players if DENY_NEW_PLAYERS is true */


In this example I searched for the word "player" and by pressing <enter> caused gdb to repeat the search command, and find subsequent matching lines. This command has the synonym "forward_search" which can be abbreviated to "fo".

Similarly use "reverse-search" (rev) to search backwards.



info functions - list all known functions

If you want a list of functions that match a regular expression (maybe because you don't remember the full name) you can type "info functions", like this:


(gdb) info functions do_
All functions matching regular expression "do_":

File act_move.c:
void do_bashdoor(CHAR_DATA *, char *);
void do_bolt(CHAR_DATA *, char *);
void do_climb(CHAR_DATA *, char *);
void do_close(CHAR_DATA *, char *);
void do_down(CHAR_DATA *, char *);
void do_east(CHAR_DATA *, char *);
void do_enter(CHAR_DATA *, char *);
void do_leave(CHAR_DATA *, char *);
void do_lock(CHAR_DATA *, char *);
void do_north(CHAR_DATA *, char *);
void do_northeast(CHAR_DATA *, char *);
void do_northwest(CHAR_DATA *, char *);
void do_open(CHAR_DATA *, char *);
void do_rest(CHAR_DATA *, char *);
void do_sit(CHAR_DATA *, char *);
void do_sleep(CHAR_DATA *, char *);
void do_south(CHAR_DATA *, char *);
... and so on ...




info variables - list variables in global scope

You can use "info variables" to see which variables are used in what files ...



(gdb) info variables
All defined variables:

File act_move.c:
bool DONT_UPPER;
bool MOBtrigger;
int area_version;
char * constdir_name[11];
const sh_int movement_loss[16];
const sh_int rev_dir[11];
char * constroom_sents[16][25];
char * constsect_names[16][2];
const int sent_total[16];
const int trap_door[10];
ROOM_INDEX_DATA *vroom_hash[64];

File act_obj.c:
bool DONT_UPPER;
bool MOBtrigger;
int area_version;
... and so on ...


You can append a regular expression to narrow the variable list down (eg. info variables room).



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #12 on Tue 17 Feb 2004 11:12 PM (UTC)

Amended on Sun 18 Apr 2004 03:26 AM (UTC) by Nick Gammon

Message
Examining memory

To examine memory in detail (perhaps there is an unprintable character there) you can use the "x" (examine) command.

For example, whilst debugging a Unicode problem I wanted to see exactly what data was in a variable:


(gdb) x/7c ccom
0x81c62c0 <ccom>:       116 't' 104 'h' 105 'i' 110 'n' 107 'k' 32 ' '  32 ' '
(gdb) x/7x ccom
0x81c62c0 <ccom>:       0x74    0x68    0x69    0x6e    0x6b    0x20    0x20
(gdb) x/s ccom
0x81c62c0 <ccom>:        "think  "


These examples shows examining the variable "ccom" in various ways. In each case I typed "x" (examine) followed by a "format specifier":


  • x/7c - look at the next 7 bytes as characters
  • x/7x - look at the next 7 bytes in hex
  • x/s - show it as a string (stops at a 0x00 byte)


The detail of "x" is (you can type "help x" to remind you when using gdb):

"x" followed by a slash and then:


  1. Item count (eg. number of bytes, words, instructions etc.)

  2. Format letter:

    • o (octal)
    • x (hex)
    • d (decimal)
    • u (unsigned decimal)
    • t (binary)
    • f (float)
    • a (address)
    • i (instruction)
    • c (char)
    • s (string)


  3. Size letter:

    • b (byte, 1 byte)
    • h (halfword, 2 bytes)
    • w (word, 4 bytes)
    • g (giant, 8 bytes)



The specified number of objects of the specified size are printed according to the format.


The size letter is useful is you want to look at the data in a different way to the "native" size.

For example:


  • x/7xb - look at the next 7 bytes (char) in hex
  • x/7xh - look at the next 7 16-bit groups (short) in hex
  • x/7xw - look at the next 7 32-bit groups (long) in hex
  • x/7xg - look at the next 7 64-bit groups (long long or __int64) in hex


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #13 on Tue 26 Jun 2007 10:26 PM (UTC)
Message
This thread has been closed. Please post debugging questions to a new thread.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,120 posts)  Bio   Forum Administrator
Date Reply #14 on Wed 16 Dec 2009 07:10 PM (UTC)
Message
Debugging programs that deliberately exit

Sometimes a program will not crash, per se, just exit with a non-zero exit code. This is usually because of a situation like this:


if ( <something bad happens> )
  {
  printf ("Some error message.\n");
  exit (1);  // exit the program
  }


Now if there is a lot of code like this in the program (and maybe they didn't put a printf there to tell you why it exits) then you have a program that quietly stops running for no obvious reason.

To work around this, put a breakpoint on "exit" itself, like this:


(gdb) break exit
Function "exit" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (exit) pending.
(gdb) run


Now when you run the program, instead of exiting, the breakpoint will be hit, and then you can type "bt" (backtrace) to find where in the code the exit instruction was.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


86,445 views.

This subject is now closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.