Debugging standard C library routines

A

achintmehta

I am working on MontaVista Linux on a PPC board.
The compiler I am using is a gnu cross compiler cross compiler
(ppc-82xx-gcc)

The application/software that is being run, is messing up the memory
due to which a subsequent malloc fails.

When the application core is dumped I do not get any backtrace in the
code (although the application is compiled with debug symbols, with -g
option). The only thing I get is the address of the function (and not
the name) where the crash is happening.

Using prints in the code I found that application crashes while calling
malloc. I am linking the standard C library as .so, and thats probably
why there is not backtrace conatining the functions of my application.

There is not enough memory on the board to run gdb. Also due to the
complexity of the scenarios it is not always possible to run gdbserver.


It there a possible way to:
1. Link a debuggable version of stdlib which would put more information
in the core, when it is dumped.
2. Can I enable some asserts or extra information in stdlib which would
tell me if memry is being messed up.
3. Is there a way by which I can map an address to a function of .so
loaded in the memory. Or even if i know that the address belongs t a
perticaular .so

Thanks in advance.
 
J

jacob navia

I am working on MontaVista Linux on a PPC board.
The compiler I am using is a gnu cross compiler cross compiler
(ppc-82xx-gcc)

The application/software that is being run, is messing up the memory
due to which a subsequent malloc fails.

When the application core is dumped I do not get any backtrace in the
code (although the application is compiled with debug symbols, with -g
option). The only thing I get is the address of the function (and not
the name) where the crash is happening.

Using prints in the code I found that application crashes while calling
malloc. I am linking the standard C library as .so, and thats probably
why there is not backtrace conatining the functions of my application.

There is not enough memory on the board to run gdb. Also due to the
complexity of the scenarios it is not always possible to run gdbserver.


It there a possible way to:
1. Link a debuggable version of stdlib which would put more information
in the core, when it is dumped.
2. Can I enable some asserts or extra information in stdlib which would
tell me if memry is being messed up.
3. Is there a way by which I can map an address to a function of .so
loaded in the memory. Or even if i know that the address belongs t a
perticaular .so

Thanks in advance.

You have a hard problem there.

To (1) and (2)

First thing, you do not need a full debug version of the c library but
just of malloc/free. I suppose you do not suspect asin() as beeing the
source of your problems. One such a debug library is
http://www.hexco.de/rmdebug/

To (3)

If I do an
objdump -T /lib/libc-2.2.4.so

I obtain a list of entry points with their addresses. If you get to know
at which address the .so is loaded you are all set.
 
R

Richard Heathfield

(e-mail address removed) said:
The application/software that is being run, is messing up the memory
due to which a subsequent malloc fails.

Initialise every pointer. Check every malloc/calloc/realloc to ensure it
succeeded before you rely on its return value. Check that every array
access is within bounds (0 to n - 1, for an array of n elements). Set
indeterminate ('dangling') pointers to NULL.

Following these simple steps will get rid of 99.90072% of all known
memory-related crashes.
 
F

Frederick Gotham

Richard Heathfield posted:
(e-mail address removed) said:


Initialise every pointer.


I wouldn't go so far as to advise to initialise _every_ pointer.

#define POVER(arr) ((arr) + sizeof(arr)/sizeof*(arr))

int *p;

for(p=array1;POVER(array1)!=p;++p) /* Something */ ;

for(p=array2;POVER(array2)!=p;++p) /* Something */ ;

Check every malloc/calloc/realloc to ensure it succeeded before you rely
on its return value.


If malloc'ing less than a kilobyte, I wouldn't bother.

Check that every array access is within bounds (0 to n - 1, for an array
of n elements).


One should _always_ watch out for such things when looking back over one's
code.

Set indeterminate ('dangling') pointers to NULL.


In Debug Mode, maybe. It wastes valuable nanoseconds (if not microseconds)
in Release Mode. Maybe something like the following would be a compromise:

#ifdef NDEBUG
#define DMODE(statement)
#else
#define DMODE(statement) statement
#endif

