Assertions that give full function call stack

V

virtual

I have a function called Set16. It takes a memory address as an
argument, and stores at that address a 16-Bit number in Big Endian
format. (I use it for networking programming). Here it is:

#include <assert.h>
#include <stdint.h>

void Set16(uint8_t *const p, uint_fast16_t const val)
{
assert(val <= 0xFFFFu);

p[0] = val >> 8;
p[1] = val & 0xFF;

}

Now the problem I have is that the assertion went off. My program was
running fine and then it just died and gave the filename and line
number of the assertion that went off. My program's been running fine
for weeks and then all of a sudden this happened.

So I did a "Find in files" for Set16, but there's a dozen places
from which this function is called, so I haven't a clue what went
wrong.

Is there any way of getting more elaborate assertions that give the
entire function call stack? For instance, something like:

Assertion failed:
In function: Set16 in frames.c at line 5
Called from: AssignChecksum in checksum.c at line 17
Called from: SendDNSLookup in net.c at line 2
Called from: main in main.c at line 27

My compiler is gcc under Linux. I'm using Codeblocks as my IDE.
 
J

James Kuyper

I have a function called Set16. It takes a memory address as an
argument, and stores at that address a 16-Bit number in Big Endian
format. (I use it for networking programming). Here it is:

#include <assert.h>
#include <stdint.h>

void Set16(uint8_t *const p, uint_fast16_t const val)
{
assert(val <= 0xFFFFu);
p[0] = val >> 8;
p[1] = val & 0xFF;

}

Now the problem I have is that the assertion went off. My program was
running fine and then it just died and gave the filename and line
number of the assertion that went off. My program's been running fine
for weeks and then all of a sudden this happened.d

So I did a "Find in files" for Set16, but there's a dozen places
from which this function is called, so I haven't a clue what went
wrong.

Is there any way of getting more elaborate assertions that give the
entire function call stack? For instance, something like:

You can't refer to the function call stack from within your code by any
portable mechanism. Using a debugger is my favorite non-portable
mechanism for doing this. Is that not an option?
 
N

Nate Eldredge

I have a function called Set16. It takes a memory address as an
argument, and stores at that address a 16-Bit number in Big Endian
format. (I use it for networking programming). Here it is:

#include <assert.h>
#include <stdint.h>

void Set16(uint8_t *const p, uint_fast16_t const val)
{
assert(val <= 0xFFFFu);

p[0] = val >> 8;
p[1] = val & 0xFF;

}

Now the problem I have is that the assertion went off. My program was
running fine and then it just died and gave the filename and line
number of the assertion that went off. My program's been running fine
for weeks and then all of a sudden this happened.

So I did a "Find in files" for Set16, but there's a dozen places
from which this function is called, so I haven't a clue what went
wrong.

Is there any way of getting more elaborate assertions that give the
entire function call stack? For instance, something like:

Assertion failed:
In function: Set16 in frames.c at line 5
Called from: AssignChecksum in checksum.c at line 17
Called from: SendDNSLookup in net.c at line 2
Called from: main in main.c at line 27

My compiler is gcc under Linux. I'm using Codeblocks as my IDE.

There isn't any way to do this using just the facilities of the standard
portable C language, short of manually keeping track of all function
calls. However...

[off-topic for comp.lang.c, crossposted and followups to comp.unix.programmer.]

On Unix-like systems, a failed assertion should raise SIGABRT. If
you're running the program under your debugger, this should cause it to
trap to the debugger at the failed assertion. You can then get a
backtrace, examine variables, etc. I don't know about the debugging
facilities of Codeblocks, but here's what happens in gdb.

nate@vulcan:/tmp/tst$ cat foo.c
#include <assert.h>
void f1(int x) {
assert(x > 0);
}
void f2(int x) {
f1(-x);
}
void f3(int x) {
f2(x+7);
}
int main(void) {
f3(-3);
return 0;
}
nate@vulcan:/tmp/tst$ gcc -g -o foo foo.c
nate@vulcan:/tmp/tst$ gdb ./foo
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 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 "amd64-marcel-freebsd"...
(gdb) run
Starting program: /tmp/tst/foo
Assertion failed: (x > 0), function f1, file foo.c, line 3.

Program received signal SIGABRT, Aborted.
0x00000008007097fc in kill () from /lib/libc.so.7
(gdb) where
#0 0x00000008007097fc in kill () from /lib/libc.so.7
#1 0x000000080070861b in abort () from /lib/libc.so.7
#2 0x00000008006f1f25 in __assert () from /lib/libc.so.7
#3 0x000000000040059a in f1 (x=-4) at foo.c:3
#4 0x00000000004005b7 in f2 (x=4) at foo.c:6
#5 0x00000000004005d6 in f3 (x=-3) at foo.c:9
#6 0x00000000004005ee in main () at foo.c:12
(gdb)

If you can't reproduce it inside your debugger, you can also enable core
dumps on your system (in bash, "ulimit -c unlimited" before you start
the program), and you should get one generated the next time the
assertion fails. If you compiled with -g, you can load the core file
into gdb (or Codeblocks, probably) along with the executable, and see
the complete program state at the time of the crash.
 
