Introduction(top)
Consider the following program:
%%%SOURCE CODE GOES HERE%%%
It's ok to debug your program myprog.c once in a while by adding lines such as printf("DEBUG: The variable i=d%\n", i);
but you should get used to something more sophisticated (it is not necessarily more complicated).
This tutorial shows some examples how to use gdb from inside Emacs, but it also applies to the debugger in general.
For example, you can start debugging with gdb myprog.c., step to the line you want to debug, then inside gdb type exactly the same as you would in your source code: printf "DEBUG: The variable i=d%\n", i
No more reasons for not using gdb!
Other important tools to help you produce better code are ddd (GUI for gdb), splint (security check a.o.) and Valgrind (memory leak check a.o.).
Debugging a running program (client/server example)(top)
Client/server debugging is a bit more tricky than debugging "simple executables".
Normally we are interested in debugging the server's child process created by fork(), not the parent, as the parent normally terminates after creating the child process.
To do this, we need to startup the server, get the child process ID, and attach it to gdb.
Here follows an example debug session for a buggy HTTP server.
(Check out the complete source code at http://www.redantigua.com/src/c-more-examples/basichttpd/.)
Input is shown in red, output is shown in black, and hints are shown in green.
We will need 2 windows for this session.
The following warning messages may or may not be displayed:
A problem internal to GDB has been detected,
further debugging may prove unreliable.
Quit this debugging session? (y or n) n
A problem internal to GDB has been detected,
further debugging may prove unreliable.
Create a core file of GDB? (y or n) n
Reading symbols from /home/redantig/public_html/src/c-more-examples/basichttpd/basichttpd...done.
Reading symbols from /lib/libc.so.5...done.
Loaded symbols for /lib/libc.so.5
Reading symbols from /libexec/ld-elf.so.1...done.
Loaded symbols for /libexec/ld-elf.so.1
0x280c42b7 in accept () from /lib/libc.so.5
(gdb) s
Single stepping until exit from function accept,
which has no line number information.
Window 2:
Client - connect.
telnet 0 1024
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
Window 1:
Server - accepts connection and waits for a HTTP Request from the client with recvfrom().
Let's set a breakpoint to examine what the server actually receives.
main (argc=1024, argv=0xbfbfecac) at basichttpd.c:194
194 if ((nbytes = recvfrom(fd_connection, request_header, MAXREQUESTLEN - 1 , 0, (struct sockaddr *)&client_addr, &addr_len)) < 0)
(gdb) b 203
Breakpoint 1 at 0x804a130: file basichttpd.c, line 203.
(gdb)
Window 2:
Client - send the HTTP Request.
GET /index.html HTTP/1.0Host: localhost:1024(RETURN)
Window 1:
Server - debug step, check how many bytes we received, and the HTTP Request itself.
(gdb) s
Breakpoint 1, main (argc=1024, argv=0xbfbfecac) at basichttpd.c:203
203 request_header[nbytes] = '\0';
(gdb) print nbytes
$2 = 28
(gdb) print request_header$4 = "GET /index.html HTTP/1.0\r\n\r\n\000\000\000\000\000\020\000\000ò<\005
(\037¹\b(\037¹\b(|ê¿¿v<\005(è\000\000\000xw\b(\000Ð\023\000س\006( 0\a(
\000\000\000\000¬ê¿¿!<\005(\037¹\b(+2¦\t\000\"\a(\000\000\000\000Õw\b(\000\"\a
(¼ê¿\000س\006(\000\000\000\000xw\b(,ë¿¿m:\005(\037¹\b(+2¦\t
\020 \006(\000ë¿¿\000\000\000\000\004ë¿¿ ·\006(\000\000\000
\000ô¹\006(@\020\a(üê¿¿\000 \a(\000\"\a(\003\000\000\000\000\200\023(\2229\005("...
(gdb) printf "%s", request_header
GET /index.html HTTP/1.0
(gdb) s
httprequestline (request_header=0xbfbfea2c "GET /index.html HTTP/1.0\r\n\r\n") at http.c:225
225 return strtoken(request_header, CRLF, 0);
(gdb) sstrtoken (str=0x1 <Error reading address 0x1: Bad address>, sep=0x4 <Error reading address 0x4: Bad address>, n=-1077941716)
at string.c:223
223 {
(gdb)
As we see, print displays the complete request_header char array (255 chars in this example), while printf just displays the NULL-terminated string we want to investigate.
The HTTP Request seems to be received correctly by the server.
Anyhow, when we want to split the separate tokens GET, /index.html, and HTTP/1.0 with strtoken(), something goes wrong.
Window 1:
We continue the debug session, just to exit.
(dbg) c
Continuing.
[error] 2007-05-25 17:14:47 [127.0.0.1] "Unknown error: 0"
At least a server error message is generated (although buggy itself), and it is also sent to the client.
Window 1:
Server - type CTRL-C to exit if connection hangs.
Program received signal SIGINT, Interrupt.
0x280c42b7 in accept () from /lib/libc.so.5
(gdb) quit
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /home/redantig/public_html/src/c-more-examples/basichttpd/basichttpd, process 22862kill 22862
Window 2:
Client - hangup.
Connection closed by foreign host.
To resume, we isolated the problem to httprequestline():
(We ignore the buggy HTTP Response at the moment.)
/* Extract the Request Line (the first line) from a Request Header. */char *
httprequestline(char *request_header)
{
return strtoken(request_header, CRLF, 0);
}
There is no error check for strtoken(). Bad!
We suppose incorrectly that strtoken always returns with success.
A modified version of strtoken() may look like:
/* httprequestline() - extract the Request Line (the first line) from a Request Header. */char *
httprequestline(char *request_header, char **err_msg)
{
char *request_line = NULL;
if ((request_line = strtoken(request_header, CRLF, 0)) == NULL)
{
asprintf(err_msg, "Error at file %s, function %s(), line %d: Couldn't retreive the HTTP Request-Line\n", __FILE__, __FUNCTION__, __LINE__);
}
else
{
*err_msg = NULL;
}
return request_line;
}
Note the extensive error message, including the use of __FILE__, __FUNCTION__, and __LINE__.
If you get used to use them at every check, you will save yourself a lot of time when debugging.
In this kind of application, maybe we don't want to be too detailed in our error messages sent to the client, but the server should log an error message as detailed as possible.
Further on, we try to split the the HTTP Request-Line GET /index.html HTTP/1.0 into 3 words using the strsplit() function.
However, there is a bug in strsplit(), which in this case should return the words GET, /index.html, and HTTP/1.0, and set ntokens to 3.
Anyhow, even if strsplit() seems to split the line correctly into words, something goes wrong, as we see in the following debug session:
We see that ntokens (called *n locally) is set to 4 instead of 3, and the last token is not HTTP/1.0, as expected, but NULL (0x0).
(Anyhow, tokens[4] is correctly returning an error when trying to access the non-existing token.)
Time to investigate the source code...
There seems to be unpredictable results using strtok() in strsplit().
That is, unpredictable due to my way to use strtok(), which obviously isn't correct.
Of course there are other solutions, some mentioned at comp.lang.c FAQ list � Question 13.6.
The FreeBSD strsep() man page recommends to replace strtok() with strsep() (even if there are platform compatibility problems):
The strsep() function is intended as a replacement for the strtok() function.
While the strtok() function should be preferred for portability
reasons (it conforms to ISO/IEC 9899:1990 (``ISO C90'')) it is unable to
handle empty fields, i.e., detect fields delimited by two adjacent delimiter
characters, or to be used for more than a single string at a time.
Googling around, we soon find that strsep() are missing on Solaris and on HP-UX.
This isn't the only platform compatibility problem - the function asprintf() is missing on AIX,
so to make our application platform independent, we have to include our own version of asprintf() and strsep().
The ideal situation is of course to try making better source code (in this case, using strsep() instead of strtok()), but at the same time make the source code as platform independent as possible (in this case, make it compatible with Solaris and HP-UX).
The solution should be to write two different versions of strsplit(), one using strsep() (used when available), and another using strtok().
But for now, just to fix our bug, we will rewrite strsplit(), still using strtok(), and put "strsplit-using-strsep" on our TODO-list.
The problem when using strtok() is keeping record of its internal state, that may confuse the programmer (that is, me).
This new version of strsplit() avoids using strtok() to count the tokens.
As we can see, at least we get another error message when fixing strsplit():
Window 2:
Client - receives buggy HTTP Response.
(null) 500 Internal Server Error
Date: 2007-06-04 12:08:28
Server: basicHTTPd/0.1
Last-Modified: 2007-06-04 12:08:28
Content-Type: (null)
Content-Length: 0
<html><head><title>basicHTTPd error: Internal Server Error (500)</title></head><body><h1>Internal Server
Error (500)</h1><pre>[error at basichttpd.c -> main() -> line 222]
2007-06-04 12:08:28 [127.0.0.1] "./basichttpd: httpstatuscode(): Client HTTP Request-Line error:
When using HTTP/1.0, the client must send the server host name, either as part of an
absolute URI in the Request-Line, or as a separate "Host" header field.<br>
Now the problem is the "Host" header field.
We are not going into details here how to solve this problem, but the method is the same as above:
Debug using gdb, isolate the problem, make a small test program if necessary to isolate the problem even more.
That is, have patience, and keep on debugging.