int main()
{
int *p;

...

free(p); DMODE(p=0);
}
 
R

Richard Heathfield

Frederick Gotham said:
Richard Heathfield posted:



I wouldn't go so far as to advise to initialise _every_ pointer.

At least until he is ready to apply for re-admission to civilisation, Mr
Gotham's opinion on style is of no consequence to me...
If malloc'ing less than a kilobyte, I wouldn't bother.

....but when he offers dangerously stupid advice such as that, it's a good
idea for people to point it out. Such stupid advice leads, when followed,
to buggy, unstable software.
In Debug Mode, maybe. It wastes valuable nanoseconds (if not microseconds)
in Release Mode.

Yet more stupid advice. Is there no beginning to this man's talent?
 
E

Eric Sosman

Frederick said:
Richard Heathfield posted:

If malloc'ing less than a kilobyte, I wouldn't bother.

Your camel's back can carry an infinite number of straws?

#include <stdlib.h>
#include <string.h>
int main(void) {
while (1) {
(char*)malloc(1) = 'X'; /* don't bother checking */
}
}
 
R

Richard Heathfield

Eric Sosman said:
Your camel's back can carry an infinite number of straws?

#include <stdlib.h>
#include <string.h>
int main(void) {
while (1) {
(char*)malloc(1) = 'X'; /* don't bother checking */

You mean *(char *)malloc(1) = 'X', of course, but your point is nevertheless
correct.
 
J

jacob navia

Frederick said:
Richard Heathfield posted:





I wouldn't go so far as to advise to initialise _every_ pointer.

#define POVER(arr) ((arr) + sizeof(arr)/sizeof*(arr))

int *p;

for(p=array1;POVER(array1)!=p;++p) /* Something */ ;

for(p=array2;POVER(array2)!=p;++p) /* Something */ ;

Initializing every pointer does no harm, but it is unlikely to
improve the situation of the original poster. Here we have a
malloc/free problem, so he needs to debug that part of the
library. And it is not an uninitialized pointer probably,
but a double free, or a more serius problem.
If malloc'ing less than a kilobyte, I wouldn't bother.

In a debug setting is better to never check malloc, so, if there is any
failure the program crashes immediately at the point of failure.

In a production setting it is obvious that some recovery action must be
started when there is no more memory.


This is like saying to the kids:

Be a good kid and do not do any stupid actions ok?

HOW could that advise be implemented? It is all the difficulty of
C. Telling people "check every array access" leads to no concrete
solutions for the OP.
One should _always_ watch out for such things when looking back over one's
code.

This is good advice. Start with KNOWN conditions.
 
F

Frederick Gotham

Richard Heathfield posted:
At least until he is ready to apply for re-admission to civilisation, Mr
Gotham's opinion on style is of no consequence to me...


Congratulations Great Dictator Richard. I shall rejoin your fascist regime
post haste.

...but when he offers dangerously stupid advice such as that, it's a good
idea for people to point it out. Such stupid advice leads, when followed,
to buggy, unstable software.


Many people disagree with you.

Yet more stupid advice. Is there no beginning to this man's talent?


You're on a roll today Richard, tossing your civility out the window so
that people will pay attention to your dim-witted attitude. You've yet to
call me a bonehead though -- that _really_ sealed the argument for you the
last time.
 
E

Eric Sosman

jacob said:
Frederick said:
Richard Heathfield posted:

Initializing every pointer does no harm, [...]

It does no harm to the running program, certainly. But
it *does* harm the process of developing the program, by
removing the compiler's ability to warn about certain kinds
of errors. Example:

char *next_field(char **start)
{
char *p /* = NULL */, *q /* = NULL */;
/* Skip white space to find the start of the field: */
p = *start + strspn(*start, " \t\f\r\n");
/* Skip non-whites to find the end of the field: */
p = p + strcspn(p, " \t\f\r\n");
/* Record where the next search should start: */
*start = q + (*q != '\0');
/* Zero-terminate the field just located: */
*q = '\0';
/* Return a pointer to its beginning: */
return p;
}

.... is an erroneous attempt to locate and snip a white-space-
delimited field from a string. The error is in the second
assignment: The result of the expression involving strcspn()
ought to have been assigned to q, not to p (we want p to point
to the start of the field, q to point just past its end). If
q is not initialized at the point of declaration, many compilers
will warn about its use in the third assignment: they will see
that it is being read without having been given a value and will
squawk about it. But if the declaration of q also initializes it,
the compiler won't complain about using a variable that may not
have been initialized, and the error may go undetected longer.

The cheapest errors are those not made in the first place.
The next-cheapest are those caught by the compiler and fixed
before committing the code. Errors that actually make it as far
as a testing phase -- or into deployment, may the Lord have mercy
on us! -- are more expensive than those caught earlier, so it is
a good idea to give the compiler every encouragement to catch
errors early. Wanton initialization of pointers (of any variables,
actually) discourages the compiler's assistance and therefore ought
not to be indulged in.
 
R

Richard Heathfield

jacob navia said:

Initializing every pointer does no harm, but it is unlikely to
improve the situation of the original poster.

What I gave was a list of several easy steps (initialisation being one of
those steps) which, taken together, make it easier to debug memory-related
problems. In that context, it will certainly improve the situation of the
original poster.

In a debug setting is better to never check malloc,

Oh deary deary me. You surely cannot be serious?
so, if there is any
failure the program crashes immediately at the point of failure.

The Standard offers no such guarantee.
This is like saying to the kids:

Be a good kid and do not do any stupid actions ok?

Yes. And if you're a good kid and don't do anything stupid, you improve the
chances that your code's going to work.
HOW could that advise be implemented?

for(i = 0; i < n; i++)
{
x = foo(i);
}

It's not rocket science.
It is all the difficulty of C.

I don't see what's so difficult about it. If you're writing outside your
array, your mental model of the program is wrong. And so, whether or not
the language cushions you from the bad effects of writing out of bounds,
you still need to fix the program. Anyone who can drive safely along the
road knows the importance, and *ease*, of keeping within bounds.
Telling people "check every array access" leads to no concrete
solutions for the OP.

Here's what I actually said:

"Initialise every pointer. Check every malloc/calloc/realloc to ensure it
succeeded before you rely on its return value. Check that every array
access is within bounds (0 to n - 1, for an array of n elements). Set
indeterminate ('dangling') pointers to NULL.

Following these simple steps will get rid of 99.90072% of all known
memory-related crashes."

I reckon that *will* lead to a concrete solution for the OP.
 
R

Richard Heathfield

Frederick Gotham said:
Richard Heathfield posted:


Many people disagree with you.

Yes, and many people aren't very good C programmers.

You're on a roll today Richard, tossing your civility out the window

Hypocrisy. In any case, you have surrendered your right to demand civility
from anyone else, by showing such appalling incivility to Keith Thompson -
as you are well aware.
so
that people will pay attention to your dim-witted attitude. You've yet to
call me a bonehead though -- that _really_ sealed the argument for you the
last time.

It seems that the point of that "bonehead" comment passed right over your
head.
 
F

Frederick Gotham

Richard Heathfield posted:
Hypocrisy. In any case, you have surrendered your right to demand
civility from anyone else, by showing such appalling incivility to Keith
Thompson - as you are well aware.

Allow me to reshape your words in an effort to show you how we express
ourselves in the non-fascist world:

"Hypocrisy, in my opinion. In any case, I think you have surrendered your
right to demand civility from anyone else, by showing what I deem to be
appalling incivility to Keith Thompson -- as I believe you are well aware."

In any case, may we end this Babysitters' Club masquerade? Neither you nor
Keith like me, that's fine -- I haven taken quite a disliking to the both of
you. Notwithstanding that, I'll continue to post here and respond to whatever
post captures my interest with regard to C programming, regardless of the
post's author. So do me a favour and save all your little diatribes for your
next teen book. If you want to talk about C, you're in the right place. I
quite like how Keith Thompson has accepted his disliking of me and ceased
responding -- please follow suit, it will make comp.lang.c a more pleasant
place.
 
R

Richard Heathfield

Eric Sosman said:
jacob said:
Frederick said:
Richard Heathfield posted:

Initialise every pointer.

Initializing every pointer does no harm, [...]

It does no harm to the running program, certainly. But
it *does* harm the process of developing the program, by
removing the compiler's ability to warn about certain kinds
of errors.

I beg to differ. I mean, yes, it has the effect that you say it has - on
some compilers, anyway - but it doesn't harm the development process at
all.
Example:

char *next_field(char **start)
{
char *p /* = NULL */, *q /* = NULL */;
/* Skip white space to find the start of the field: */
p = *start + strspn(*start, " \t\f\r\n");
/* Skip non-whites to find the end of the field: */
p = p + strcspn(p, " \t\f\r\n");
/* Record where the next search should start: */
*start = q + (*q != '\0');
/* Zero-terminate the field just located: */
*q = '\0';
/* Return a pointer to its beginning: */
return p;
}

Great example. Well done. If you omit the initialisation, okay, let's say
the compiler issues a warning (despite the fact that it needn't and some
don't). But you know and I know that some people will say "oh, it's only a
warning, it's fine", and they'll be scratching their heads trying to debug
it. Whereas, if you set q to NULL, then it doesn't take a genius to
discover that *start is being set to an obviously silly value (probably
NULL, possibly 1). So the debugging process is very swift after all.
The cheapest errors are those not made in the first place.
The next-cheapest are those caught by the compiler and fixed
before committing the code.

That's fine, provided people treat diagnostic messages seriously. We have
ample evidence here on comp.lang.c that this is not the case.
Wanton initialization of pointers (of any variables,
actually) discourages the compiler's assistance and therefore ought
not to be indulged in.

Giving the program deterministic behaviour by ensuring that all variables
are initialised helps the programmer to understand the program better and
debug it more quickly, and therefore ought to be encouraged. :)
 
R

Richard Heathfield

Frederick Gotham said:
In any case, may we end this Babysitters' Club masquerade? Neither you nor
Keith like me, that's fine

You are mistaken. I got on with you just fine until you called Keith a
fascist. I continue to hope that you will apologise to him because I
continue to believe you are a reasonable man, albeit somewhat stubborn.
 
S

sjdevnull

Frederick said:
Richard Heathfield posted:


In Debug Mode, maybe. It wastes valuable nanoseconds (if not microseconds)
in Release Mode. Maybe something like the following would be a compromise:

#ifdef NDEBUG
#define DMODE(statement)
#else
#define DMODE(statement) statement
#endif

Yikes, that sounds like a great way to introduce heisenbugs. I could
_maybe_ see this in a very tight inner loop, but aside from that it's
unlikely that the time difference would even be measureable within the
context of the whole program running, let alone noticeable.
 
E

Eric Sosman

Richard said:
Eric Sosman said:

jacob said:
Frederick Gotham wrote:


Richard Heathfield posted:

Initialise every pointer.

Initializing every pointer does no harm, [...]

It does no harm to the running program, certainly. But
it *does* harm the process of developing the program, by
removing the compiler's ability to warn about certain kinds
of errors.


I beg to differ. I mean, yes, it has the effect that you say it has - on
some compilers, anyway - but it doesn't harm the development process at
all.

Example:

char *next_field(char **start)
{
char *p /* = NULL */, *q /* = NULL */;
/* Skip white space to find the start of the field: */
p = *start + strspn(*start, " \t\f\r\n");
/* Skip non-whites to find the end of the field: */
p = p + strcspn(p, " \t\f\r\n");
/* Record where the next search should start: */
*start = q + (*q != '\0');
/* Zero-terminate the field just located: */
*q = '\0';
/* Return a pointer to its beginning: */
return p;
}


Great example. Well done. If you omit the initialisation, okay, let's say
the compiler issues a warning (despite the fact that it needn't and some
don't). But you know and I know that some people will say "oh, it's only a
warning, it's fine", and they'll be scratching their heads trying to debug
it. Whereas, if you set q to NULL, then it doesn't take a genius to
discover that *start is being set to an obviously silly value (probably
NULL, possibly 1). So the debugging process is very swift after all.

This programmer you describe seems a bit of an odd fish. He's
reckless enough to ignore warning messages, yet diligent enough to
initialize all his pointers. Sounds like a person with a multiple
personality disorder ...
That's fine, provided people treat diagnostic messages seriously. We have
ample evidence here on comp.lang.c that this is not the case.

It's interesting to read this in light of remarks about "drool-
proof languages" on another current thread ...

I feel -- without quantitative evidence, I admit -- that there's
more to be gained from improving a programmer's skills than by trying
to compensate for his deficiencies. A person can be taught to pay
attention to warnings, or to initialize everything in sight so the
warnings go away and their Heisenbugs become reproducible. The first
course seems to me to offer more benefits, on balance, than the second.
Yes, a Heisenbug can be a royal PITA -- but it's not a certainty that
a reproducible error arising from a wrongly-initialized variable will
be reliably detected, either. Even if that variable is a pointer and
the initialization is to NULL, it's not a sure bet that there will be
a catastrophic failure, easily spotted: Maybe the pointer will be used
as the argument to fflush() or the first argument to strtok() or in
some other context where NULL is perfectly legal -- but perhaps not
what was intended.
Giving the program deterministic behaviour by ensuring that all variables
are initialised helps the programmer to understand the program better and
debug it more quickly, and therefore ought to be encouraged. :)

Get rid of the STOP sign at the busy intersection, but make sure
that anyone who fails to stop *will* be run over by a cement truck.
Well, there won't be many repeat offenders ... (An old Dilbert strip
involved Dogbert trying to teach common sense to those who lacked it
rather conspicuously. "Larry the auto mechanic liked to smoke cigars
while working on gasoline engines," says Dogbert. "Can anybody think
of a problem this might cause?" The answer comes from someone swathed
in bandages and smoking a cigar: "He gets struck by lightning every
time?")
 
I

Ian Collins

Richard said:
Eric Sosman said:

jacob said:
Frederick Gotham wrote:


Richard Heathfield posted:

Initialise every pointer.

Initializing every pointer does no harm, [...]

It does no harm to the running program, certainly. But
it *does* harm the process of developing the program, by
removing the compiler's ability to warn about certain kinds
of errors.


I beg to differ. I mean, yes, it has the effect that you say it has - on
some compilers, anyway - but it doesn't harm the development process at
all.
Thank goodness C99 gave us the ability to declare variables at point of
use, this entire debate becomes moot.
 
C

Christopher Layne

Richard said:
it. Whereas, if you set q to NULL, then it doesn't take a genius to
discover that *start is being set to an obviously silly value (probably
NULL, possibly 1). So the debugging process is very swift after all.

Right. There should be an immediate crash upon (*q != '\0') .
Hopefully.
That's fine, provided people treat diagnostic messages seriously. We have
ample evidence here on comp.lang.c that this is not the case.

Yes. But is that really a good enough reason to say "always initialize every
pointer and tie your shoes before going outdoors?" Might as well never
use malloc and always use calloc while we're at it.
Giving the program deterministic behaviour by ensuring that all variables
are initialised helps the programmer to understand the program better and
debug it more quickly, and therefore ought to be encouraged. :)

I pretty much agree - but after a certain point where it reaches
compulsiveness, then it starts to head into doubting something you already
know or have enough experience with that pre-initializing ends up being token
exercise. What I mean by this is that, given a long chain of string
wrangling, like Eric pointed out, setting a pointer to NULL, beforehand, as a
method of insta-crash due to coding mistakes is almost like saying "I am
initializing this because I might make a mistake here." as opposed to
something like:

list_s *head = NULL;

Which has a functional purpose rather than a "I might screw up" purpose.
 
C

Christopher Layne

Ian said:
Thank goodness C99 gave us the ability to declare variables at point of
use, this entire debate becomes moot.

Yea instead the code looks like crap to read.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top