V

viza

Hi

uint_fast16_t can have more than 16 bits if doing so is just as fast as
having exactly 16. uint16_t has exactly 16 bits.

Here I have:
tcv@rose:~ $ csizeof uint16_t stdint.h
2
tcv@rose:~ $ csizeof uint_fast16_t stdint.h
8

void Set16(uint8_t *const p, uint_fast16_t const val) {
assert(val <= 0xFFFFu);

If your uint_fast16_t is uint16_t then this is necessarily true, and your
assert can never be triggered.

If it isn't, then you have to decide what to do when the function is
called with an argument that has a value that doesn't fit in 16 bits.

If your decision is to take the modulus 0x10000, then just replace
uint_fast16_t with uint16_t.

If it is something else, then get rid of the assert and write the
appropriate logic.

If you want to find where the function is called with an argument that is
bigger than just run your program in a debugger, such as gdb.

$ gcc -o foo foo.c
$ gdb foo
gdb> run [arguments]
Program received SIGABRT
gdb> bt
abort()
Set16()
foo()
bar()
main()
gdb> kill
gdb> quit
$

To find out how to do this in your IDE, read its manual.

HTH
viza
 
B

Bart van Ingen Schenau

I have a function called Set16. It takes a memory address as an
argument, and stores at that address a 16-Bit number in Big Endian
format. (I use it for networking programming). Here it is:

#include <assert.h>
#include <stdint.h>

void Set16(uint8_t *const p, uint_fast16_t const val)
{
assert(val <= 0xFFFFu);

uint_fast16_t can actually be larger than 16 bits. uint_fast16_t is
the fastest type on your processor that has at least 16 bits, so if
the processor is faster when working with 32 bit quantities,
uint_fast16_t will be 32 bits wide.

If Set16() is also used to encode values that could be negative, such
negative values could easily trigger the assertion if uint_fast16_t is
wider than 16 bits.

If the signature of the function can be changed, you can resolve the
issue by using the type 'uint16_t', which is guaranteed to be exactly
16 bits wide (or it does not exist).

Bart v Ingen Schenau
 
J

James Kuyper

Jujitsu Lizard wrote:
....
Nearly every compiler preprocessor provides the __FILE__ and __LINE__
macros.


All conforming implementations will predefine those macros, whether or
not they have separable preprocessor.
 
J

John Carter

Is there any way of getting more elaborate assertions that give the
entire function call stack? For instance, something like:

From the gnu libc info pages..

A "backtrace" is a list of the function calls that are currently active
in a thread. The usual way to inspect a backtrace of a program is to
use an external debugger such as gdb. However, sometimes it is useful
to obtain a backtrace programmatically from within a program, e.g., for
the purposes of logging or diagnostics.

The header file `execinfo.h' declares three functions that obtain
and manipulate backtraces of the current thread.

-- Function: int backtrace (void **BUFFER, int SIZE)
The `backtrace' function obtains a backtrace for the current
thread, as a list of pointers, and places the information into
BUFFER. The argument SIZE should be the number of `void *'
elements that will fit into BUFFER. The return value is the
actual number of entries of BUFFER that are obtained, and is at
most SIZE.

The pointers placed in BUFFER are actually return addresses
obtained by inspecting the stack, one return address per stack
frame.

Note that certain compiler optimizations may interfere with
obtaining a valid backtrace. Function inlining causes the inlined
function to not have a stack frame; tail call optimization
replaces one stack frame with another; frame pointer elimination
will stop `backtrace' from interpreting the stack contents
correctly.

-- Function: char ** backtrace_symbols (void *const *BUFFER, int SIZE)
The `backtrace_symbols' function translates the information
obtained from the `backtrace' function into an array of strings.
The argument BUFFER should be a pointer to an array of addresses
obtained via the `backtrace' function, and SIZE is the number of
entries in that array (the return value of `backtrace').

The return value is a pointer to an array of strings, which has
SIZE entries just like the array BUFFER. Each string contains a
printable representation of the corresponding element of BUFFER.
It includes the function name (if this can be determined), an
offset into the function, and the actual return address (in
hexadecimal).

Currently, the function name and offset only be obtained on
systems that use the ELF binary format for programs and libraries.
On other systems, only the hexadecimal return address will be
present. Also, you may need to pass additional flags to the
linker to make the function names available to the program. (For
example, on systems using GNU ld, you must pass (`-rdynamic'.)

The return value of `backtrace_symbols' is a pointer obtained via
the `malloc' function, and it is the responsibility of the caller
to `free' that pointer. Note that only the return value need be
freed, not the individual strings.

The return value is `NULL' if sufficient memory for the strings
cannot be obtained.

-- Function: void backtrace_symbols_fd (void *const *BUFFER, int SIZE,
int FD)
The `backtrace_symbols_fd' function performs the same translation
as the function `backtrace_symbols' function. Instead of returning
the strings to the caller, it writes the strings to the file
descriptor FD, one per line. It does not use the `malloc'
function, and can therefore be used in situations where that
function might fail.

The following program illustrates the use of these functions. Note
that the array to contain the return addresses returned by `backtrace'
is allocated on the stack. Therefore code like this can be used in
situations where the memory handling via `malloc' does not work anymore
(in which case the `backtrace_symbols' has to be replaced by a
`backtrace_symbols_fd' call as well). The number of return addresses
is normally not very large. Even complicated programs rather seldom
have a nesting level of more than, say, 50 and with 200 possible
entries probably all programs should be covered.

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to `stdout'. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;

size = backtrace (array, 10);
strings = backtrace_symbols (array, size);

printf ("Obtained %zd stack frames.\n", size);

for (i = 0; i < size; i++)
printf ("%s\n", strings);

free (strings);
}

/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
print_trace ();
}

int
main (void)
{
dummy_function ();
return 0;
}
 
G

Guest

Now the problem I have is that the assertion went off.

No, the problem is that you aren't an adequately skilled programmer.

This caused an error in your program, which caused your assertion to go off,
which is causing you to waste my time.




My program was
running fine and then it just died and gave the filename and line
number of the assertion that went off. My program's been running fine
for weeks and then all of a sudden this happened.
So I did a "Find in files" for Set16, but there's a dozen places
from which this function is called, so I haven't a clue what went
wrong.
Is there any way of getting more elaborate assertions that give the
entire function call stack? For instance, something like:
Assertion failed:
In function: Set16 in frames.c at line 5
Called from: AssignChecksum in checksum.c at line 17
Called from: SendDNSLookup in net.c at line 2
Called from: main in main.c at line 27
My compiler is gcc under Linux. I'm using Codeblocks as my IDE.

There are two different families of problems that could exist:

a)A mechanism that would cause an intermitent problem might exist.  Examples
would include a threading issue or a problem with function linkage.

b)It could be an "ordinary" logical problem in your program.

Let's cover (a) first.

Threading and similar problems -- don't have enough information about your
program to help.

Function linkage problem ... it is imperative that your C program have what
is called "full function prototype linkage"  This means that:

(a) You should enable the compiler warning that will warn/error if it
compiles a function before having seen a prototype.

(b) The defined function must see its prototype (which will cause the
compiler to generate an error if the function is incompatible with its
prototype).

(c) Each caller of a function must see the prototype, which will cause
either (c1) an error if the number of arguments or return/no return type is
incompatible or (c2) the arguments to be cast correctly to match the
prototype.

The standard way is to include the function prototypes in the .H file and to
include this in the .C file both where the function is defined and where
functions that call the function are located.

The definition is checked against the prototype, and the prototype is
checked against the invocation.

Here are two typical problems that might result:

#1)Assume you have a function returning nothing and the function that calls
it "uses" the returned value.  This will usually result in something
resembling a random return value.  What is "returned" depends on the
machine, interrupts, what functions were called before, etc.

#2)Assume you have a function that expects long as a parameter and you call
it as myfunc(3).  The compiler needs to know that "3" must be placed on the
stack as a long (or whatever the compiler's calling convention is).  When
you have this kind of mismatch, the trouble happens at the machine-language
level, and just about any behavior is possible (depending on the machine,
the O/S, and the compiler).  A very common behavior (when CPU registers are
used) is that the value "passed" is nearly always the same, until an
unrelated part of the program is changed, optimization settings are changed,
etc.

So, first, BE SURE YOUR PROGRAM HAS FULL PROTOTYPE LINKAGE.

And now (b).

If the problem isn't related to threading, function linkage, ...

You can just say:

void Set16(uint8_t *const p, uint_fast16_t const val, char *filename, int
linenum)
{
    if (val > 0xFFFFu)
       printf("My program has lost its mind in file %s at line number
%d.\n", filename, linenum);

    p[0] = val >> 8;
    p[1] = val & 0xFF;

}

and then change all the calls to:

Set16(p, val, __FILE__, __LINE__);

If the call is a logical problem only (and not related to the traditional
causes of intermittency), this will let you know which of the dozen calls
are causing the problem, and you can narrow it down from there.

Note that this will print the filename and line number of THE CALLER, not
the function.

Nearly every compiler preprocessor provides the __FILE__ and __LINE__
macros.

http://www.delorie.com/gnu/docs/gcc/cpp_21.html

Good luck,
The Lizard.- Hide quoted text -

- Show quoted text -
 
G

Guest

No, the problem is that you aren't an adequately skilled programmer.

you havn't a clue what his level of competence is

This caused an error in your program, which caused your assertion to go off,
which is causing you to waste my time.

so don't reply and keep your snotty reamrks to yourself.

There are two different families of problems that could exist:

a)A mechanism that would cause an intermitent problem might exist.  Examples
would include a threading issue or a problem with function linkage.

b)It could be an "ordinary" logical problem in your program.

c) platform error. eg. compiler, OS or hardware

ok so this is less likely, but it does happen

<snip>
 

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

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top